首发:https://zhuanlan.zhihu.com/p/91439580
原型,原型链属于js语言的基础,近期面试了很多同学,发现有很大一部分并不清楚原型,隐式原型,原型链之间的关系,加上自己在业务中分析源码遇到的一些问题,秉着知识回顾和总结的初心,详细总结下js原型相关的知识,有不足之处,还望大家补充。
几个概念:
原型:prototype
隐式原型:__proto__
本文将从创建对象的不同方法中引出原型链查找规则
创建对象的5个方法
在讲创建方法之前先来看下Object例子
const object_shadow = Object;
console.log(object_shadow)
console.log(typeof object_shadow) // function
console.log(typeof new object_shadow()) //object
调试上面的代码,可以得到如下结果:
object_shadow没有constructor,没有prototype
object_shadow的__proto__等于function (){}
=> function(){}的constructor等于 function Function(){}
=> function Function(){}的prototype 等于function() {}
可以得到:object_shadow._proto_ = object_shadow.constructor.prototype
说明:object_shadow在查找constructor没找到时,会通过__proto__向上查找,由于object_shadow并没有constructor,本质object_shadow会通过__proto__向上查找
另外Object是一个函数类型,所以可以使用new关键字
接下来看5中创建对象的方法
1,字面量创建对象
const test = {
title:'字面量创建对象'
}
调试test:
从结果可以看出直接创建,_proto_指向Object
2,构造函数创建
function a(){
this.title = "构造函数创建对象"
}
const A = new a()
执行过程:
创建一个字面量对象var obj = {}
设置新对象的constructor属性为构造函数的名称
指定obj的隐式原型等于a的prototype obj._proto_ = a.prototype
执行构造函数中的代码,构造函数中的this指向new出对象 a.call(obj)
将初始化完毕的新对象地址,保存到等号左边的变量中, 即赋值给A
调试A
可以看出:A._proto_ = A.constructor.prototype
3,Object.create()创建对象
const create_test = Object.create(test)
const create_a = Object.create(a)
create_test不满足 create_test._proto_ = create_test.constructor.prototype
create_a不满足 create_a._proto_ = create_a.constructor.prototype
console.log(create_a.title)//会得到undefined
Object.create(o)执行过程
// 创造一个新匿名函数
var F = function () {};
// 给该匿名函数的原型指向o
F.prototype = o;
// 返回该匿名函数的实例
return new F();
Object.create创建对象的过程包括了new
通过create创建的对象的_proto_指向构造函数F的prototype,通过上面的打印结果也可以看出。
4,class创建对象
class b {
title(){
return 'class创建对象'
}
}
class创建的对象默认有prototype属性,不需要通过_proto_向上查找
5,extends创建对象
class extends_b extends b{}
//下面三个均报错
class extends_A extends A{}//TypeError: Class extends value #<Object> is not a constructor or null
class extends_test extends test{}/TypeError: Class extends value #<Object> is not a constructor or null
class extends_create_test extends create_test{}//TypeError: Class extends value #<Object> is not a constructor or null
extends创建对象 的 _proto_和prototype均指向被继承对象
由上面5种创建方法可以看出
1,class创建对象的方式,对象默认有prototype属性
2,Object.create的创建的对象没有prototype属性,只有_proto_属性指向传入的对象
3,new a()创建方式 _proto_属性指向constructor为a的对象,也没有prototype属性
4,字面量创建对象_proto_属性指向Object,也没有没有prototype属性
原型链查找规则
来看一个例子:
const test = {
title:'字面量创建对象'
}
test.constructor.prototype.happy = 'constructor.prototype.happy'
test.constructor.prototype.pro_l = 'constructor.prototype.pro_l'
test.__proto__.pro_l = '_l'
console.log(test.pro_l)//'_l'
console.log(test.happy)//'constructor.prototype.pro_l'
create_a.constructor.prototype.__proto__.constructor.prototype.index = 3;
console.log(create_a.index)//3
test对象上没有happy,pro_l属性,默认向__proto_查找,此时如果_proto_上有属性,则获取,如果没有__proto_会指向_proto_的constructor的prototype,也就是会查找其构造函数的prototype,如果没有,继续查找当前_proto_的_proto_
再看一个例子
//A是上面的对象
A.__proto__.constructor.prototype.test_a = 'pro_test_a'
A.__proto__.__proto__.test_a = 'test_a'
console.log(A.test_a)//pro_test_a
如果A.__proto__.constructor.prototype.test_a不存在,则取A.__proto__.__proto__.test_a
//A是上面的对象
//A.__proto__.constructor.prototype.test_a = 'pro_test_a'
A.__proto__.__proto__.test_a = 'test_a'
console.log(A.test_a)//test_a
constructor的prototype上没有属性时,继续查找当前__proto__的_proto_
如图:
总结:
1,对象的_proto_和A.constructor.prototype不一定相等,比如Object.create()就不是
2,{}创建对象效率是最高的,不需要执行构造函数,_proto_直接指向Object
3,new 一个对象的过程,新对象的_proto_指向该构造函数的prototype
4,Object.create()对象内部包含了new 运算符,新对象的_proto_指向传入的对象
5,new和Object.create()的区别,_proto_是否指向传入的对象
6,原型链查找
对象的prototype>_proto_>对象的constructor.prototype>_proto_._proto_