【《你不知道的JS(上卷②)》】五、原型

五、原型:

一)、[[Prototype]]:

​ JS中的对象有一个特殊的[[Prototype]]内置属性,就是对于其他对象的引用。虽然可以为空,但是几乎所有的对象在创建时[[Prototype]]都会被赋予一个非空的值。

​ 前面提过引用对象的属性时会触发[[Get]]操作。对于默认的[[Get]]操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的[[Prototype]]链。如下所示:(如果在整条[[Prototype]]链中也无法找到,则会返回undefined

var anotherObject = {
  a:2
};

var myObject = Object.create(anotherObject);

myObject.a;  // 2
  • Object.create(..)会创建一个对象并把这个对象的[[Prototype]]关联到指定的对象。

1、Object.prototype:

​ 所有 普通的[[Prototype]]链最终都会指向内置的Object.prototype。包含了 .toString()、.valueOf()、.hasOwnProperty()、.isPrototypeOf()等方法。

2、属性设置和屏蔽:

给一个对象设置属性并不仅仅是添加一个新属性或者修改已有的属性值。

myObject.foo = "bar";
  1. 如果该对象包含名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性值。

  2. 如果foo不是直接存在于该对象中,[[Prototype]]链就会被遍历。

    1. 如果原型链中找不到该属性,foo属性就会被添加到myObject上。
    2. 如果foo位于原型链上层:
      1. 如果foo属性没有被标记为 只读(writeable:false),就会直接在myObject中添加foo属性,这就是屏蔽属性
      2. 如果标记为只读,严格模式下会抛出错误,否则会被忽略。
      3. 如果foo是一个setter,则会被调用,并且也不会被添加到myObject中。
  3. 如果即出现在myObject中,也出现在原型链上层中。就会发生 屏蔽myObject.foo总是会选择原型链中最底层的foo属性。

二)、“类”:

​ 之所以使用[[Prototype]],是因为JS与其他面向对象语言不同。JS中,类无法描述对象的行为,对象直接定义自己的行为。

1、“类”函数:

​ 在JS中,模仿”类“一直在被滥用。上一章提到了,JS中不存在”类“的继承,只是将两个对象互相关联。将这种行为看作其他语言中的类的继承会导致副作用。

​ 继承意味着复制操作,JS(默认)并不会复制对象属性。相反,JS会在两个对象之间创建一个关联,这样一个对象就可以通过 委托(本卷第六章将会详细介绍)访问另一个对象的属性和函数。

差异继承:

​ 基本原则:在描述对象行为时,使用其不同于普遍描述的特质。

2、”构造函数“:

​ JS中有几点会让人误以为存在类:

function Foo() {
  // ...
}

Foo.prototype.constructor === Foo;  // true

var a = new Foo();
a.constructor === Foo;  // true
  • 使用new关键字,看起来像是执行了类的构造函数。
  • prototype中默认有一个constructor属性,引用的是对象关联的函数。(a本身并没有constructor属性,只是被关联的)。
  • ”类“名首字母大写

​ 实际上new关键字可以用于任何函数。new会劫持所有普通函数并用构造对象的形式来调用它。

在JS中,”构造函数“就是所有带new的函数调用。函数不是构造函数,但是当且仅当使用new时,函数调用会变成”构造函数调用。“

三)、(原型)继承:

function Foo(name) {
  this.name = name;
}

Foo.prototype.myName = function() {
  return this.name;
};

function Bar(name, label) {
  Foo.call(this, name);
  this.label = label;
}

Bar.prototype = Object.create(Foo.prototype);

Bar.prototype.myLabel = function() {
  return this.label;
};

var a = new Bar("a", "obj a");

console.log(a.myName());  // a
console.log(a.myLabel());  // obj a

上例就是典型的“原型风格”。

Object.create(..)会凭空创建一个”新“对象并把新对象内部的[[Prototype]]关联到你指定的对象。

其他方法:

  1. Bar.prototype = Foo.prototype;

    该语句不会创建新对象,而是直接引用Foo原型,此后的修改会直接修改Foo的原型。

  2. Bar.prototype = new Foo();

    基本符合需求,但是存在副作用(见下文)。

  3. Object.setPrototypeOf(Bar.prototype, Foo.prototype);

    ES6的新方法,这样可以直接改变对象原型,而不需要创建一个新对象然后抛弃旧对象。

检查”类“关系:

​ 假设有对象a,如何寻找对象a委托的对象(如果存在的话)呢?

​ 在传统的面向类环境中,检查一个实例(JS中的对象)的继承祖先(JS中的委托关联)通常被称作内省(反射)。JS中存在a instanceof Foo;Foo,prototype.isPrototypeOf(a);a.__proto__ === Foo.prototype;等方法来判断[[Prototype]]反射。

四)、对象关联:

​ [[Prototype]]机制就是存在于对象中的一个内部链接,它会引用其他对象。通常来说,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就会继续在[[Prototype]]关联的对象上进行查找。 这一系列对象的链接被称为“原型链”

​ 前面在 (原型)继承中提过Object.create(..),它的重要性在于:

  1. 充分发挥[[Prototype]]机制的威力(委托)。
  2. 避免new的构造函数调用生成 .prototype、.constructor引用

内部委托:

var anotherObject = {
  cool: function () {
    console.log("cool");
  }
};

var myObject = Object.create(anotherObject);

myObject.doCool = function () {
  this.cool();  // 内部委托
};

myObject.doCool();  // cool
  • 内部委托比起直接委托可以让API接口设计更加清晰

五)、小结:

​ 在JS中,“委托”比“继承”更何叔来描述JS中的机制,因为对象之间的关系不是复制而是委托。

原文地址:https://www.cnblogs.com/enmac/p/13179924.html