开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情
涅槃计划JS篇
原型是 JavaScript 中一座难以逾越的大山,难归难,该越还是得越。
定义
在 JavaScript 中,原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript 的对象中都包含了一个 [[Prototype]] 内部属性,这个属性所对应的就是该对象的原型。
prototype
每个函数都有一个 prototype 属性(prototype 是函数才会有的属性),当一个函数被用作构造函数来创建实例时,这个函数的 prototype 属性值会被作为原型赋值给所有对象实例。也就是说,所有实例的原型引用的是函数的 prototype 属性。
栗子
// 创建构造函数
function Family() {}
// 构造函数原型添加属性
Family.prototype.name = 'xiaoyu';
// 调用构造函数创建实例化对象
var member = new Family();
console.log(member.name) // xiaoyu
构造函数 Family 的 prototype 属性指向的就是栗子中 member 的原型。
知道了构造函数的原型 prototype,继续往下看↓
__proto__
所有 object 对象都有一个隐式引用,它被称之为这个对象的 prototype 原型。
obj 只声明了 name 和 sex 两个属性,在控制台却可以发现它有 __proto__ 属性,这意味着 obj 被隐式地挂载了另一个对象的引用,置于 __proto__ 属性中。
了解了这个属性后继续了解它们的关系↓
每一个对象的 __proto__ 属性都会指向该对象的原型
function Student()
var stu = new Student();
console.log(stu.__proto__ === Student.prototype); // true
绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于
Student.prototype中,实际上,它是来自于Object.prototype,与其说是一个属性,不如说是一个getter/setter,当使用obj.__proto__时,可以理解成返回了Object.getPrototypeOf(obj)。
constructor
实例对象和构造函数都可以指向原型,但是原型不能指向实例,因为一个构造函数可以生成多个实例。但是原型可以指向构造函数,每个原型都有一个 constructor 属性指向关联的构造函数。
// 所以可以得到
console.log(Family === Family.prototype.constructor); // true
我们知道了构造函数、实例原型、和实例之间的关系。对,我知道了,有什么用?别着急 Look down......
prototype chain 原型链
先分析实例和原型之间的关系,当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
// 构造函数Person
function Person() {}
Person.prototype.name = 'xiaoyu';
var menber = new Person();
// 给实例添加属性
menber.name = 'niuniu';
console.log(menber.name) // niuniu
我们给实例对象 menber 添加了 name 属性,打印 menber.name 结果大家都知道是 niuniu
// 删除实例自身属性
delete menber.name;
console.log(menber.name) // xiaoyu
当我们删除了
menber的name属性时,从menber对象中找不到name属性就会从menber的原型也就是menber.__proto__(Person.prototype) 中查找,然后找到了name属性为xiaoyu。
继续打印 menber 看看
可以看到 menber.[[Prototype]].[[Prototype]] 属性中还有很多其他的方法,那 menber 能调用吗?回答:当然可以,查找机制和查找属性一样。
再展开看看
可以看到最后的 __proto__ === null 了,或者说 Object.prototype.__proto__ === null,null 表示“没有对象”,即该处不应该有值,所以也可以理解为 Object.prototype 没有原型,所以查找属性的时候查到 Object.prototype 就可以停止查找了。