理解 prototype 、__proto__及原型链

122 阅读5分钟

instanceof

instanceof 运算符用来检测 constructor.prototype(构造函数的原型) 是否存在于参数 object 的原型链上。它的实质就是通过 __proto__属性不断地在原型链上查找,找到返回true

obj instanceof constructor
<!--检测obj.__proto__==constructor.prototype-->

实现instanceof

function myInstanceof(instance, obj) {
  let proto = instance.__proto__
  let prototype = obj.prototype
  while (true) {
    if (proto === null) return false
    if (proto === prototype) return true
    proto = proto.__proto__
  }
}

// test
function Foo(name){
this.name=name
}
let ins=new Foo('avicii')
console.log(myInstanceof(ins,Foo)); //true

Object.create(proto[,propertiesObject])

obj=Object.create(proto[,propertiesObject]),中 产生的新对象(obj)指向一个空的proto对象,不继承proto上面的属性和方法,但是因为这个方法使obj.__proto__=proto,所以obj能够通过原型链(__proto__)访问proto上面的方法和属性.proto最好为一个普通对象或者函数的原型对象,这样符合原型链规则.


Object.create(proto[, propertiesObject])我认为分两种使用情况, 一种是参数proto是一个对象,另一种是参数proto是一个函数的原型对象


Object.create(obj[, propertiesObject])

创建的新对象(ins)指向一个空的me对象,因为没有做相应的操作使ins继承obj里面的属性和方法,只是将ins.__proto__=me,ins能访问原型链上面各原型对象里面定义的方法和属性.

let me = {
  name: "avicii"
}
let ins2 = Object.create(me)
console.log(ins2.__proto__ == me); //true
console.log(ins2, me, ins2.name); //{} { name: 'avicii' } avicii
console.log(ins2.name==me.name,ins2.prototype==me.prototype,ins2.__proto__.prototype==me.prototype,ins2.prototype==ins2.__proto__.prototype); //true true true true
// ins2指向一个空的me对象,因为ins2没有通过相应操作继承me里面定义的属性和方法,只是ins2.__proto__==me
// ins2可以通过原型链访问这个对象的属性和方法proto__
//因此ins2.prototype==me.prototype实质上是因为ins2本身还没有定义prototype,所以它顺着原型链(__proto__)找到me.prototype的内存地址引用,
// ins2.prototype,ins2.__proto__,me.prototype三者的内存地址是同一个
// 同理ins.name和me.name

Object.create(fn.ptorotype[, propertiesObject])

创建的新对象(ins)指向一个空的fn.prototype对象,不继承在fn.prototype上面定义的方法和属性,但是因为ins.__proto__=fn.prototype,因此ins能访问fn.prototype上面定义的方法和属性,而fn里面定义的属性和方法需要通过new fn()等操作使ins继承fn里面定义的属性和方法

function Say() {
  this.name = 'avicii'
}
function newFunc() {
  this.name = 'mxalive'
}
Say.prototype.age = 28
let ins1 = Object.create(Say.prototype)
newFunc.prototype = Object.create(Say.prototype)
newFunc.prototype.face = 'cool'
let ins2 = Object.create(newFunc.prototype)
console.log(ins1.__proto__ == Say.prototype); //true
console.log(ins1, Say.prototype, ins1.age);// Say {} Say { age: 28 } 28
// ins1指向一个空的Say.prototype对象,因为没有通过new 操作使ins1继承Say函数里面定义的属性和方法,
// 只是ins1.__proto__==Say.prototype
// ins可以通过原型链访问这个对象的属性和方法
console.log(ins2, newFunc.prototype, ins2.age, ins2.face);// Say {} Say { face: 'cool' } 28 cool
//ins2指向一个空的newFunc.prototype对象,而newFunc.prototype因为newFunc.prototype = Object.create(Say.prototype)从而指向了一个空的Say.prototype对象
// 因此ins2最后还是指向了一个空的Say.prototype对象,打印出Say {},ins2可以通过原型链(__proto__)访问newFunc.prototype和Say.prototype上面定义的属性和方法
////////////////////////////////////////////

总结

Object.create(proto[, propertiesObject]),proto为函数的原型对象符合原型链的原则,所以尽量不要使proto为函数,要么为普通对象,要么为函数的原型对象,根据相应的需求去处理




__proto__和prototype

只有函数拥有prototype,而对象不拥有
对象拥有__proto__
在Javascript里面函数也是一个对象,所以函数既拥有__proto__也拥有prototype

原型链

每个实例对象(object )都有一个隐式原型属性(称之为__proto__ )指向了创建该对象的构造函数的原型。也就是指向了函数的 prototype 属性。


从定义上看,对象拥有属性和方法,而prototype也拥有属性和方法,因此prototype看作一个对象,只不过是依附在函数的一个属性上面.那么我们可以称fn.prototype为原型对象.


prototype拥有一个属性constructor,它是一个指针,指向函数fn本身


一般情况下(除开Object.create()的特殊用法,比如传递进的对象是一个函数而不是函数的原型对象),__proto__永远指向一个函数的原型对象

function Foo() { }
let foo = new Foo()
console.log(foo.__proto__ == Foo.prototype, Foo.prototype,
  Foo.prototype.__proto__ == Object.prototype, Foo.prototype.__proto__,
  Foo.prototype.__proto__.__proto__ == null, Foo.prototype.__proto__.__proto__);
// true Foo {} true {} true null
// 原型链:foo.__proto__.__proto__.proto__==null
console.log(Foo.__proto__ == Function.prototype, Foo.__proto__,
  Foo.__proto__.__proto__ == Object.prototype, Foo.__proto__.__proto__,
  Foo.__proto__.__proto__.__proto__ == null, Foo.__proto__.__proto__.__proto__);
    // true [Function] true {} true null
    // 原型链:Foo.__proto__.__proto__.__proto__==null

构造函数的实例是一个对象,因此符合对象拥有__proto__属性而没有prototype属性的原则


prototype是每个函数创建的时候自带的默认属性,只有函数拥有,且存在于原型链上,其他的类型没有.即使其它类型自己定义一个prototype属性,这个属性也不会存在原型链上,只是一个普通的属性而已.

但是我们只要将一个实例对象(obj)连接到我们自己定义的原型上面,比如obj=Object.create(Obj.prototype)等方法使obj.__proto__=Obj.prototype,那么obj也可以通过原型链(__proto__)访问Obj.prototype上面定义的属性
let Person = {
  name: 'avicii',
  type:{}
}
let obj=Object.create(Person.type)
Person.type.hobby='music'
console.log(obj,obj.hobby,Person);//{} music { name: 'avicii', type: { hobby: 'music' } }
// 原型名称可以为任意名字,只要将实例obj.__type__=Person.type,那么obj就可以按照原型链的规则(通过__proot__查找)访问Person.type上面定义的属性
总结

以上例子可以看出原型链的本质就是实例对象通过__proto__这个隐式原型属性向后面的原型对象查找,即使一个对象(OBJ)不在原型的链条上,只要另一个对象(obj)的obj.__proto__=OBJ,那么obj就能以默认的原型链方法:通过__proto__访问的OBJ上面的属性和方法,但是因为OBJ不在原型链上(像举例代码中:OBJ=Person.type,type只是Person的属性,Person.type.__proto__没有链到原型链上),所以不能够访问原型链条上的其他属性和方法,但如果OBJ链到原型链上,那么obj就可以正常访问这个原型链上的属性和方法比如:

// function Say() {
//   this.name = 'tim'
// }
// let Person = {
//   name: 'avicii',
//   type: {}
// }
// Person.type = Object.create(Say.prototype)
// Person.type.hobby = 'music'
// Say.prototype.face='cool'
// let obj = Object.create(Person.type)
// console.log(obj.hobby); //music
// 这里需要最后执行let obj = Object.create(Person.type),如果最开始执行这一句,那么obj.__proto__==Person.type实际上就是它们两者引用了在内存中的同一个地址
// 但是第二步执行的Person.type = Object.create(Say.prototype),会使Person.type引用一个空的Say.prototype对象在内存中的地址,Person.type引用内存中的地址发生了变化
// 这里直接是赋值不是引用, 所以obj.__proto__链接的Person.type在内存中的地址还是原来的地址,没有改变
//因此原型链其实是断开的,所以得出一个结论:原型链是从后面开始向前链接的,最前面的obj可以通过原型链访问后面的原型对象的属性和方法