prototype

我们知道每个函数都有一个prototype指向他的原型对象。
我们可以这么理解: 每个对象在创建时就与另一个对象产生关联,其实就是对另一个对象的引用。而js 再找对象属性时会顺着原型链一直往上找,直到无法找到为止

来看一下

function Person(name) {
    this.name = name
}

// 控制台打印一下, => 是结果
Person.prototype  => { constructor: f }

// 而 Person.prototype.constructor 又指向与构造函数自己
Person.prototype.constructor == Person  // true

用下图表示:
1591078556.jpg

proto

每个对象都有一个__proto__属性,除了null外,而这个属性又指向他的原型对象

验证一下

function Person(name) {
    this.name = name
}

var person = new Person("Bill");

// 控制台打印一下, person.__proto__
person.__proto__ => { constructor: f }

// 我们从上面[prototype]看 Person.prototype 也是指向 { constructor: f }, 那么他们是否是同一个东西呢?
person.__proto__ == Person.prototype   // 结果是 true 的

// 上面一样的话,那么
person.__proto__.contructor == Person // true

用图表示一下就是

1591082949_1_.jpg

new 操作符

这里解释一下new 操作符,其实跟原型有关

首先搞清楚new 做了什么

  • 创建对象
  • 将对象的原型指向函数的原型
  • 改变 this 上下文
  • 返回这个对象
function newObj(fn, ...args) {
    var obj = {};
    obj.__proto__ = fn.prototype;
    let res = fn.call(obj, ...args);

    let isObject = typeof res === 'object' && typeof res !== null;
    let isFunction = typeof res === 'function';
    return isObject || isFunction ? res : obj;

    // 这里其实可以直接返回obj
    // 但防止 fn 这个方法直接返回this, 以下这种情况

    // function fn(name) {
    //     this.name = name;
    //     return this;
    // }
} 

function Person(name) {
    this.name = name;
}
// 测试一下
person = newObj(Person, "Bill")

// 打印一下 person
// Person {name: Bill}

Fn.prototype.proto

  • 我们说Fn.prototype 是一个原型对象,我们上面说只要是对象都有__proto__, 那么Fn.prototype.__proto指向的是什么呢?

答案是: Object.prototype

Object.prototype == Person.prototype.__proto__  // true

所以有了下图

1591086668_1_.jpg

到此原型基本已经讲完了

继承

下面写继承

原型链继承

function Parent(){
    this.role = "parent"
    this.list = [1,2,3]
}

Parent.prototype.getRole = function() {
    return this.role
}

function Child(name) {
    this.name = name
}

Child.prototype = new Parent();

Child.prototype.getName = function() {
    return this.name;
}

var children = new Child("admin");
children.getName()
children.getRole()

var parent = new Parent();
console.log(parent.role)  // parent

Child.prototype.value = "123"
console.log(parent.value);  // "undefined"
console.log(children.value) // “123”

// 以下是原型链的缺点
var children2 = new Child("child2");
console.log(children.list, children2.list) // [1,2,3], [1,2,3,4]
children.list.push(4);
console.log(children.list, children2.list) // [1,2,3,4], [1,2,3,4]

看下图

1591092645_1_.jpg

  • 当执行了 Child.prototype = new Parent(), 我们说找属性的话,会顺着原型链去找, 比如children要找getRole,那么他会一直找,Child.prototype.__proto__下有getRole, 这就是继承
  • 当执行了 Child.prototype.value = "123", children实例也拥有了value
  • 缺点:
    • 只要在某个子类下改变了父类引用类型的值,那么子类都会改变,这样做就不能做到相互独立
    • 无法实现父类构造函数属性的赋值

使用call,改变this 作用域

function Parent() {
    this.role = "parent"
}

function Child(role, name) {
    Parent.call(this, role)
    this.name = name;
}

var children = new Child("children", "哈哈哈");

// 注意
Parent.prototype.getRole = function() {
    return this.role;
}

// 上面原型方法,子类是无法继承的,即children没有getRole 方法

优点:

  • 解决了原型继承无法向父类赋值的问题
  • 可以继承多个构造函数属性(call多个);

缺点:

  • 只能继承父类构造函数的属性。原型链新创建的无法被继承
  • 无法实现构造函数的复用。(每次用每次都要重新调用)
  • 每个新实例都有父类构造函数的副本,臃肿。

组合模式

将call方式和原型链方式组合起来

function Parent(role) {
    this.role = role
}

Parent.prototype.value = "test";

function Child(role, name) {
    Parent.call(this, role);
    this.name = name;
}

Child.prototype = new Parent();

var children = new Child("child", "admin")

优点:

  • 可以继承父类原型上的属性,可以传参,可复用;
  • 每个新实例引入的构造函数属性是私有的;

缺点:

  • 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数;

寄生式继承

function Parent(role) {
    this.role = role;
    this.list = [1,2,3];
}

Parent.prototype.value = "123"

// 原型式继承
function _extend(obj) {
    function F(){};
    F.prototype = obj;  // F.prototype = new Parent()  -> 还是回到原型链继承
    return new F();     // 这里像不像 原型链继承上面的 new Child();
}

var parent = new Parent();  // 获取父类实例

// 以上是原型式继承,以下给原型继承在套个壳子传递参数
function wrap(obj, name, role){
    var child = _extend(obj);   
    child.role = role;
    child.name = name;
    return child;
}
var child = wrap(parent, 'test', "child");
console.log(child.role)  // "test"
console.log(child.value) // "123"

var child1 = wrap(parent, "Bill", "child1");
console.log(child1.list) // [1,2,3]

child1.list.push(456);   
console.log(child1.list) // [1,2,3,456]
console.log(child.list)  // [1,2,3,456] 

看上面

  • 又回到了原型链继承的缺点之一了,对于父类引用类型的,一旦做了修改,子类的都会发生改变

寄生组合式继承

function Parent(role) {
    this.role = role;
    this.list = [1,2,3];
}

Parent.prototype.value = "123"

function Child(name, role) {
    Parent.call(this, role)  // 等于有了父类构造函数的副本
    this.name = name;
}

function _extend(obj) {
    function F(){};
    F.prototype = obj;  // F.prototype = new Parent()  -> 还是回到原型链继承
    return new F();     // 这里像不像 原型链继承上面的 new Child();
}

var extendFn = _extend(Parent.prototype);
Child.prototype = extendFn;

extendFn.constructor = Child;
var children = new Child("test", "child");

我们看下图
1591157016_1_.jpg

上面与下面其实大致一样,

function F(){};
F.prototype = Parent.prototype;
function Child() {
    Parent.call(this);
}
Child.prototype = new F();  // 是不是有点像组合模式
// 寄生组合模式理想版
function inheritPrototype(subType, superType){
    var prototype = Object.create(superType.prototype); //创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //指定对象
}
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);//实现继承
SubType.prototype.sayAge = function(){
    alert(this.age);
}