JS深入理解原型链

283 阅读3分钟

原型链

图解原型链过程以及相关关系

下图来自(juejin.cn/post/684490…) 原型链

原型链寻找属性顺序

  • 寻找自身的属性挂载
  • 通过__proto__到达其构造函数的原型(Person.prototype)中寻找属性
  • 通过__proto__再到构造函数的原型的原型寻找属性
  • 继续上一步直到null

注意:

  • 如果未找到属性,属性值是undefined,原型链是终点的null
  • 如果属性的值就是undefined,那就需要用hasOwnProperty('属性')来判断是否存在该属性(hasOwnProperty()只会对自身进行检查,并不会对原型链进行检查)

下一例子

function Person(){
}
p=new Person() // 执行[[prototype]]连接 ,即p.__proto__ = Person.prototype
Person.len =2
Person.prototype.number =345 // 为Person原型挂载属性
console.log(p.number) // 345  
console.log(p.len) // undefined
console.log(Person.number) //undefined 寻找Person.__proto__,而不是Person.prototype

p.number 的寻找顺序

  • 寻找自身的属性,没有找到
  • 向原型寻找,即寻找p.__proto__中的属性

只有prototype上的属性才会被继承

设置属性

给对象设置属性,会先执行上面的寻找属性

未找到该属性

如果没找到,就将该属性添加到这个对象

找到该属性

  • 如果这个属性是在原型上(不是自身)并且是普通数据访问属性(没有被标记已读),并且不是Setter,则会发生属性屏蔽,即将忽略原型上的该属性,将该属性和属性值挂载到自身的对象上,该属性就叫屏蔽属性
function Person(){
}
p=new Person() // 
Person.prototype.number =345 
console.log(p.number) // 345
p.number =2
console.log(Person.prototype.number) // 345
console.log(p.number) // 2
  • 如果这个属性是在原型上但是是只读属性,则无法对自身挂载新属性,也无法修改已有属性,默认忽略.在严格模式下就会报错
function Person(){
}
p=new Person() // 执行[[prototype]]连接 ,即p.__proto__ = Person.prototype
Object.defineProperty(Person.prototype,"number",{
  value: 345,
  writable: false // 设置只读
})
console.log(p.number) // 345
p.number =2;
console.log(p.number) // 345
  • 如果这个属性在原型上但是是Setter的,则就一定会调用这个Setter,不会添加新属性到对象上,也不会重新定义这个属性
function Person(){
}
p=new Person() 
Object.defineProperty(Person.prototype,"number",{
  get(){
    return value
  },
  set(getValue){
    value = getValue*2
  }
})
p.number =2; // 
console.log(p.number) // 4 
console.log(Person.prototype.number) // 4
console.log(p.hasOwnProperty('number')) // false 本质上是给原型链上的number属性赋值4
console.log(p.__proto__.number===Person.prototype.number)// p.number是调用p.__proto__.number

instance

看下一经典例子

console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true

MDN: instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链

有点奇怪是吧, 结合上述例子Function的prototype属性出现在了自身的原型链上,怎么回事呢?下面逐一分析

(L)Object instanceof (R)Object

需要判断LObject 的 原型链中是否存在RObject的prototype(即Object.prototype),寻找只能对左值进行寻找

// 第一层寻找
console.log(Object.__proto__) // Function.prototype(Object的构造函数是Function)
console.log(Object.__proto__ === Function.prototype) // true

//第二层寻找
console.log(Object.__proto__.__proto__ === Object.prototy) // Object.prototype
console.log(Function.prototype.__proto__)

关系图

如上图,由于Object的构造函数是Funtion ,所以Object.__proto__Function.__proto__,又因为Function.prototype.__proto__Object.prototype,所以成立

(类型的构造函数都是Function? 所有没有显式new生成的函数的构造函数都为Function)

Function instanceof Function

同理,需要寻找左Function的原型链中是否存在Function.prototype

Function.__proto__等于Function.prototype

所以成立

fn instanceof fn
function fn(){
}

需要寻找左fn的原型链中是否存在fn.prototype

fn.__proto__为Function.prototype , fn.__proto__.__proto__等于Object.prototype ,fn.__proto__.__proto__.__proto__ 等于null

所以不成立

function fn(){
}
console.log(fn instanceof fn) //false
console.log(fn.__proto__=== Function.prototype) // true
console.log(fn.__proto__.__proto__ === Object.prototype) // true
console.log(fn.__proto__.__proto__.__proto__ === null) // true

对象关联

我们多用Object.create()来进行对象关联,接下来逐一分析

var foo={
    showName :function(){
        console.log('qianlon')
    }
}
var anotherFoo =Object.create(foo) // 对象关联
anotherFoo.showName() // qianlon
console.log(anotherFoo.__proto__ === foo) // true

Object.create本质是将右值(foo)添加到左值(anotherFoo)的原型链中,也就是上例中第7行anotherFoo.showName()本质上调用的是anotherFoo.__proto__.showName()

同样我们可以模仿实现Object.create()函数以做兼容性处理和底层了解

// Object.create() (ES5) 
if(!Object.create){
    Object.create=function(foo){
        function fn={}
        fn.prototype =foo
        return new fn()
    }
}