Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
w4ctech opened this issue Jun 25, 2019 · 0 comments
Open

Comments

@w4ctech
Copy link

w4ctech commented Jun 25, 2019

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;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant