JavaScript基础自检之原型和原型链

534 阅读5分钟

这篇总结,主要是看了一名【合格】前端工程师的自检清单这篇文章,照着单子来整理总结下,这篇介绍的是「JavaScript中的原型和原型链」这个小结,是按着其中的问题进行归档整理的,其中参考了很多前辈总结的知识,在这里表示感谢!

  1. JavaScript基础自检之原型和原型链
  2. JavaScript自检之变量和类型
  3. JavaScript基础自检只作用域闭包

理解原型设计模式以及JavaScript中的原型规则

  • 原型

    [[Prototype]]是对象中的一个内置属性,表示对对象的引用;除了顶级Object的[[prototype]],其他的[[Prototype]]均不为null。

  • 原型链

    内置属性[[Prototype]],表示对对象的引用,而其引用的对象也有[[Prototype]],以此类推,直到顶级对象Object的[[prototype]],而这些[[Prototype]]的构成的集合,就被称为原型链。

  • 原型规则

    • 所有引用类型(数组、对象以及函数),都具有对象的特征,可自由扩展属性;

    • 所有的引用类型,都具有一个「proto」,属性值是一个普通对象;

    • 所有函数,都有一个prototype属性,属性值是一个指向原型对象的指针;

    • 所有引用类型,其隐式原型指向其构造函数的显示原型;

    • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__中去找;

  • 原型对象

    函数的prototype属性指向的对象,即被成为原型对象。该对象包含了通过构造函数调用创建对象的所有共享的方法和属性。

  • 创建对象的方式

    • 工厂模式

      在函数内创建一个对象,给对象赋予属性及方法再将对象返回

      function Person() {    
          var person = new Object();    
          return person;
          
      }
      var n = Person();
      
    • 构造函数模式

      无需在函数内部创建新对象,使用this

      function Person(name) {    
          this.name = name   
          // ....
      }var p = new Person();
      
    • 原型模式

      函数中不对属性进行定义,利用prototype属性进行定义。

      function Person(name) {    
          Person.prototype.name = name;
          
      }
      var p = new Person();
      
    • 混合模式

      原型模式 + 构造函数模式

      function Person(name) {
          // 定义实例属性    
          this.name = name;
      }
      Person.prototype.sayname = function() { 
          // 定义公共属性和方法    
          return this.name;
      }
      var n = new Person();
      
    • 动态原型模式

      将所有信息封装在构造函数中,而通过构造函数初始化属性,动态掌握方法是否需要关联到原型中

      function Person(name) {    
          this.name = name;    
          if(typeof Person._ddd === 'undefined') {                       
              Person.prototype.sayname = function() {           
                  return this.name;
              } 
              Person._ddd = true;    
          }
      }
      
    • 寄生构造函数模式

      封装创建对象的代码,然后返回新的对象

      function Person(name) {    
          var n = new Object();    
          n.name = name;    
          /// ...    
          return n;
      }
      var n = new Person();
      
    • 稳妥构造函数

      道格拉斯.克罗克福德发明了JavaScript中稳妥对象(durable objects)这一概念。所谓稳妥是指没有公共属性,而且其方法也不引用this的对象。

      稳妥构造函数遵循与寄生构造函数类似的模式,但有2点不同:

      1、新创建对象的实例方法不引用this

      2、不使用new操作符调用构造函数

      实现:

      function Person(name) {    
          var o = new Object();    
          // TODO 定义私有变量和方法    
          // 添加方法    
          o.sayName = function() {        
          alert(name);    
              
          }   
          return o;
      }
      

实现一个方法:能简单模拟instanceof的功能

  • instanceof的polyfill版本

    function instanceofPolyfill(instance, obj) { //instance->实例;obj->func
        let type = typeof instance;
        if(type == 'null' || type == 'undefined') {
            throw new Error('instance must be instance Object');
        }
        
        if(type == 'string' || type == 'number' || type == 'boolean') {
            return false;
        }
        
        if(typeof obj != 'function') {
            throw new Error('obj must be Function');
        }
        
        console.log('obj是', obj.constructor.prototype);
        var _prototype = obj.constructor.prototype;
        if(obj.prototype.isPrototypeOf(instance)) {
            // obj出现在instance的原型链上
            return true;
        }
        
        return false;
    }
    

实现继承的几种方式以及他们的优缺点

  • 类式继承

    function Animal() {    
        this.name = 'animal';
        
    }
    function Dog() {    
        this.name = 'dog'
        
    }
    var _dog = new Animal();
    
    // 类式继承有个不足:对于引用类型的数据,一个实例修改值,则所有实例对应的该值都会变。
    
  • 构造函数继承

    function Animal() {   
        this.name = 'erer';
    }
    
    function Dog() {    
        Animal(this, arguments); 
        // 注:arguments为函数中的参数对象
    }
    
    var _dog = new Dog();
    // 构造函数继承,获取不到父类的公共方法(非)
    
  • 组合继承

    function Animal() {    
        this.name = '12312';
    }
    
    function Dog() {    
        Animal(this, arguments);
    }
    
    var _dog = new Animal();
    // 实例化 子类 时,会调用两次父类的构造函数
    
  • 寄生组合式继承

    function Animal() { 
        this.name = '12312'
    }
    
    function Dog() { 
        Animal(this, arguments);
    }
        
    Dog.prototype = Object.create(Animal);
    Dog.prototyp.constructor = Dog;
    
  • extends继承

    ES6中新引入的继承方式。

属性设置和屏蔽

对一个对象设置属性,不仅仅是简单的添加一个新属性或者修改现有的属性,下面以

myobject.foo = '123';

为例,分情况讲解下这个过程:

  1. myobject中包括foo这个普通的属性,那么就会直接修改现有的属性;

  2. foo不存在于myobject,并不存在其[[Prototype]]中,那么就会直接添加为myobject;

  3. foo存在于myobject的原型链行,那么:

    • 如果为普通的属性,并且是可写,那么就会添加到myobject,并且foo为屏蔽属性

    • 如果为普通的属性,并且是不可写,在严格模式下,会报错;在非严格模式下,不会进行任何操作:不会覆盖属性,不会添加到myobject中。

    • 如果是一个setter,那就一定会调用这个setter。foo不会添加到myobject,也不会重新定义foo这个setter。

new 运算符调用,会发生什么?写一个函数。

function newPolyfill(obj) { 
    // obj为要new的构造函数    
    // 处理参数    
    var args = [].slice.call(arguments);    
    args.shift();
    
    var _obj = Object.create({});    
    _obj.__proto__ = obj.prototype;          
    var res = obj.apply(_obj, args);   
    
    if((res != null && typeof res === 'object') || typeof res === 'function'){       
        return res;   
    }        
    return _obj;
}

理解es6 class构造以及继承的底层实现原理

根据MDN文档结合我自己的理解,es class是现有JavaScript原型继承的扩展,原理还是原型扩展。可以参考实现继承的几种方式以及他们的优缺点