深入原型和原型链
amandakelake opened this issue · 4 comments
对象都是通过函数创建的,函数是对象(剪不断,理还乱,是离愁,别是一番滋味在心头)
一、Prototype
每个函数都有这属性,有且仅有函数才拥有该属性
但有个例外:
// 用这个方法创建的函数是不具备prototype属性的
let fun = Function.prototype.bind()
Prototype
属性里面默认有一个属性constructor
,对应着构造函数
constructor
是一个公有的而且不可枚举的属性,一旦改写了函数的Prototype
,新对象就没有这个属性了
二、__proto__
每个对象都有一个隐藏属性__proto__
,这个属性引用了创建这个对象的构造函数的原型
fn.__proto__ === Fn.prototype
__proto__
将对象和原型连接起来组成了原型链
有三个要注意的地方
1、Object.prototype.__proto__ === null
对象的原型的__proto__
属性是null
2、Object.__proto__ === Function.prototype
既然对象是函数创建的,那么对象的__proto__
要指向创建它的构造函数的原型
3、Function
的特殊性Function.__proto__ === Function.prototype
函数也是被Function
创建的,那么函数的__proto__也应该指向Function
的原型,这是一个环形结构,函数的prototype
和__proto__
属性指向同一个对象
Function.prototype
和 Object.prototype
是两个特殊的对象,他们由引擎来创建
三、new
除了以上Function.prototype
和 Object.prototype
两个特殊对象,其他对象都是通过构造器 new 出来的
先看看new的实现过程
- 声明一个中间对象;
- 将该中间对象的原型指向构造函数的原型;
- 将构造函数的this,指向该中间对象;
- 返回该中间对象,即返回实例对象。
function create() {
// 创建一个新的对象
let obj = new Object();
// 取出第一个参数,该参数就是我们将会传入的构造函数
// arguments会被shift去除第一个参数
let Constructor = [].shift.call(arguments);
// 将obj的原型指向构造函数,此时obj可以访问构造函数原型中的属性
obj.__proto__ = Constructor.prototype;
// 改变构造函数的this的指向,使其指向obj
// 此时obj也可以访问构造函数中的属性了
let result = Constructor.apply(obj, arguments);
// 确保 new 出来的是个对象
// 返回的值是什么就return什么出来
return typeof result === 'object' ? result : obj
}
所以平时更推荐使用字面量的方式创建对象,因为使用new Object()
需要通过作用域链往上层层寻找Object
因为存在我们创建了一个同名的构造函数
Object()
的可能,当调用Object()
的时候,解析器需要顺着作用域链从当前作用域开始查找,如果在当前作用域找到了名为Object()
的函数就执行,如果没找到,就继续顺着作用域链往上照,直到找到全局Object()构造函数
为止
四、原型链
上面说__proto__
将对象和原型连接起来组成了原型链
具体起来说就是:访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__
这条链向上找,这就是原型链
来看个例子
function Foo() {};
let f1 = new Foo();
f1.a = 1;
Foo.prototype.a = 10;
Foo.prototype.b = 20;
// 打印自身属性和原型属性
for(const item in f1) {
console.log(item)
}
// 打印自身属性
for (const item1 in f1) {
if (f1.hasOwnProperty(item1)) {
console.log(item1);
}
}
稍等一下,hasOwnProperty
这个方法从哪找的?
f1
和Foo.prototype
都没有啊
很好,它从Object.prototype
继承而来的,由于__proto__
的存在,可以查找下面这条链条
f1
=> Foo.prototype
=> Object.prototype
最终找到了Object.prototype
上定义的各种原生方法
那我们平时看见的call
、apply
,还有数组的一些方法呢?
它们分别在Function.prototype
,Array.prototype
上面,我们日常使用的原生方法都继承于这几个对象,可以尝试去浏览器控制台打印这两看看
console.dir(Function.prototype);
console.dir(Array.prototype)
原型、原型链我说清楚了吗?
参考资料
深入理解javascript原型和闭包(1)——一切都是对象 - 王福朋 - 博客园
深度解析原型中的各个难点 · Issue #2 · KieSun/Blog · GitHub
加个微信吧!
Prototype属性里面默认有一个属性constructor,对应着构造函数
这句话读了两三遍,这样可能更好一点
构造函数的原型属性里面默认有一个constructor属性,指想构造函数
很好,它从Object.prototype继承而来的,由于__proto__的存在,可以查找下面这条链条
f1 => Foo.prototype => Object.prototype
这个应该是
很好,它从Object.prototype继承而来的,由于__proto__的存在,可以查找下面这条链条
f1 => Foo.__proto__=> Object.prototype
很好,它从Object.prototype继承而来的,由于__proto__的存在,可以查找下面这条链条 f1 => Foo.prototype => Object.prototype
这个应该是
很好,它从Object.prototype继承而来的,由于__proto__的存在,可以查找下面这条链条 f1 => Foo.__proto__=> Object.prototype
能加一下你微信吗?