原型继承

360 阅读3分钟

笔记来自:zh.javascript.info/prototypes

[[Prototype]]

JavaScript 中,所有的对象都有一个隐藏的 [[Prototype]] 属性,它要么是另一个对象,要么就是 null

proto

  • 通过 [[Prototype]] 引用的对象被称为“原型”,用 obj.__proto__ 访问它
     let animal = {
       eats: true
     };
     let rabbit = {
       jumps: true
     };
    
     rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal
     // rabbit 能使用 animal 的所有属性和方法
     
     console.log(rabbit.eats) // true
    
    • __proto__ 的值可以是对象,也可以是 null。而其他的类型都会被忽略
    • __proto__ 与内部的 [[Prototype]] 不一样。__proto__[[Prototype]] 的 getter/setter。
    • __proto__ 属性有点过时了,用函数 Object.getPrototypeOf/Object.setPrototypeOf 来取代 __proto__ 去 get/set 原型

“this” 的值

无论在哪里找到方法:在一个对象还是在原型中。在一个方法调用中,this 始终是点符号 . 前面的对象。

    // animal 有一些方法
  let animal = {
    walk() {
      if (!this.isSleeping) {
        alert(`I walk`);
      }
    },
    sleep() {
      this.isSleeping = true;
    }
  };

  let rabbit = {
    name: "White Rabbit",
    __proto__: animal
  };

  // 修改 rabbit.isSleeping
  rabbit.sleep();

  alert(rabbit.isSleeping); // true
  alert(animal.isSleeping); // undefined(原型中没有此属性)

for…in 循环

  • for..in 循环会迭代继承的属性,即会获得原型的属性
  • Object.keys 只返回自己的 key
  • obj.hasOwnProperty(key):如果 obj 具有自己的(非继承的)名为 key 的属性,则返回 true
  • 思考 任务

F.prototype

F.prototype 属性仅在 new F 被调用时使用,它为新对象的 [[Prototype]] 赋值

function F() {}
let f = new F
f.__proto__ == F.prototype // true
  • 每个实例对象都会保持对旧的 prototype 的引用
  • 每个函数都有 prototype 属性,默认的 "prototype" 是一个只有属性 constructor 的对象,属性 constructor 指向函数自身。
    F.prototype.constructor == F // true
    
  • 通过实例对象的 constructor 来创建一个和它类似的对象
    let ff= new f.constructor() // ff 为 F 的实例对象
    
  • prototype 增加属性
    F.prototype.name = "ddd" // {name: "ddd", constructor: ƒ}
    

原生的原型

Object.prototype

let obj = {}
obj.__proto__  === Object.prototype // true
  • let obj = {} 创建了一个对象,其相当于 new Object(),因此 obj[[Protopype]] 会指向 Object.prototype

其他内建原型

内建对象,像 Array、Date、Function 及其他,都在 prototype 上挂载了方法。所有的内建原型顶端都是 Object.prototype 。(更直观的图示

  • null:Object.prototype.__proto__ === null
    let arr = [1, 2, 3];
    
    // 它继承自 Array.prototype?
    alert( arr.__proto__ === Array.prototype ); // true
    
    // 接下来继承自 Object.prototype?
    alert( arr.__proto__.__proto__ === Object.prototype ); // true
    
    // 原型链的顶端为 null。
    alert( arr.__proto__.__proto__.__proto__ ); // null
    

基本数据类型

  • 字符串、数字和布尔值上会被包装成对象,然后调用 String.prototypeNumber.prototypeBoolean.prototype 中的方法。
  • nullundefined 没有对象包装器

更改原生原型

String.prototype.show = function() {
   alert(this);
}

"BOOM!".show(); // BOOM!
  • 原型是全局的,所以很容易造成冲突

在现代编程中,只有一种情况下允许修改原生原型。那就是 polyfilling。Polyfilling 是一个术语,表示某个方法在 JavaScript 规范中已存在,但是特定的 JavaScript 引擎尚不支持该方法,那么我们可以通过手动实现它,并用以填充内建原型。

原型方法,没有 _proto_ 的对象

proto 被认为是过时且不推荐使用的,可以用新方法代替。现代的方法有:

  • Object.create(proto, [descriptors]) —— 利用给定的 proto 作为 [[Prototype]] 和可选的属性描述来创建一个空对象。
  • Object.getPrototypeOf(obj) —— 返回对象 obj 的 [[Prototype]]
  • Object.setPrototypeOf(obj, proto) —— 将对象 obj 的 [[Prototype]] 设置为 proto。
    let animal = {
      eats: true
    };
    
    // 创建一个以 animal 为原型的新对象
    let rabbit = Object.create(animal, {
      jumps: {
        value: true
      }
    });
    
      alert(rabbit.eats); // true
    
      alert(Object.getPrototypeOf(rabbit) === animal); // true
    
      Object.setPrototypeOf(rabbit, {}); // 将 rabbit 的原型修改为 {}
    
  • 比复制 for..in 循环中的属性更强大的对象克隆方式:
    let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
    // 此调用可以对 obj 进行真正准确地拷贝,包括所有的属性
    

"Very plain" objects

  • obj.__proto__ = 11 该表达式能否正确运行??(详情__proto__obj 的原型即 Object.prototype 的访问器属性,因此每次赋值的时候只会调用原型中对应方法。
  • 如何使 __proto__ 成为对象的属性?
    let o = Object.create(null) 
    // 原型为 null,没有继承的 __proto_ 访问器属性
    o.__proto__ = 111 // right