理解javascript原型链的思路关键点

1,229 阅读11分钟

一,给一个对象增加原型的方式:

  • 通过new   (js中所有的东西都是可以new出来的,new出来的对象为了实现继承,才必须有一个隐式原型的指针属性__proto__)
  • 通过Object.create(原型)
  • 字面量的原型链(和new的一样)

下面我们分析这两种方式形成的原型链:

二,通过new一个构造函数->创建对象时形成的原型链:

下面一个构造函数的例子和new的实现,通过看new操作符的实现可以更好的理解原型链上的

__proto__和prototype

function Person(name,age){
 this.name = name;
 this.age= age;
 this.fn=()=>{ console.log('方法')}
}
Person.prototype={ 
    fn1:function(){console.log("fn1")}
}
var p=new Person('andy');//可以不加小括号,不传参的话
p.name//"andy"
p.fn()//"方法"复制代码@实现一个构造函数(入参是构造函数,与其他参数,返回一个对象)function _new(/*constr,params*/){//...arg更方便
   let args=[...arguments];
   let constructor=args.shift();
   var newObj={};
   newObj.__proto__=constructor.prototype;
  //let newObj = Object.create(constructor);//issue
    let result =  constructor.apply(newObj ,args);
    console.log("result",result,"newObj ",newObj )
    return typeof result === "object" ? result: newObj 
}
var p = _new(Person,'andy',22);//new总结四步:提取参数,第一个参数为构造函数,剩余的为其他参数(构造函数私有属性,方法);创建对象,并把其原型指向构造函数的原型;调用构造函数,并把this指向新创建的对象;返回此对象,如果构造函数返回一个对象,则返回此对象(不建议构造函数有返回值),否则返回新创建的对象;


以上代码我们只需要关注这一句代码即可:

 newObj.__proto__=constructor.prototype;

我们都知道new一个构造函数,返回的是一个对象; 
那么,在new在创建的这个对象,通过以上代码很直观的可以看出,
这个对象有一个__proto__的属性,指向了构造函数的prototype

得出结论,只有对象才有隐式原型,只有函数才有显示原型,下方会继续推导正确的结论。

@那么,我们在往下思考:在近一步,哈哈~


Number.prototype.__proto__===Object.__proto__;  //false
Number.prototype.__proto__===Object.prototype;  //true
Object.prototype.__proto__;   //null

数字的prototype对象也是一个对象,所以,他也有__proto__属性。

@继续思考: Number既是函数,又是对象。所以,他同时拥有两种原型属性。

Number.__proto__; //ƒ () { [native code] }
Number.__proto__===Function.prototype;  //true

Number.prototype;//Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}


@看一下constructor 

构造函数的prototype上都有一个construtor指向他自己

Number.prototype.constructor===Number  //true

发现一个很诡异的问题:

Object.__proto__.__proto__.__proto__;   //null

这是什么情况??~~


@包装对象1的原型链

new Number(1).__proto__.__proto__.__proto__===null  //true
(1).__proto__.__proto__.__proto__===null   //truenew Object({a:1}).__proto__.__proto__===null   //true


@图解原型链

  • JS中万物皆是对象,对象上都会有__proto__属性。
  • 而Number,Object属于构造函数,所以,都有prototype原型对象。此原型对象上,有属性__proto__和constructor属性等。(见下方图一)
  • 构造函数上不止有prototype对象,而且还有另外的一个__proto__属性。(见下方第二图)



@从构造函数上出发的原型链

  • Number构造函数上的__proto__,(并非Number.prototype原型对象的上__proto__)

Number.__proto__===Function.prototype; // true
Function.prototype.__proto__===Object.prototype;  // true
Object.prototype.__proto__===null;   // true


Number.__proto__.__proto__.__proto__ === null;  //true

  • Object构造函数上的__proto__, (并非Object.prototype原型对象的上__proto__)

Object.__proto__;  // ƒ () { [native code] }
Object.__proto__ ===Function.prototype; // true
Function.prototype.__proto__===Object.prototype;  // true
Object.prototype.__proto__===null;   // true
Object.__proto__.__proto__.__proto__===null;  // true

  • Function构造函数上的__proto__,(并非Function.prototype原型对象的上__proto__)

此链比较特别。Function上的__proto__指向了自己的prototype原型对象。

Function.__proto__;  // ƒ () { [native code] }
Function.__proto__===Function.prototype;   // trueFunction.prototype.__proto__ === Object.prototype;   // trueObject.prototype.__proto__ === null;   // true
Function.__proto__.__proto__.__proto__===null;  // true


  • Number原型对象上__proto__属性出发的原型链条

Number.prototype.__proto__===Object.prototype;   // true
Object.prototype.__proto__ ===null;   // true
    


  • Object构造函数上的__proto__属性,指向Function.prototype,(证明Object是函数的本质。)(Object有__proto__属性,因为只有对象才有__proto__属性,证明了Object是对象。)=》说明了所有构造函数既有prototype,又有__proto__。近一步证明了,JS中万物皆是对象。{数字1,字符串,布尔值(是包裹对象)、正则表达式、Function、Number、Object都是对象}。
  • Object

Function.prototype原型对象上的__proto__又指向Object.prototype


Object.__proto__===Function.prototype;   // true
Function.prototype.__proto__===Object.prototype;   // true
Object.prototype.__proto__ ===null;   // true


只有函数有prototype,只有这种对象不是函数,固没有prototype。

new Number(1).prototype; //undefined

JS中一切皆是对象。都有隐式原型__protot__


new Number(1).__proto__; //Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
(1).__proto__; //Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}

Number.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }

Array.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
RegExp.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
String.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
Object.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }

Symbol.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }
Date.__proto__; // ƒ () { [native code] }; //ƒ () { [native code] }

undefined.__proto__; //VM3065:1 Uncaught TypeError: Cannot read property '__proto__' of undefined
null.__proto__;//VM3065:1 Uncaught TypeError: Cannot read property '__proto__' of undefined

三、通过Object.create(原型对象),创建的原型链

Object.create(proto [,propertiesObject]) 功能:创建一个对象,其原型为prototype,同时可添加多个属性。 

参数: 

  • proto(必须):原型对象,可以为null表示没有原型。 
  • descriptors(可选):包含一个或多个属性描述符的对象。 

propertiesObject参数详解: 

 数据属性 

  •  value:值 
  • writable:是否可修改属性的值 
  • configurable:是否可通过
  • delete:删除属性
  • enumerable:是否可for-in枚举 
  • 访问属性 get():访问 set():设置 

示例:

function Person(name){
    this.name = name;
  }
  Person.prototype.say = function(){console.log('my name is ' + this.name +',my age is ' + this.age);}

  var person = new Person('andy');
  var p = Object.create(person,{
    age:{
      value: 23,
      writable: true,
      configurable: true
    },
    sex:{
      configurable: true,
      get:function(){return sex + '士';},
      set:function(value){sex = value;}
    }
  });
  
  p.sex = '男';
  p.say(); //'my name is andy,my age is 23'
  console.log(p.sex); //'男士'
  p.sex = '女';
  console.log(p.sex);//女士

@Object.create的原型链

p.__proto__.__proto__.__proto__.__proto__;//null

@Object.create的原型链2

var a1={}
a1.prototype; //undefined
a2.prototype; //undefined
var a2=Object.create(a1)
a2.__proto__===a1 ;   // true
a1.__proto__===Object.prototype ;   // true
a2.__proto__.__proto__.__proto__===null;   // true




@注意:

a1 没有原型对象prototype; a2指向的原型对象就是a1本身!

@Object.create 的基本实现原理

入参为原型对象,出参为一个new之后的构造函数。此构造函数的原型指向入参Obj.

function create(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}

内部实现就是通过创建一个构造函数,然后new一个实例出来,此构造函数的原型指向我们指定的原型。最终返回一个对象。指定原型的对象创建完成。

四、原型链使用注意事项:

  • 不要太长的原型链,影响查询效率!
  • 构造函数上不仅仅有prototype原型对象(此原型对象上有__proto__),此构造函数也有自己的__proto__属性。可以理解为一个构造函数上有两个__proto__。但是,一个在他的原型对象上,一个在他自己身上。
  • 只有函数有prototype,(有一些对象是函数(new出来的对象就一定只是对象),但是所有的函数都是对象)
  • JS中一切皆是对象。都有隐式原型__proto__
  • (1)、[] 、{} 、 true、“abc” 、Object 、 Function 、Array、String都有__proto__,试想如果没有这个隐式原型的指针,就没有办法实现继承。


五、疑问推导:

Object的隐式原型指向的是Function原型对象。只有这个指向是逆向的回指,先逆向指向Function,然后在顺指回来。然后在指向null

Object.__proto__.__proto__.__proto__;   //null

Object.__proto__ ===Function.prototype;   //true
//Function.__proto__ === Function.prototype;   //trueFunction.prototype.__proto__===Object.prototype;   //true
Object.prototype.__proto__===null;   //true



六、instanceof原理与实现

说原型链,就不得不说一下instansof,那么,先看一下instansof是如何实现的

instanceof的作用是用来做检测类型

instanceof 检测一个对象A是不是另一个对象B的实例的原理:

查看对象B的prototype指向的对象是否在对象A的[[prototype]]链上。如果在,则返回true,如果不在则返回false。不过有一个特殊的情况,当对象B的prototype为null将会报错(类似于空指针异常)。

函数模拟A instanceof B

function instans_of(obj,func){
	var obj = obj.__proto__;//取对象obj的隐式原型
	var func = func.prototype;//取构造函数的显示原型
    while(true){
	if(obj===null){return false};
        if(obj===func){return true};//这里重点:当 obj  严格等于 func  时,返回 true	
       obj=obj.__proto__;//
   }

}

规则简单来说就是 obj的 __proto__ 是不是强等于 func.prototype,不等于再找 obj.__proto__ .__proto__ 直到 __proto__ 为 null  


@instanceof对整个原型链上的对象都有效,因此同一个实例对象,可能会对多个构造函数都返回true

七、类型判断方法总结

  • typeof
判断基本类型的,比如:Number, Boolean, Undefined, String, Null, 其他的引用类型和Null会返回object
  • instanceof
检测一个对象A是不是另一个对象B的实例
  • Object.prototype.toString


Object.prototype.toString.call('');//"[object String]"

Object.prototype.toString.call(Symbol);//"[object Function]"

Object.prototype.toString.call(Symbol());//"[object Symbol]"Object.prototype.toString.call([]);//"[object Array]"Object.prototype.toString.call(RegExp());//"[object RegExp]"Object.prototype.toString.call({});//"[object Object]"Object.prototype.toString.call(Symbol());//"[object Function]"

七、学生的原型链

function Person(name, age){
    this.name = name;
    this.age = age;
}

function Student(score){
    this.score = score;
}

Student.prototype = new Person('李明',22);
var s = new Student(99);

console.log(s instanceof Student);  //true
console.log(s instanceof Person);  //true
console.log(s instanceof Object);  //true

s.__proto__ === Student.prototype;   // true
Student.prototype; //Person new之后返回的对象{name: "李明", age: 22}
Student.prototype.__proto__===Person.prototype;   // true
Person.prototype.__proto__===Object.prototype;   // true
Object.prototype;//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
Object.prototype.__proto__===null; //   true

s.__proto__.__proto__.__proto__.__proto__===null; //   true

//Object的隐式原型与Function原型对象上的隐式原型互相指向对方的原型对象。(互指向)
Object.__proto__===Function.prototype;   // true
Function.prototype.__proto__===Object.prototype;   // true



@小例子总结:

  • Object的隐式原型与Function原型对象上的隐式原型互相指向对方的原型对象。互指向
  • s只有__proto__没有prototype。其__proto__指向Student的prototype原型对象。
  • Student.prototype上的__proto__又指向Person的prototype的原型对象。
  • Person.prototype原型对象上只有__proto__属性和constructor属性
  • __proto__是属性,prototype是对象,除了JS自己的构造函数默认有prototype对象上的很多方法属性外,其他自己创建的构造函数,默认prototype对象上只有两个属性(__proto__和constructor),如果需要,需要自己去增加原型对象上的属性和方法。
  • Student.prototype = new Person('李明',22); 这行代码的作用实现了Student.prototype 原型对象上的__proto__指向了Person的prototype。所以student是有prototype对象的。并且二者都为{name: "李明", age: 22}这个对象。                                                   也就是new Person的时候 this指向了Student.prototype
  • Student.prototype.name可以 取到对象中的值。Person构造函数中this下的属性,就是给new出来的实例用的,并非是给自己用的。他自己也用不到。
  • 还注意一点:Student原型对象上的constructor指向的是他的构造函数Person,Person原型对象上的constructor指向的是他自己Person,

Person.prototype.constructor===Person; //   true
Student.prototype.constructor===Person; //   trueObject.prototype.constructor===Object; //   true

所有构造函数原型对象中都有一个constructor指向的是构造函数自己。因为他没有自己的构造函数。

八、constructor 我是谁的实例对象

function F(){};
var f = new F();
f.__proto__.constructor===F; //   true
F.__proto__.constructor===Function; //   true
F.prototype.constructor===F; //   true
Function.__proto__.constructor===Function; //   true

Number.prototype.constructor===Number; //   true
Number.__proto__.constructor===Function; //   true
Object.prototype.constructor===Object; //   true

__________________________________________________________________

@看图加深印象

var a=new Number(1);
a.prototype; // undefined
a.__proto__.constructor===Number; //   true

Number.__proto__.constructor===Function; //   true
Number.prototype.constructor===Number; //   trueFunction.prototype.constructor===Function; //   true
Function.__proto__.constructor===Function; //   true
//都是对象
typeof a.__proto__; //"object"
typeof  Number.prototype; //"object"//默认优先使用__proto__属性对象上的constructor
a.constructor===Number; //   true
Number.constructor===Function; //   true//
a.constructor===Function; // false


@constructor总结:

  • a.constructor===Number; 或者a.__proto__.constructor===Number;默认使用隐式原型上的constranctor。
  • 不建议使用prototype下的constranctor。
  • 除了(1)这种对象外,其他的构造函数(也就是其他的所有对象)都有两个constructor。
  • 我们只需要记住直接使用constanctor即可。
  • 关键:我是谁构造出来的实例对象。


九、字面量的原型链(字面量其实也是new出来的,原型链无异)

__proto__永远指向prototype
prototype下的__proto__永远指向prototype。除了null

var o={};
o.__proto__===Object.prototype;
Object.prototype.__proto__===null
({}).__proto__.__proto__===null


var arr=[1]
arr.__proto__===Array.prototype
Array.prototype.__proto__===Object.prototype
Object.prototype.__proto__===null
[1].__proto__.__proto__.__proto__===null



十、总结

  • __proto__和prototype都是对象。
  • 只有函数有prototype。函数同时也有两个__proto__.一个在函数本身上,一个在他的原型对象上。
  • 只有对象有__proto__。回想new的实现中 obj.__proto__=courstranctor.prototype.
  • __proto__永远指向prototype
  • prototype下的__proto__永远指向prototype。除了null
  • JS中万物皆是对象,对象上都会有__proto__属性。
  • 只有函数有prototype。(1),“ab”,[],{}等实例是没有prototype.因为他们是数据对象(实例对象),不是函数。
  • 函数也有__proto__,而且有两个




————————————————————————————————————————

@待补充与完善:

  • isPrototypeOf() 方法允许你检查一个对象是否存在于另一个对象的原型链上。
  • __proto__属性,用来读取或设置当前对象的prototype对象
  • Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)

@__proto__属性,多角度参考思考

@它本质上是一个内部属性,而不是一个正式的对外的 API ,只是由于浏览器广泛支持,才被加入了 ES6 。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替

@__proto__

主要用来回溯对象,即用来继承的,
即当执行a.b的时候,如果当前对象没有定义b属性,则会通过a.__proto__到父对象中查找,以此类推

@__proto__的实现 (从网上粘贴了一段如下)未考证

Object.defineProperty(Object.prototype, '__proto__', {
    get() {
       let _thisObj = Object (this);
       return Object.getPrototypeOf(_thisObj);
   },
   set(proto) {
       if (this === undefined || this === null) {
           throw new TypeError();
       }
       if (!isObject(this)) {
           return undefined;
       }
       if (!isObject(proto)) {
           return undefined;
       }
       let status = Reflect.setPrototypeOf(this, proto);
       if(!status) {
           throw new TypeError();
       }
   },
});
function isObject(value) {
    return Object(value) === value;
}