简述原型链
背景
JavaScript 是动态的,本身不提供一个 class 的实现。即便是在 ES2015/ES6 中引入了 class 关键字,但那也只是语法糖,JavaScript 仍然是基于原型的
当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
链接:https://developer.mozilla.org...
先说几个名词概念
- prototype 显式原型
- proto 隐式原型
介绍下 引用类型
- 都具有对象特性,即可自由扩展属性。
- 都有一个隐式原型 proto 属性,属性值是一个普通的对象。
- 隐式原型 __proto__ 的属性值指向它构造函数的显式原型 prototype 的属性值。
- 当获取一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的隐式原型 __proto__(也就是它的构造函数的显式原型 prototype)中寻找。
看个
function Student(name) {
this.name = name
}
const stu = new Student('xiaomin')
stu.name // 'xiaomin'
// toString 为对象常用方法,返回一个表示该对象的字符串
stu.toString() // '[object Object]'
// 给 toString 赋值
stu.prototype.toString = function stuToString() {
return `${this.name}`;
};
console.log(stu.toString());
// expected output: "Gabby"
问题抛出:为什么 stu 一开始没有定义 toString 却能获取到呢?
stu 实例先从自身出发查找,发现没有 toString 方法。找不到,就往上走,找 Student 构造函数的 prototype 属性,还是没找到。由于构造函数的 prototype 是一个对象,那对象的构造函数是 Object ,所以就找到了 Object.prototype 下的 toString 方法。
这里补充说明下,如果最终都未找到,则返回 undefined。并且此时 Object.prototype 的隐式原型指向 null
tips: null 是为了避免死循环而设置的
stu.xx // undefined
引申一个概念:instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car);
// expected output: true
console.log(auto instanceof Object);
// expected output: true
instabceof 实现方式(原理)
// 变量R的原型 存在于 变量L的原型链上
function instance_of (L, R) {
// 验证如果为基本数据类型,就直接返回 false
const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']
if(baseType.includes(typeof(L))) { return false }
let RP = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null) { // 找到最顶层
return false;
}
if (L === RP) { // 严格相等
return true;
}
L = L.__proto__; // 没找到继续向上一层原型链查找
}
}
instanceof 判断流程如下:
- f 的隐式原型 __proto__ 和 Foo.prototype, 是相等的所以返回 true 。
- f 的隐式原型 __proto__ ,和 Object.prototype 不等, 所以继续往上走。f 的隐式原型 __proto__ 指向 Foo.prototype ,所以继续用 Foo.prototype.__proto__ 去对比 Object.prototype, 判断相等返回 true。(因为 Foo.prototype 就是一个普通的对象)
参考链接:https://juejin.cn/post/693449...
丰富一下知识
基于原型链的继承
1.继承属性
JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
给对象设置属性会创建自有属性。获取和设置行为规则的唯一例外是当继承的属性带有 getter 或 setter 时(没能理解)。
2.继承方法
在 JavaScript 里,任何函数都可以添加到对象上作为对象的属性。函数的继承与其他的属性继承没有差别
当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象
var o = {
a: 2,
m: function(){
return this.a + 1;
}
};
console.log(o.m()); // 3
// 当调用 o.m 时,'this' 指向了 o.
var p = Object.create(o);
// p是一个继承自 o 的对象
p.a = 4; // 创建 p 的自身属性 'a'
console.log(p.m()); // 5
// 调用 p.m 时,'this' 指向了 p
// 又因为 p 继承了 o 的 m 函数
// 所以,此时的 'this.a' 即 p.a,就是 p 的自身属性 'a'