美文网首页
一文搞懂原型和原型链

一文搞懂原型和原型链

作者: 蛙哇 | 来源:发表于2019-11-06 17:41 被阅读0次

    前瞻

    JavaScript是面对对象编程,但是它又跟其他编程语言不一样,不同之处是JavaScript面对对象并不是依赖于抽象类,而是通过原型链。在C++Java使用new命令时,都会调用"类"的构造函数。而在Javascript语言中,new命令后面跟的不是类,而是构造函数。但是,用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。为了解决这个问题,于是在构造函数设置了prototype属性,也就是原型。

    从一个构造函数说起

    用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。从而造成对系统资源的浪费。具体例子如下:

    function Cat(name, color) {
      this.name = name;
      this.color = color;
      this.meow = function () {
        console.log('喵喵');
      };
    }
    
    var cat1 = new Cat('大毛', '白色');
    var cat2 = new Cat('二毛', '黑色');
    
    cat1.meow === cat2.meow
    // false
    

    从上面的代码,可以cat1cat2meow方法是独立的但是实现的功能都是一样的,这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。这个问题的解决方法,就是JavaScript构造函数中添加prototype属性。

    prototype是什么?

    只有函数才有prototype属性,又称为显式原型。prototype属性包含一个对象,所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。

    JavaScript规定,每个函数都有一个prototype属性,指向一个对象。

    function f() {}
    typeof f.prototype // "object"
    

    对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。

    function Animal(name) {
      this.name = name;
    }
    Animal.prototype.color = 'white';
    
    var cat1 = new Animal('大毛');
    var cat2 = new Animal('二毛');
    
    cat1.color // 'white'
    cat2.color // 'white'
    

    上面代码中,构造函数Animalprototype属性,就是实例对象cat1cat2的原型对象。原型对象上添加一个color属性,结果,实例对象都共享了该属性。

    原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻体现在所有实例对象上

    Animal.prototype.color = 'yellow';
    
    cat1.color // "yellow"
    cat2.color // "yellow"
    

    如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。

    cat1.color = 'black';
    
    cat1.color // 'black'
    cat2.color // 'yellow'
    Animal.prototype.color // 'yellow';
    

    总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。

    注:Object.prototype中Object是一个全局对象,也是一个构造函数,以及其他基本类型的全局对象也都是构造函数。
    

    proto又是什么?

    _proto_是每个对象都有的属性,又称为隐式原型。但是,_proto_不是一个规范属性,只是部分浏览器实现了此属性,对应的标准属性是[[Peototype]]

    大多情况下,_proto_可以理解为‘构造函数的原型’,是实例和它的构造函数之间建立的链接,即_proto_ ===constructor.prototype

    例子:

    // 字面量方式
    var a = {};
    console.log(a.prototype);  //undefined
    console.log(a.__proto__);  //Object {}
    
    //构造器方式
    var b = function(){}
    console.log(b.prototype);  //b {}
    console.log(b.__proto__);  //function() {}
    
    // Object.create方式
    var a1 = {}
    var a2 = Object.create(a1)
    console.log(b.prototype);  //undefined
    console.log(b.__proto__);  //Object a1
    

    ES6中的proto使用建议

    本段摘自阮一峰ES6入门,具体解析可以点击查看。

    1、_proto_属性没有写入ES6的正文,而是写入了附录,

    2、原因是__proto__前后的双下划线,说明它本质上是一个内部属性,而不是一个正式的对外的API,只是由于浏览器广泛支持,才被加入了ES6

    3、标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。

    4、无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。

    原型链是什么?

    每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。

    原型链的终点

    所有对象的原型最终都可以上溯到Object.prototypeObject.prototype对象有没有它的原型呢?回答是Object.prototype的原型是nullnull没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null

    1、字符串原型链终点

    let test = 'hello'
    
    // 字符串原型
    let stringPrototype = Object.getPrototype(test) 
    
    // 字符串的原型是String对象
    stringPrototype === String.Prototype  // true 
    
    // String对象的原型是Object对象
    Object.getPrototypeOf(stringPrototype) === Object.prototype // true 
    

    2、函数原理链的终点

    
    let test = function () {}
    let fnPrototype = Object.getPrototype(test)
    fnPrototype === Function.prototype // true
    Object.getPrototypeOf(Function.prototype) === Object.Prototype // true
    
    

    原型链是用来做什么?

    1、属性查找

    当JS引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在,会去原型链上查找,但不会查找本自身的prototype。

    用一个例子来形象说明一下:

    let test = 'hello'
    
    // 字符串原型
    let stringPrototype = Object.getPrototype(test) 
    
    // 字符串的原型是String对象
    stringPrototype === String.Prototype  // true 
    
    // String对象的原型是Object对象
    Object.getPrototypeOf(stringPrototype) === Object.prototype // true 
    

    2、拒绝查找原型链

    hasOwnPrototype:指示对象自身属性中是否具有指定的属性。

    语法:obj.hasOwnPrototype(prop)

    参数prop:查找的属性。

    返回值:Boolean判断是否存在。

    let test = {'ABC': 'EDF' }
    test.hasOwnPrototype('ABC') // true
    test.hasOwnPrototype('toString') // false
    

    该方法是在Object.prototype上,所有对象都可以使用,且会忽略掉那些从原型链上继承的属性。

    扩展

    constructor 属性

    prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

    function P() {}
    P.prototype.constructor === P // true
    

    由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。

    function P() {}
    var p = new P();
    
    p.constructor === P // true
    p.constructor === P.prototype.constructor // true
    p.hasOwnProperty('constructor') // false
    

    上面代码中,p是构造函数P的实例对象,但是p自身没有constructor属性,该属性其实是读取原型链上面的P.prototype.constructor属性。

    constructor 属性的作用

    constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。

    举个小栗子:

    function F() {};
    var f = new F();
    
    f.constructor === F // true
    f.constructor === RegExp // false
    

    另一方面,有了constructor属性,就可以从一个实例对象新建另一个实例。

    我再举个小栗子:

    function Constr() {}
    var x = new Constr();
    
    var y = new x.constructor();
    y instanceof Constr // true
    

    总结

    天下无难事,只怕有心人。原型和原型链算是前端工程师的开发基础了,原型虽然不太好理解,但是我相信只要有心学习和实践,也没有想象中的那么难。希望,这篇文章对我自己帮组的同时,童鞋们看完之后也有一定的收获。

    相关文章

      网友评论

          本文标题:一文搞懂原型和原型链

          本文链接:https://www.haomeiwen.com/subject/ldfpbctx.html