js原型链,一步一步找祖宗

321 阅读5分钟

原型链深度

前言

我经常搞混一个概念,在读文章前一定要记得这句话:并不是所以对象都有prototype,通常只有函数有prototype对象,但是每个对象都有一个__proto__。 原型链的查找过程始终都是沿着__proto__向上找的。构造函数的原型链之所以会去prototype上找是因为被构造函数new出来的对象的__proto__指向的是构造函数的prototype,(换个说法就是被构造函数new出来的对象的__proto__=构造函数的prototype。叫指向或者等于我觉得都可以)

一 如何指定原型链

1、普通构造函数

准备一个对象和函数,如下,然后手动的将构造函数的prototype指定对象。

var obj = {b:function(){}}

function Foo(){
 	this.name = '蓉蓉'
}
// 默认情况Foo.prototype上有一个
//constuctor:Foo.prototype = { constuctor: Foo }

// 正常情况添加原型的方法:Foo.prototype.a = function(){}
// 手动设置原型对象:Foo.prototype = obj

var a = new Foo()

// a =  {
// name:'蓉蓉',
// __proto__: Foo.prototype //{b:function(){}}
}

这时当别人new Foo时,就会构造出一个新的对象(例:a),a里面会默认有个proto的属性指向Foo.prototype,
(补充:Foo.prototype默认是一个对象,并且默认的对象里有个constuctor属性,constuctor的值指向该原型对象对应的构造函数 (例:Foo(){}),但是上面例子中,由于我们手动的将Foo.prototype指向了另一个新对象,新对象里也并没有给出constuctor的属性,((注:当然可以选择给这个新对象的增加一个constuctor属性并且赋值))这时就会出现构造出来的a在访问constuctor时到原型链(Foo.prototype)上找,但是因为Foo.prototype上没有constuctor这个属性,那么就会继续向更深的原型链上去找,直到找到Object为止。

补充知识点:所有的构造函数最后都会隐形的将this return出去,我们平时没有写只是js默认帮我们干了return这件事,按照这个推理我们可以人工的干预return出去的对象,举个栗子:

function Foo(){
 	this.name = '蓉蓉'
return { name: '我是浏览'}
}
var a = new Foo()
// a = {
name:  '我是浏览'
__proto__:Foo.prototype // {}
}

2、使用Object.create()指定原型

var obj = {bb:1}
var a = Object.create(obj)

//a = {
//  __proto__:{ bb :1}
//}

a.rr = 1;
var b  = Object.create(a)
// b = {
// __proto__:{
//              rr:1,
//              __proto__:{bb:1
//            }
//    }
//}
b.constuctor  // Object(){}

由于没有指定constuctor,所以是由Object构造函数构造
上面这种写法是最简单的持续继承的方式,祖宗十八代的原型链,简洁易懂,没啥子好说的

3、es6的extends指定原型

class A{
    static getname(){ }   
    rr(){}
}

class B extends A{

}

//转换成es5
 function A(){
}
A.prototype ={
constructor:A,
rr:function(){}
}

function B(){

}
B.__proto__ = A
B.prototype = new A()

// 运行结果
B.prototype ={
    __proto__:{
    constructor:function A(){},
    rr::function(){}
    }
}
B.prototype.constructor = B

需要补充的知识点B.prototype = new A()这句话可以看出B.prototype 被赋值给了一个新的对象,并且这个新对象里没有constructor这个这个属性,只有原型proot指针上有一个constructor是指向A的,B.prototype.constructor = B这句话,为了保证被B类new出来的对象原型链能正常找B祖宗,所以需要指定B.prototype.constructor =B;但是在extends中会自动将这种关系指定好,另外注意到有这么一句话:
B.proto= A,为什么会写这个呢,是class中定义了一个静态方法,getname,这个方法是直接挂在A上的,正常的new的时候实例是无法继承的,但是类有的方法,继承的类也是可以访问的,我一直很好奇静态方法继承的类是如何继承的,(只是访问父级类,并不会给继承的类也生成一份)这个地方曾让我一度绕晕,__proto__不是通常都指的是一个prototype,是一个对象吗,怎么直接指的是一个类了?

function B()

// 默认B的prototype是这样的
B.prototype={
    constructor : B,
    __proto__: Object.prototype
}
但是其实B还有

const cat1 = new B()
// cat1等于是
cat1={
 __proto:B.prototype
}
从这里可以看出__proto__指向的是说是谁把你new出来的那个的prototype,所以当你找不到方法时可以顺着
原型一直向原型上找,但是其实function B(),B本身其实也是有__proto__这个属性的,可怕的是我之
前一直以为没有,因为B是被new Function弄出来的,所以这样的话就是:

B._proto__ = Function 
所以B如果直接写B.xx以点啥访问一个属性时访问不到会去Function这个类里找,注意哦,这个B是一个类了,不
是实例,我老是会混掉,那上面的例子种B是extends继承A的,所以关系是:

B_proto__ = A
A__proto__ = Function

4、es6类的基础知识

class A{
    coco = ()=>{}
    

    bar(){}

    

   	 render(){
 	    return <div onClick={this.bar.bind(this)}></div>
     }
}

coco:

coco是箭头函数默认绑定外部环境的this,所以在类里面这么写等于于是写在构造函数的this上的,每次在new这个类的时候都会生成一个coco函数

bar:

bar函数在类里面这么写函数等于是写在构造函数的prototype上的,当然new的时候不会重新生成一个bar,但是因为没有为其绑定this,所以通常在写react的时父组件向子组件传递绑定事件时需要手动绑定this指明执行时this指向父级,而不是执行环境时的this

二 关于instanceof

我总是会忘记掉instanceof是干嘛的,希望能记在这个地方,加深我的记忆。instanceof与typeof类似,都是用来判断一个变量是什么类型。但是typeof通常就返回固定的几种类型:number,boolean,string,function,object,undefined
在判断对象的时候比较局限。而instanceof还可以返回一个变量是否某个对象的实例,比如:

var a=new Array();
a instanceof Array // 会返回 true,
// 同时 
a instanceof Object // 也会返回 true;
//这是因为 Array 是 object 的子类。再如:
function test(){};
var a=new test();
a instanceof test //会返回 true

重点:
总结instanceof还可以判断一个变量是否某个对象的实例

该文章为个人学习笔记,有很多不严谨的地方