[[Prototype]]

在 JavaScript 中,对象具有一个隐藏属性 [[Prototype]],它指向另一个对象(或者 null),称为原型对象。当我们尝试访问对象缺失的属性或方法时,JavaScript 会自动从原型对象上找。
这个属性是隐藏在内部的,我们无法直接访问,但是,浏览器给我们提供了访问它的方式:__proto__

  1. let person = {
  2. name: 'Tom',
  3. age: 18
  4. };
  5. let student = {
  6. studentId: 1
  7. };
  8. student.__proto__ = person;
  9. console.log(student.name); // 'Tom'

给对象添加原型对象可以称为原型继承,这个继承关系可以一直串联,形成原型链。
注意:

  1. 引用不能形成闭环,否则 JavaScript 会报错
  2. __proto__ 是 [[Prototype]] 的 getter/setter,它们并不是一个东西,实现不相同
  3. for...in 会遍历继承的属性,但是 Object.keys() 不会

    F.prototype

    如果 F.prototype 是一个对象,那么 new 操作符会使用它为新对象设置 [[prototype]]。 ```javascript let person = { name: ‘Tom’, age: 18 }; function Student(studentId) { this.studentId = studentId; } Student.prototype = person;

let student = new Student(1); console.log(student.name); // ‘Tom’ console.log(student.proto === Student.prototype); // true

  1. 注意:只有在指定了 F.prototype 的值之后 new 出来的对象才有效果,之前已经存在的保持自身的值<br />当然,如果我们没有指定 F.prototype,它会有一个默认值,默认值中只有有一个属性 constructor,指向构造函数本身。
  2. ```javascript
  3. function Student() {}
  4. console.log(Student.prototype.constructor === Student); // true

对于一开始的 Student.prototype = person;,我们直接重写了它的整个 prototype,使得 constructor 丢失了。

有关原型方法

Object.getPrototype(obj)

返回 obj 的 [[Prototype]]

Object.setPrototype(obj, proto)

设置 obj 的 [[Prototype]]

Object.create(proto [, descriptors])

利用给定的 proto 和属性描述创建一个空对象

  1. let person = {
  2. name: 'Tom',
  3. age: 18
  4. };
  5. let student = Object.create(person, {
  6. studentId: {
  7. value: 1
  8. }
  9. });
  10. console.log(student.studentId); // 1

继承

原型链

  1. function Parent() {
  2. this.parentProperty = 'parentProperty';
  3. }
  4. Parent.prototype.getParentProperty = function () {
  5. return this.parentProperty;
  6. }
  7. function Son() {
  8. this.sonProperty = 'sonProperty';
  9. }
  10. Son.prototype = new Parent();
  11. Son.prototype.getSonProperty = function () {
  12. return this.sonProperty;
  13. }
  14. let instance = new Son();
  15. console.log(instance.getParentProperty()); // 'parentProperty'

原型链实现继承存在一个问题:如果父构造函数包含引用类型时,会在所有实例中共享。

盗用构造函数

  1. function Parent() {
  2. this.nums = [1, 2, 3];
  3. }
  4. function Son() {
  5. Parent.call(this);
  6. }
  7. let instance1 = new Son();
  8. instance1.nums.push(4);
  9. console.log(instance1.nums); // [1, 2, 3, 4]
  10. let instance2 = new Son();
  11. console.log(instance2.nums); // [1, 2, 3]

盗用构造函数使得每个实例不再共享父构造函数的引用类型的值,但是没法重用原型的方法,必须在构造函数中创建。

组合继承

  1. function Parent(name) {
  2. this.name = name;
  3. this.nums = [1, 2, 3];
  4. }
  5. Parent.prototype.sayName = function () {
  6. console.log(this.name);
  7. }
  8. function Son(name, age) {
  9. Parent.call(this, name);
  10. this.age = age;
  11. }
  12. Son.prototype = new Parent();
  13. Son.prototype.sayAge = function () {
  14. console.log(this.age);
  15. }
  16. let instance1 = new Son('Alice', 20);
  17. instance1.nums.push(4);
  18. console.log(instance1.nums); // [1, 2, 3, 4]
  19. instance1.sayName(); // 'Alice'
  20. instance1.sayAge(); // 20
  21. let instance2 = new Son('Bob', 18);
  22. console.log(instance2.nums); // [1, 2, 3]
  23. instance2.sayName(); // 'Bob'
  24. instance2.sayAge(); // 18

原型式继承

  1. function object(o) {
  2. function F() {};
  3. F.prototype = o;
  4. return new F();
  5. }

这个主要用于在现有对象的基础上再创建一个新对象,新对象的原型就是 o。
ES5 新增的方法 Object.create() 就是这个东西的规范化,并且支持属性描述符定义属性。

寄生式继承

  1. function createAnother(original) {
  2. let clone = object(original); // 通过调用函数创建一个新对象
  3. clone.sayHi = function () { // 以某种方式增强对象
  4. console.log('Hi');
  5. };
  6. return clone; // 返回这个对象
  7. }

终极方案:寄生式组合继承

  1. function inheritPrototype(parent, son) {
  2. let prototype = Object.create(parent.prototype);
  3. prototype.constructor = son;
  4. son.prototype = prototype;
  5. }
  6. function Parent() {}
  7. function Son() {
  8. Parent.call(this);
  9. }
  10. inheritPrototype(Parent, Son);