原型 & 继承一 - 你不知道的 JS

  1. __proto__ 与 [[Prototype]]
    1. 区别
  2. this 的指向
  3. for in 遍历
  4. 练习

对象有个特殊的隐藏属性 [[Prototype]]

__proto__[[Prototype]]

属性 [[Prototype]] 是内部的而且是隐藏的,但是有设置此属性的方法。
例如,__proto__

let animal = {
    eats: true
};
let jiaoshou = {
    jumps: true
}
jiaoshou.__proto__ = animal;  // 将 animal 设为 jiaoshou 的原型

现在,如果从 jiaoshou 读取一个它没有的属性,JS 会顺着 [[Prototype]] 到 animal 中寻找:

jiaoshou.eats => true
animal['f'] = function() {console.log('f**k')}
jiaoshou.f() => "fuck"

我们可以说,animal 是 jiaoshou 的原型。或者说,jiaoshou 的原型是从 animal 继承来的。animal 中的属性和方法自动地在 jiaoshou 中可用,这就是继承。

这种继承方式有书写限制:

  1. 引用不能形成闭环。会报 TypeError: Cyclic __proto__ value
  2. __proto__ 只能是 null 或者对象,其他的值不生效。

NOTE, [[Prototype]] 只有一个。一个对象不能同时从两个对象获得直接继承。

区别

  1. __proto__[[Prototype]] 不一样,__proto__[[Prototype]] 的 getter 和 setter。
  2. __proto__ 属性是历史遗留的老家伙。可以使用 Object.get/set/PrototypeOf 替代。

this 的指向

在一个方法调用中, this 始终是点符号 . 前面的对象。

let user = {
    name: "John",
    surname: "Smith",
    firends: [1, 2],
    set fullName(value) {
        [this.name, this.surname] = value.split(" ");
    },
    get fullName() {
        return `${this.name} ${this.surname}`;
    },
    get fri() {
        return this.firends;
    },
    set fri(f) {
        this.firends.push(f);
    }
};
let admin = {
    __proto__: user,
    isAdmin: true
};
console.log(admin.fri);  // [1,2]
console.log(admin.fri == user.fri);  //true
admin.fri = 3;
console.log(admin.fri, user.fri);  // [1, 2, 3] [1, 2, 3]
console.log(admin.fri == user.fri);  //true

admin.hasOwnProperty("surname") // false
console.log(admin.fullName);  // John Smith
admin.fullName = "lu you";
console.log(admin.fullName);  // "lu you"
console.log(user.fullName);  // "John Smith"
admin.hasOwnProperty("surname") // true

当我们调用 fullName 的 setter 时,写入了 this,所以会将其存储到这个 this 对应的对象,也就是 admin 中,让 admin 有了自己的 name 和 surname 属性。

for in 遍历

for in 遍历会遍历可枚举属性。包括继承的可枚举属性。
使用 hadOwnProperty 可以过滤掉继承来的属性。

练习

  1. let animal = {
     jumps: null
    };
    let rabbit = {
     __proto__: animal,
     jumps: true
    };
    alert( rabbit.jumps ); // ? (1) 
    delete rabbit.jumps;
    alert( rabbit.jumps ); // ? (2)
    delete animal.jumps;
    alert( rabbit.jumps ); // ? (3)

2.

let head = {
    glasses: 1
};
let pockets = {
    money: 2000
};
pockets.__proto__ = head;

通过 pockets.glasseshead.glasses 获取 glasses ,哪个更快?

  1. 为什么两只仓鼠都饱了?
    我们有两只仓鼠: speedylazy 都继承自普通的 hamster 对象。
    当我们喂其中一只的时候,另一只也吃饱了。为什么?如何修复它?
    let hamster = {
     stomach: [],
     eat(food) {
         this.stomach.push(food);
     }
    };
    let speedy = {
     __proto__: hamster
    };
    let lazy = {
     __proto__: hamster
    };
    

speedy.eat(“apple”);
alert( speedy.stomach ); // apple
// 这只仓鼠也找到了食物,为什么?请修复它。
alert( lazy.stomach ); // apple



---


答案:
1. (1)true,直接访问自己的 jumps 属性;(2)null,访问 animal 的(3)undefined,找不到此属性
2. 在现代引擎中,从性能的角度来看,我们是从对象还是从原型链获取属性都是没区别的。因为访问一次后就缓存了。
3. 因为 `speedy.stomach == lazy.stomach => true`,都指向他们的原型 --hamster 对象的 `stomach 属性 `。调用 `speedy.eat` 方法时,改动的也是 `stomach 属性 `。因此,所有的仓鼠共享了同一个胃!




转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。
我的空间