zoukankan      html  css  js  c++  java
  • javascript--prototype机制

      在Javascript对象有一个特殊的prototype内置属性,它其实就是对于其他对象的引用

      当我们试图访问一个对象下的某个属性的时候,会在JS引擎触发一个GET的操作,首先会查找这个对象是否存在这个属性,如果没有找的话,则继续在prototype关联的对象上查找,以此类推。如果在后者上也没有找到的话,继续查找的prototype,这一系列的链接就被称为原型链。

      在javascript中对象都是可以使用tostring(),valueOf()方法,函数都是可以使用call(),apply(),因为普通对象都是通过prototype链最终指向了内置的Object.prototype,而函数都是指向了Function.prototype。

    基于prototype机制实现"继承"

      在javascipt中实现继承背后是依靠prototype机制的,javascript与其他面向类的语言不同,它是没有类作为对象的抽象模式去实现继承(在ES6中是有class这个关键字的,也是prototype机制的一种语法糖),但是我们可以在javascript中写出模仿类的代码,这里来看看常见的构造函数模式+原型模式:

    function Animal(name,color){
            this.name = name;
            this.color = color;
        }
        Animal.prototype.type = "动物";
        Animal.prototype.msg = function(){
            alert( "I am "+this.name+" my color is "+this.color);
        }
        var cat = new Animal("cat","black");
        console.log(cat.name);              //cat
        console.log(cat.type);                 //动物
        console.log(cat.constructor === Animal);        //true
    

      

      函数Animal就可以看做是一个'类',里面定义了name和color属性,主要看下划线的代码,cat是通过new关键字将Animal实现“构造函数调用”产生的新对象,并传入两个参数。

      一般我们通过new来调用函数,会发生以下的操作:

      1、创建一个全新的对象;

      2、这个新对象会被执行prototyp链接;

      3、这个新对象会被绑定函数调用的this上;

      4、如果函数没有返回对象,那么new表达式中的函数调用会自动返回这个新对象

      可以看到,使用getPrototypeOf获取cat对象的原型是全等于Animal.prototype的,cat也如预期那样继承了Animal的name、color属性,当然也继承了type属性和msg方法了,这段代码展示了两个技巧:

      ·this.name = name给每个对象都添加了name属性,有点像类实例封装的数据值;

      ·Animal.prototype.type和Animal.prototype.msg会给Animal.prototype对象添加一个属性和方法,现在cat.type和cat.msg()是可以正常工作的,背后是依靠prototype机制,因为在创建cat对象的时候,内部的prototype都会关联到Animal.prototype上了,当cat中无法找到type(或者msg)的时候,就会到Animal.prototype上找到。

      它是函数,为什么会被我们认为是"类"?

      在javascript中有个约定俗成的惯例,就是“构造函数”(或者叫类函数)首字母需要大写,其实这个Animal函数和其他普通的函数是没有任何区别的,只是我们在调用这个函数的时候加上一个new的关键字,把这个函数调用变成一个“构造函数调用”。

      大概就是这个函数都是要通过new来调用了吧。

       其实还有个更重要的原因,来看一段代码:

    function Animal(name,color){
            this.name = name;
            this.color = color;
        }
        Animal.prototype.type = "动物";
        Animal.prototype.msg = function(){
            alert( "I am "+this.name+" my color is "+this.color);
        }
        var cat = new Animal("cat","black");
        console.log(cat.constructor === Animal);    //true
    

      新创建的对象cat里面有个constructor属性,它是全等于函数Animal,看起来就像是cat对象是由Animal函数构造出来的

      但事实是cat.consructor引用同样被关联到Animal.prototype上,Animal.prototype.constructor默认指向了Animal,来看一段代码验证一下这个观点

      

    function Foo(){
                name : 'x'
        }
    Foo.prototype = { } var foo = new Foo(); console.log(foo.constructor === Foo);    //false console.log(foo.constructor === Object); //true

      上面代码定义了一个函数Foo同时修改了原型对象prototype,foo是通过new调用Foo产生的新对象,但是foo.constructor并不等于Foo,原因是foo上并不存在constructor属性,所以它会委托原型链到Foo.prototpe上查找,但是这个对象上也没有constructor属性,所以会继续在原型链找,直到原型链的终点--Object.prototype,这个对象有constructor属性,并指向了内置的Object()函数。

      foo是由Foo构造出来这个观点是错误的。

      原型继承和类继承的区别?

      在面向类的语言中,类可以被复制多次,就像模具制作东西一样,实例一个类的时候就会将类的行为复制一份到物理对象中,但是在javascript中,是不存在这种复制机制的,在创建实例对象时,它们的prototype会被关联到“构造函数”原型对象上。拿之前的栗子来说就是cat对象的prototype被关联到Animal.prototype上,当我们想访问cat.type时,就会在原型链上去查找,而不是另外复制一份出来保存到新对象上

      来看一段代码:

    function Foo(){
                    name : 'x'
            }
            Foo.prototype = {
                    friends : ['y','z','c']
            }
            var foo = new Foo();   
            console.log(foo.friends);                 //["y", "z", "c"]
            Foo.prototype.friends.push('a');          //向Foo.prototype.friends添加一个a
            console.log(foo.friends);                 //["y", "z", "c", "a"]     
    

      继承的打开方式

      在javascript中有许许多多的继承方式:原型继承、借用构造函数、寄生继承等等。

      假设我们现在有两个“类”,SuperType和SubType,我们想要SubType去继承SuperType,就要修改Subtype的原型了,常见的写法有:

      ·SubType.prototype = SuperType.protype

      ·Subtype.protype = new SuperType()

       安利另一种写法,来看代码:

    function Foo(name){
                    this.name = name;
            }
            Foo.prototype.sayName = function(){
                    console.log(this.name);
            }
            function Bar(name,label){
                    Foo.call(this,name);            //借用Foo函数
                    this.label = label;
            }
            Bar.prototype = Object.create(Foo.prototype);       //创建Bar.prototype对象并关联到Foo.prototype上
            //在Bar.prototype添加一个方法
            Bar.prototype.sayLabel = function(){
                    console.log(this.label);
            }
        
            var a = new Bar('x','person');
            a.sayName();      //x
            a.sayLabel();       //person
    

      这段代码的核心语句就是下划线的Bar.prototype = Object.create(Foo.prototype),调用Object.create会凭空创建一个新对象并把新对象内部的prototype关联到你指定的对象上。

      为什么要安利这种写法呢?

      ·Bar.prototype = Foo.prototype并不会创建一个关联到Bar.prototype的新对象,这样只是让Bar.prototype直接引用Foo.prototype对象,当我们试图在Bar.prototype添加(或者修改)属性或者方法时,就相当于修改了Foo.prototype对象本身了,这会影响到后面创建的后代,显然不是我们想要的

      ·Bar.prototype = new Foo()的确会创建一个关联到Bar.prototype的新对象,这样写法会调用Foo函数,假如Foo里面有一些讨厌的操作:比如向this添加属性、写日志、修改状态等等也会影响到后代,这也不是我们想看到的

      因此,需要创建一个合适的关联对象,通过使用 Object.create

      在ES6新增了一个辅助函数Object.setPrototypeOf,它可以修改对象的prototype关联,用法如下:

      Object.setPrototypeOf(Bar.prototype,Foo.prototype);

      结束语:这篇博文酝酿了挺久,关于原型链可以展开来细讲的点很多,也很凌乱,一直想把这些点串起来,也是自己对于这些知识点理解不够透彻、掌握不熟练,如果文中出现错误的地方,欢迎大家指正,如果这篇博文有些许帮助,点下右下角的推荐哈:)

    参考资料:《你不知道的javascript》

      

      

  • 相关阅读:
    log4j1修改DailyRollingFileAppender支持日志最大数量
    log4j1 修改FileAppender解决当天的文件没有日期后缀
    log4j生成有日期的日志文件名
    Java删除List和Set集合中元素
    Java并发编程:并发容器之ConcurrentHashMap
    Java并发编程:并发容器之CopyOnWriteArrayList
    java.util.ConcurrentModificationException解决详解
    Handshake failed due to invalid Upgrade header: null 解决方案
    web项目Log4j日志输出路径配置问题
    log4j.properties 的使用详解
  • 原文地址:https://www.cnblogs.com/weapon-x/p/5330526.html
Copyright © 2011-2022 走看看