一名【合格】前端工程师的自检清单之原型和原型链

Author Avatar
w4ctech 6月25日
  • 在其它设备中阅读本文章

最后更新于2019年06月27日; 如遇到问题,请留言及时通知站长

1、理解原型设计模式以及javascript中的原型规则

原型模式:使用构造函数的prototype属性来指定那些应该共用的属性和方法。

  • javascript语言中,除来undefinedsymbol,其他类型都会被包装成对象来处理(Number、String、Boolean),来方便我们共享一些属性和方法。
  • 在 js 中对象的形成有以下几种方式:

    1. new一个构造函数;
    2. 字面量{}创建;
    3. 隐式的装箱操作(周期短)和Object的一些方法;

原型规则

  • 由于原型模式的特点,也就造就来原型的一些特性;
  • 原型与 in 操作符:in操作符单独使用可以判断属性是否可以被对象访问,无论该属性存在实例中还是原型中;在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。
function Person(){
}                    
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;

var person1 = new Person();        
alert(person1.hasOwnProperty("name"));  //false
alert("name" in person1); //true

//无论该属性存在于实例中还是存在于原型中。
//同时使用 hasOwnProperty()方法和 in 操作符,
//就可以确定该属性到底是存在于对象中,还是存在于原型中。
function hasPrototypeProperty(object, name){
    return !object.hasOwnProperty(name) && (name in object);
}  
  • 实例中的指针仅指向原型,而不指向构造函数。
  • 构造函数的prototype指向共享的对象,我们通过new 这个构造函数来获取这些共享的属性和方法;而每个对象都有一个__proto__属性指向一个Object。而 new 形成的实例,就是把__proto__指向来构造函数的prototype
  • 构造函数的prototype===实例的__proto__;
  • 基本类型:String、Number、Boolean、Symbol、Undefined、Null
  • 引用类型:Object

2、instanceof 的底层实现原理,手动实现一个 instanceof

  • instanceof是用来判断引用类型,也可以通过原型链实现继承关系的判断。
  • instanceof的实现实际上是调用 JS 内部函数 [[HasInstance]] 来实现的。
function instanceof(left, right) {    
    const rightVal = right.prototype    
    const leftVal = left.__proto__    
    // 若找不到就到一直循环到父类型或祖类型    
    while(true) {        
        if (leftVal === null) {            
            return false
        }        
        if (leftVal === rightVal) {            
            return true
        }
        leftVal = leftVal.__proto__ // 获取祖类型的__proto__
    }
}

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

  • 继承是面向对象的编程中最重要的概念,在语言中都支持两种继承方式:接口继承和实现继承。接口继承只继承方法名,而实现继承则 - 继承实际的方法。在 Js 中无法实现接口继承,只支持实现继承,也就是原型链继承。
  • 原型链继承:利用原型让一个引用类型继承另一个引用类型的方法和属性。
//一个构造函数
function SuperType(){
        this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
//另一个构造函数
function SubType(){
    this.subproperty = false;
}
//继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};                    
var instance = new SubType();
//instance
//SubType {subproperty: false}
    subproperty: false
__proto__: SuperType
getSubValue: ƒ ()
property: true
__proto__: Object

缺点:构造函数原型上的属性在所有该构造函数构造的实例上是共享的,即属性没有私有化,原型上属性的改变会作用到所有的实例上。

借用构造函数继承 :在构造子类构造函数时内部使用 call 或 apply 来调用父类的构造函数。

function Super(){  
    this.flag = true;  
}  
function Sub(){  
    Super.call(this)  //如果父类可以需要接收参数,这里也可以直接传递
}  
var obj = new Sub();  
obj.flag = false;  
var obj_2 = new Sub();  
console.log(obj.flag) //依然是true,不会相互影响

优缺点:实现了属性的私有化,但是子类无法访问父类原型上的属性。

组合继承:利用构造函数和原型链的方法,可以比较完美的实现继承。

function Super(){  
    this.flag = true;  
}  
Super.prototype.getFlag = function(){  
    return this.flag;     //继承方法  
}  
function Sub(){  
    this.subFlag = flase  
    Super.call(this)    //继承属性  
}  
Sub.prototype = new Super;//会导致Sub.prototype的constructor指向Super
var obj = new Sub();  
Sub.prototype.constructor = Sub;  
Super.prototype.getSubFlag = function(){  
    return this.flag;  
}

寄生继承 :即将sub.prototype=new super改为sub.prototype=Object.creat(supper.prototype),避免了组合继承中构造函数调用了两次的弊端。

4、至少说出一种开源项目 ( 如Node)中应用原型继承的案例

  • node中继承通过util中的inherites方法来实现。接受两个参数,第一个参数是要继承的构造函数,第二个参数是父类的构造函数,通过子类的prototype指向一个新对象,新对象是拷贝父类的prototype,并修改constructor指向子类自己。
//inherits 的源码
exports.inherits = function(ctor, superCtor) {
 ctor.super_ = superCtor;
 ctor.prototype = Object.create(superCtor.prototype, {
 constructor: {
  value: ctor,
  enumerable: false,
  writable: true,
  configurable: true
 }
 });
};

5、可以描述new一个对象的详细过程,手动实现一个 new 操作符

  • new一个对象的详细过程:

    1. 创建一个新对象;
    2. 对象的__proto__指向构造函数的prototype
    3. 绑定this
    4. 返回一个对象实例;
function newObject(){
  //创建一个空对象
  let obj = new Object();

  //获取构造函数
  let Constructor = [].shift.call(arguments);

  //链接到原型
  obj.__proto__ = Constructor.prototype;

  //绑定this值
  //使用apply,将构造函数中的this指向新对象,
  //这样新对象就可以访问构造函数中的属性和方法
  let result = Constructor.apply(obj,arguments);

  //返回新对象
  //如果返回值是一个对象就返回该对象,否则返回构造函数的一个实例对象
 return typeof result === "object" ? result : obj;

}

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

class 的构造 javascript的底层离不开构造函数,可以说构造函数是js语言的核心。而class类是对构造函数的一种规范使用。
创建一个类:

class Parent {
  constructor(a){
    this.filed1 = a;
  }
  filed2 = 2;
  func1 = function(){}
}

babel转换

"use strict";
//_instanceof方法判断this实例是否是构造函数生成
function _instanceof(left, right) {
    if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { 
        return right[Symbol.hasInstance](left); 
    } else { 
        return left instanceof right; 
    } 
}
//`classCallCheck防止类的直接调用
function _classCallCheck(instance, Constructor) { 
    if (!_instanceof(instance, Constructor)) { 
        throw new TypeError("Cannot call a class as a function"); 
    } 
}
//_defineProperty方法定义类中定义的属性
function _defineProperty(obj, key, value) { 
    if (key in obj) { 
        Object.defineProperty(obj, key, 
            { 
                value: value, 
                enumerable: true, 
                configurable: true, 
                writable: true 
            }); 
    } else { 
        obj[key] = value; 
    } 
    return obj; 
}
//创建构造函数
var Parent = function Parent(a) {
  _classCallCheck(this, Parent);

  _defineProperty(this, "filed2", 2);

  _defineProperty(this, "func1", function () {});

  this.filed1 = a;
};

类的继承 :类的继承是通过extendssuper来实现的。

//ES6继承
class Child extends Parent {
    constructor(name,age){
        super(name,age);
    }
    coding(){
        console.log("I can code JS");
    }
}

babel转换:

//ES6继承
//判断可能的返回值
function _possibleConstructorReturn(self, call) { 
    if (call && (_typeof(call) === "object" || typeof call === "function"))
     { return call; }
    return _assertThisInitialized(self); 
}
//判断this是否继承和super是否调用
function _assertThisInitialized(self) { 
    if (self === void 0) { 
        throw new ReferenceError("this hasn't been initialised - 
        super() hasn't been called"); 
    } 
    return self; 
}
//继承的主要实现方法
function _inherits(subClass, superClass) { 
    if (typeof superClass !== "function" && superClass !== null) { 
        throw new TypeError("Super expression must either be null or a 
        function"); 
    } 
    subClass.prototype = Object.create(superClass && superClass.prototype, 
    { constructor: { 
            value: subClass, 
            writable: true, 
            configurable: true 
         }
    }); 
    if (superClass) _setPrototypeOf(subClass, superClass); 
}
//设置原型
function _setPrototypeOf(o, p) { 
    _setPrototypeOf = Object.setPrototypeOf || 
        function _setPrototypeOf(o, p) { 
            o.__proto__ = p; 
            return o; 
        }; 
    return _setPrototypeOf(o, p); 
}
//父构造函数
var Parent = function Parent(a) {
  _classCallCheck(this, Parent);

  _defineProperty(this, "filed2", 2);

  _defineProperty(this, "func1", function () {});

  this.filed1 = a;
};

//子构造函数
var Child = function (_Parent) {
  _inherits(Child, _Parent);

  function Child(name, age) {
    _classCallCheck(this, Child);

    return _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name, age));
  }

  _createClass(Child, [{
    key: "coding",
    value: function coding() {
      console.log("I can code JS");
    }
  }]);

  return Child;
}(Parent);

在理解_inherits函数之前我们需要明白:
Function.__proto__===Function.prototype;

Function.prototype.__proto__.constructor===Object;

Function.__proto__.constructor===Function;

_inherits` 函数的核心就是

subClass.__proto__=superClass;

subClass.prototype__proto__=superClass.prototype;

superClass.prototype.constructor=subClass;

本文链接:https://i.w4ctech.cn/frontWeb/Prototype_prototypeChain.html
This blog is under a CC BY-NC-SA 3.0 Unported License