详解js原型链

先看下方面试题,思考一下结果.

    Object.prototype.test1 = function () {
      console.log('test1');
    };
    
    Function.prototype.test2 = function () {
      console.log('test2');
    };

    function Fun() {
      this.a = 1;
    }

    var obj = new Fun();

    console.log(obj.test1);
    console.log(obj.test2);




答案:
在这里插入图片描述


obj.test1输出了一个函数,而obj.test2输出undefined.

为了解答这道题,需要深入理解javascript的原型链.



知识点

什么是js原型链

在学习原型链之前,先了解三个基本概念:构造函数,实例对象,原型对象

构造函数

构造函数也是一个函数,只不过它比普通函数多一个功能,使用new关键字调用时能创建对象.

 function student(){
    this.name = "张三"; 
 }
 
 var stu1 = new student(); //创建对象
	
 console.log(stu1.name); //输出张三

上面定义的student函数就是一个构造函数,通过使用new关键字生成一个新对象

实例对象

实例对象是使用new关键字调用构造函数后返回的新对象.对应到上面代码中的stu1

原型对象

原型对象也是一个对象,它是构造函数的prototype指向的一个对象.

 function student(){
    this.name = "张三"; 
 }

 student.prototype.run = function(){console.log("奔跑");}
 
 var stu1 = new student(); //创建对象
	
 stu1.run()  //打印'奔跑'

student.prototype就是原型对象,原型对象上面添加属性和方法后,实例对象就可以调用原型对象上面的属性和方法.


值得注意,Object,Function,Array,String,Number等都属于构造函数.通过使用new关键字可以创建相应的实例对象,另外在构造函数的prototype上面添加属性和方法,相应的实例对象就能调用.

实例对象可以访问原型对象上的属性和方法,而原型对象可能又继承于另外一个原型对象.这样第一层的实例对象可以访问第二层原型对象的属性和方法,也可以访问第三层原型对象的属性和方法.这样形成的链条就是原型链.下面通过图示来深入理解原型链的结构.



原型链的架构

在这里插入图片描述


上图中矩形是构造函数,椭圆是原型对象,带圆角的矩形是实例对象.

以上图中变量array为例,array继承了 Array的原型对象 , Array的原型对象 又继承了 Object原型对象 .此时array, Array的原型对象 和 Object的原型对象 构成了一条原型链.

原型链上的高层对象定义的属性和方法,低层对象都能调用和访问.

Object.prototype的地位

原型链的最高层是Object.prototype.所有对象都继承Object.prototype,倘若在Object.prototype上定义属性和方法,所有对象(null除外)都能继承使用.

  Object.prototype.test1 = 123;
  var obj = {};
  console.log(obj.test1); //输出123
  
  obj.test1 = 456;
  
  console.log(obj.test1); //输出456,倘若子对象定义了与原型链上的对象相同的属性和方法时,子对象会覆盖
  console.log(typeof null) // 输出 "object"
  console.log(null.test1) //报错,null虽然也是对象,但是它不能调用任何属性和方法



构造函数和原型对象

Object,Function,Array,String,RegExp都是构造函数,通过.prototype的方式可以访问上图右侧的原型对象.

将上述原型对象打印输出,可以看到原型对象和普通对象没有区别,只不过本身定义了很多方法.

在这里插入图片描述


对象的继承

不管是实例对象还是原型对象都可以使用__proto__获取父级的原型对象.

var array = new Array();

array.__proto__ === Array.prototype; // true,通过__proto__属性可以获取父级的原型对象

Array.prototype.__proto__ === Object.prototype //true

//Object.prototype是所有对象的最高层,它再往一层就为null了
console.log(Object.prototype.__proto__) //输出 null

array是实例对象,它可以使用Array原型对象上所有属性和方法,也可以使用Object原型对象的属性和方法.
 Object.prototype.test1 = 123;
 
 Array.prototype.test2 = 456;

 var array = new Array(); 
 
 console.log(array.test1); //输出123
 console.log(array.test2); //输出456



普通定义的对象和new出对象有何区别

很多时候定义对象喜欢直接使用var obj = {} ,var array = [], var fun = function(){},它们和new出来的对象有何区别.

    var obj1 = {};
    var obj2 = new Object();
    
    obj1.__proto__ === obj2.__proto__ // 打印 true,都是指向了Object.prototype 
    
    var array1 = [];
    var array2 = new Array();
    
    array1.__proto__  === array2.__proto__ // 打印 true,都是指向了Array.prototype

从上面看出,使用var定义和new出来的对象没有区别,但有时使用new创建对象功能会更强大

  var fun1 = function(){
      console.log(123);
  }

 var code = "console.log(456)";
 
 var fun2 = new Function(code);
 
 fun2(); // 输出456

如果执行的代码是一段字符串,当使用构造函数Function创建函数对象时,就能动态改变函数体,达到了类似eval的效果.


Function和Object

从上面的叙述可知,Object.prototype是所有对象的祖先.Function.prototype的父级就是Object.prototype

  Function.prototype.__proto__ === Object.prototype //输出 true

Function的原型对象和Object的原型对象关系理清楚了,接下里看构造函数Object和Function

Object是一个构造函数,同时它也是一个函数(函数本身就是个对象).只要是函数就会继承Function.prototype的属性和方法.

Function.prototype.test = "测试";
console.log(Object.test); //输出 "测试"

在 Function.prototype 上定义属性和方法,那么所有的函数都能继承调用.

Object和Object.prototype

Object.prototype.test = "测试";
console.log(Object.test); //输出 "测试"

在Object.prototype上面定义任何属性和方法,不光是Object, Function,Array,RegExp等都能继承调用.

以Object为例,Object是一个构造函数,同时也是一个函数对象.Object继承于Function.prototype,而Function.prototype又继承于Object.prototype.由此Object,Function.prototype和Object.prototype构成了一条原型链.

 Object.__proto__.__proto__ === Object.prototype; //输出 true

总结

  • 在分析原型链相关题目时,首先要根据上下文判端变量是作为构造函数还是函数对象而言,比如 Function 既可以作构造函数,也可以当做函数对象.当作为构造函数时才可以调用.prototype获取原型对象,当作为实例对象或原型对象时才可以调用__protp__寻找父级的原型对象.

    Function.prototype === Function.__proto__ // 输出true
    

    Function作为构造函数而言调用prototype 获取到了所有函数对象的父级.而Function作为普通函数而言,通过__proto__也能得到所有函数对象的父级.因此它们两个相等.

  • 只有在一条原型链上的对象才构成继承关系.比如 Array.prototype.test = “测试”;var obj = {}; 此时obj就不能获取test属性.



原型链的应用


对象属性获取

 var obj1 = {
   a:1
 };
  var obj2 = {
   b:2
 };
  var obj3 = {
   c:3
 }
 obj1.__proto__ = obj2;
 obj2.__proto__ = obj3;

console.log(obj1.c); //输出3

创建3个对象,通过改变__proto__使obj1,obj2和obj3以及 Object.prototype 形成了一条原型链.

当obj1获取c属性值时,会先从obj1中寻找,没找到跳到上一层obj2,仍然没找到继续跳上一层,最终在obj3中找到了c输出.

for in循环

  var obj1 ={
    a:1
  }
  var obj2 = {
    b:2
  };
  obj1.__proto__ = obj2;
 
 for(var key in obj1){
   console.log(key); //输出 a b
 }

虽然代码只对obj1做循环,但是for in循环的机制会将整条原型链上所有可枚举的属性和方法全部获取到.

有没有办法只找出对象自身定义的属性和方法,放弃原型链上的定义呢?(可以通过hasOwnProperty做到)

  var obj1 ={
     a:1
  }
  var obj2 = {
     b:2
  };
  obj1.__proto__ = obj2;
 
 for(var key in obj1){
   if(obj1.hasOwnProperty(key)){
	 console.log(key); //只输出a
  }
}



解题

    Object.prototype.test1 = function () {
      console.log('test1');
    };
    
    Function.prototype.test2 = function () {
      console.log('test2');
    };

    function Fun() {
      this.a = 1;
    }

    var obj = new Fun();

    console.log(obj.test1);
    console.log(obj.test2);

现在再来看最初的这道面试题.obj是一个普通对象,它的父级是Fun.prototype.

obj,Fun.prototype和Object.prototype构成了一条原型链,所以test1能获取,test2为undefined.


Function.prototype上定义了test2方法,可以让所有的函数对象都能获取test2,但普通对象不行.如下:

console.log(Fun.test2 ); //输出 test2函数

猜你喜欢

转载自blog.csdn.net/brokenkay/article/details/109724464