zoukankan      html  css  js  c++  java
  • 聊一聊JS的原型链之高级篇

    首先呢JS的继承实现是借助原型链,原型链即__proto__形成的链条。

    下面一个例子初步认识下原型链:

    function Animal (){
    
    }
    var cat = new Animal()
    			
    我们创建了一个Animal这个构造函数,然后实例化出一个对象cat,当我们输出这个实例化对象cat的时候,这个实例化对象里面有一个__proto__属性,这个__proto__属性指向的是创建自己的那个构造函数Animal的原型prototype,
    也就是说这个实例化对象cat的__proto__指向的是Animal里面的prototype这个原型对象,因为prototype是一个对象因此里面肯定也会有一个__proto__。而这个__proto__指向的是创建Animal这个构造函数的对象,可以想象一下谁创建了Animal? 肯定是一个对象创建了Animal。而这个对象就是 Function。有对象那么肯定就会有__proto__.那么我们可以想象一下还有什么可以创建出来对象吗?这个时候我们就不得不说一句"万物皆是对象".因此对象的顶端也就是null。

    那么__proto__与prototype有什么区别呢?

    prototype属性也叫原型对象, prototype只有函数才有的属性, _proto_是所有对象都有的属性(null和undefined除外),而且指向创造该obj对象的函数对象的prototype属性,但是_proto_不是标准的属性,只有部分浏览器实现了,对应的标准的属性是[[Prototype]],大多数情况下,大多数情况下,__proto__可以理解为'构造器的原型',即Animal.__proto__===Animal.constructor.prototype(通过Object.create创建对象不适用此对象);

    下面看一下各种情况下_proto_的指向;

    一、Object.create()

    var p={};

    var q=Object.create(p)
    q.__proto__===q.constructor.prototype//false
    q.__proto__===p;//true

    二、字面量

    var a={}

    a.__proto__===Object.prototype//true

    三、构造器(proto指向构造器的prototype)

    function Animal(){

    }

    var cat = new Animal()

    cat.__proto__===Animal.prototype//true

    var obj = {name: 'jack'}
    var arr = [1,2,3]
    var reg = /hello/g
    var date = new Date
    var err = new Error('exception')
     
    console.log(obj.__proto__ === Object.prototype) // true
    console.log(arr.__proto__ === Array.prototype)  // true
    console.log(reg.__proto__ === RegExp.prototype) // true
    console.log(date.__proto__ === Date.prototype)  // true
    console.log(err.__proto__ === Error.prototype)  // true
    
    

      

     

    1. cat.__proto__ 是什么?
    2. Animal.__proto__ 是什么?
    3. Animal.prototype.__proto__ 是什么?
    4. Object.__proto__ 是什么?
    5. Object.prototype__proto__ 是什么?

    1、cat.__proto__===Animal.prototype;

    2、Animal.__proto__===Function.prototype//Animal是函数对象

    3、Animal.prototype.__proto__===Object.prototype//Animal.prototype是普通对象

    4、Object.__proto__===Function.prototype//Object是函数对象

    5、Object.prototype.__proto__===null

     四、函数对象

    所有的函数对象的__proto__都指向Function.prototype,它是一个空函数(empty function)

    // 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
    
    Number.__proto__ === Function.prototype  // true
    Number.constructor == Function //true
    
    Boolean.__proto__ === Function.prototype // true
    Boolean.constructor == Function //true
    
    String.__proto__ === Function.prototype  // true
    String.constructor == Function //true
    
    Object.__proto__ === Function.prototype  // true
    Object.constructor == Function // true
    
    // 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
    Function.__proto__ === Function.prototype // true
    Function.constructor == Function //true
    
    Array.__proto__ === Function.prototype   // true
    Array.constructor == Function //true
    
    RegExp.__proto__ === Function.prototype  // true
    RegExp.constructor == Function //true
    
    Error.__proto__ === Function.prototype   // true
    Error.constructor == Function //true
    
    Date.__proto__ === Function.prototype    // true
    Date.constructor == Function //true
    

      

    JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的proto是Object.prototype。如下
     
    Math.__proto__ === Object.prototype  // true
    Math.construrctor == Object // true
    
    JSON.__proto__ === Object.prototype  // true
    JSON.construrctor == Object //true

    再看看自定义的构造器,这里定义了一个 Person

    function Person(name) {
      this.name = name;
    }
    var p = new Person('jack')
    console.log(p.__proto__ === Person.prototype) // true
    

    pPerson 的实例对象,p 的内部原型总是指向其构造器 Person 的原型对象 prototype

    所以所有的构造器都来自于 Function.prototype,甚至包括根构造器ObjectFunction自身。所有构造器都继承了·Function.prototype·的属性及方法。如length、call、apply、bind

    Function.prototype也是唯一一个typeof Funtion.prototype为 functionprototype。其它的构造器的prototype都是一个对象

     知道了所有构造器(含内置及自定义)的__proto__都是Function.prototype,那Function.prototype__proto__是谁呢?

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

      这说明所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等

     最后Object.prototype的proto指向null,到顶了。

     什么是Prototype

    我们看一下熟知的函数的原型对象

     Function.prototype;//function() {}
        Object.prototype;//Object {}
        Number.prototype;//Number {[[PrimitiveValue]]: 0}
        Boolean.prototype;//Boolean {[[PrimitiveValue]]: false}
        Array.prototype;//[]
        String.prototype;//String {length: 0, [[PrimitiveValue]]: ""}
    

     说道这里,必须提的是所有函数对象的原型对象都继承制原始对象,即fn.prototype.__proto__为原始对象(原始对象在继承属性__proto__中有定义)。这其中比较特别的是Object函数,他的原型对象就是原始对象,即Object.prototype。

     

        var f1 = new Function();
        var f2 = Function();
        var fn3 = function(){}
    
        console.log(f1.prototype.__proto__ === Object.prototype);//true
        console.log(f2.prototype.__proto__ === Object.prototype);//true
        console.log(fn3.prototype.__proto__ === Object.prototype);//true
    
        console.log(Number.prototype.__proto__ === Object.prototype);//true
        console.log(Boolean.prototype.__proto__ === Object.prototype);//true

     实际上js没有继承这个东东,但是__proto__却起到了类似继承的作用。我们所知的所有的对象起源都是一个空对象,我们把这个空对象叫做原始对象。所有的对象通过__proto__回溯最终都会指向(所谓的指向类似C中的指针,这个原始对象是唯一的,整个内存中只会存在一个原始对象)这个原始对象。用下面的例子佐证

    var o = new Object();
        o.__proto__;//Object {}
        o.prototype;//undefined
        Object.prototype;//Object {}
        Object.__proto__;//function(){}
        Object.__proto__.__proto__;//Object {}
        Object.__proto.prototype;//undefined
        var f = new Function();
        f.__proto__;//function(){}
        f.prototype;//Object {}  新的实例对象非原始对象
        Function.prototype;//function(){}
        Function.__proto__;//function(){}
        Function.__proto__.__proto__;//Object {}
       Function.prototype.__proto__;
    //Object {}
       Function.prototype.__proto__.__proto__//null

    原始对象的__proto__属性为null,并且没有原型对象。

    所有的对象都继承自原始对象;Object比较特殊,他的原型对象也就是原始对象;所以我们往往用Object.prototype表示原始对象

       //所有的对象都继承自原始对象
        //Object比较特殊,他的原型对象也就是原始对象
        //所以我们往往用Object.prototype表示原始对象
        Object.prototype === o.__proto__;//true
        Object.prototype === Object.__proto__.__proto__;//true
        Object.prototype === Function.__proto__.__proto__;//true
    

     所有的函数对象都继承制原始函数对象;Function比较特殊,他的原型对象也就是原始函数对象;所以我们往往用Function.prototype表示原始函数对象;

       而原始函数对象又继承自原始对象。

    //所有的函数对象都继承制原始函数对象,
        //Function比较特殊,他的原型对象也就是原始函数对象
        Function.prototype === f.__proto__
        Function.prototype === Object.__proto__ ;//true
        Function.prototype === Function.__proto__;//true
        //所以我们往往用Function.prototype表示原始函数对象
    
        //而原始函数对象又继承自原始对象
        Function.prototype.__proto__ === Object.prototype; 
    在 ECMAScript 核心所定义的全部属性中,最耐人寻味的就要数 prototype 属性了。对于 ECMAScript 中的引用类型而言,prototype 是保存着它们所有实例方法的真正所在。换句话所说,诸如 toString()valuseOf() 等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访问罢了。  ——《JavaScript 高级程序设计》第三版 P116

    当我们创建一个数组时:

    var num = new Array()
    num 是 Array 的实例,所以 num 继承了Array 的原型对象Array.prototype上所有的方法:

    我们可以用一个 ES5 提供的新方法:Object.getOwnPropertyNames
    获取所有(包括不可枚举的属性)的属性名不包括 prototy 中的属性,返回一个数组:
     
    Object.getOwnPropertyNames(Array.prototype)
    ["length", "constructor", "toString", "toLocaleString", "join", "pop", "push", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach", "some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight", "copyWithin", "find", "findIndex", "fill", "includes", "keys", "entries", "concat"]
    

      细心的你肯定发现了Object.getOwnPropertyNames(Array.property) 输出的数组里并没有 constructor/hasOwnPrototype等对象的方法。但是随便定义的数组也能用这些方法

    因为Array.prototype 虽然没这些方法,但是它有原型对象(__proto__):

    Array.prototype.__proto__ == Object.prototype//Array.prototype是普通对象
    

      

    所以 Array.prototype 继承了对象的所有方法,当你用num.hasOwnPrototype()时,JS 会先查一下它的构造函数 (Array) 的原型对象 Array.prototype 有没有有hasOwnPrototype()方法,没查到的话继续查一下 Array.prototype 的原型对象 Array.prototype.__proto__有没有这个方法。
    当我们创建函数时:
    Object.getOwnPropertyNames(Function.prototype)
    ["length", "name", "arguments", "caller", "apply", "bind", "call", "toString", "constructor"]
    

     这些属性和方法所有的函数对象都可以用。

    重写一个函数原型对象
    function Animal(){
    	
    }
    var cat = new Animal()
    
    cat.__proto__===Animal.prototype;//true
    
    cat.constructor.prototype===Animal.prototype//true
    cat.__proto__===cat.constructor.prototype;//true

    如果改写

    function Animal(){
    	
    }
    
    Animal.prototype={
        getName:function(){}
    }
    var cat = new Animal();
    cat.__proto__===Animal.prototype;//true
    cat.constructor.prototype===Animal.prototype//false cat.__proto__===cat.constructor.prototype;//false

      这样重写了Animal的prototype属性,cat.constructor.prototype不在等于Animal.prototype,

    这也很好理解,给Animal.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器ObjectObject.prototype是一个空对象{}{}自然与{getName: function(){}}不等。如下:
    cat.constructor===Object;
    
    cat.constructor.prototype===Object.prototype;
    

      

    疑惑点

    Function.prototype.__proto__ === Object.prototype //true
    其实这一点我也有点困惑,不过也可以试着解释一下。 Function.prototype是个函数对象,理论上他的__proto__应该指向 Function.prototype,就是他自己,自己指向自己,没有意义。 JS一直强调万物皆对象,函数对象也是对象,给他认个祖宗,指向Object.prototype。Object.prototype.__proto__ === null,保证原型链能够正常结束。

      再来看下面的:

    //原型和原型链是JS实现继承的一种模型。
    //原型链的形成是真正是靠__proto__ 而非prototype
    
    var animal = function(){};
     var dog = function(){};
    
     animal.price = 2000;
     dog.prototype = animal;
     var tidy = new dog();
     console.log(dog.price) //undefined
     console.log(tidy.price) // 2000
    
    
    var dog = function(){};
     dog.prototype.price = 2000;
     var tidy = new dog();
     console.log(tidy.price); // 2000
     console.log(dog.price); //undefin
    
    
     var dog = function(){};
     var tidy = new dog();
     tidy.price = 2000;
     console.log(dog.price); //undefined

    实例tidy和 原型对象dog.prototype存在一个连接。不过,要明确的真正重要的一点就是,这个连接存在于实例tidy与构造函数的原型对象dog.prototype之间,而不是存在于实例tidy与构造函数dog之间。
    构造器constructor

    constructor 属性返回对创建此对象的函数对象的引用。

    function a(){};
    console.log(a.constructor===Function); //true
    console.log(a.prototype.constructor===a); //true

    函数a是由Function创造出来,那么它的constructor指向的Function,a.prototype是由new a()方式创造出来,那么a.prototype.constructor理应指向a

    1、组合继承

    //		组合继承
    		function Animal(){
    			this.name=name||'Animal';
    			this.sleep=function(){
    				console.log(this.name+'sleep');
    			}
    		}
    		Animal.prototype.eat=function(food){
    			console.log(this.name+'eat'+food);
    		}
    		
    		function Cat(name){
    			Animal.call(this);//继承实例属性/方法,也可以继承原型属性/方法
    			this.name=name||'tom';//调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
    		}
    		Cat.prototype=new Animal();
    		Cat.prototype.constructor=Cat;//组合继承也是需要修复构造函数指向的。
    		var cat = new Cat();//既是子类的实例,也是父类的实例
    		console.log(Cat.prototype.constructor);
    		console.log(cat.name)
    		console.log(cat.eat('haha'))//可传参
    

      

    特点:

     可以继承实例属性/方法,也可以继承原型属性/方法

    1. 既是子类的实例,也是父类的实例
    2. 不存在引用属性共享问题
    3. 可传参
    4. 函数可复用

    缺点:

    1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

    推荐指数:★★★★(仅仅多消耗了一点内存)

    2、寄生组合继承

    寄生组合继承
    		function Animal(){
    			this.name=name||'Animal';
    			this.sleep=function(){
    				console.log(this.name+'sleep');
    			}
    		}
    		Animal.prototype.eat=function(food){
    			console.log(this.name+'eat'+food);
    		}
    		
    		function Cat(name){
    			Animal.call(this);
    			this.name=name||'tom';
    		}
    		
    		(function(){
    			var Super=function(){};// 创建一个没有实例方法的类
    			Super.prototype=Animal.prototype;
    			Cat.prototype=new Super(); //将实例作为子类的原型
    		})()
    		Cat.prototype.constructor = Cat;
    		var cat=new Cat();
    		console.log(cat.eat('haha'))
    

      

    特点:

    1. 堪称完美

    缺点:

    1. 实现较为复杂

    推荐指数:★★★★(实现复杂,扣掉一颗星)

  • 相关阅读:
    汽车金融评分卡
    Lending Club 数据做数据分析&评分卡
    时间序列分析和预测 (转载)
    距离计算公式总结(转载)
    机器学习常用算法与辅助函数公式
    金融领域常用的数据分析方法
    常用模型评估方法总结
    A--集成算法的实现
    A--随机森林(RF)的 sciklit-learn 实现
    A--Scikit-Learn 实现决策树
  • 原文地址:https://www.cnblogs.com/yiyi17/p/8430827.html
Copyright © 2011-2022 走看看