2、面向对象编程/原型及原型链(手写call,apply,bind,new;创建对象的多种方式&优缺点;继承的多种方式&优缺点)

参数按值传递

参数分为基本类型与引用类型

  1. 基本类型值存储于栈内存中,传递的就是当前值,修改不会影响原有变量的值;
  2. 引用类型值其实也存于栈内存中(存的是实际内容的地址),它的值是指向堆内存;当改变时,改变了堆内存当中的实际值;
    栈存固定大小的值

手写call 和 apply

改变函数的this指向
call() :在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

let foo = {
    value: 1
    
};

function bar() {
    console.log(this.value);
}

bar.call(foo); // 1

等价于

//等价于
let foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};

foo.bar(); // 1
1. call 改变了 this 的指向,指向到 foo;
2. bar 函数执行了;

实现思路:
1、对象里添加一个属性函数
2、调用函数
3、删除函数

var obj = {
    value: 1
}
function bar(name, age) {
  
    return {
        value: this.value,
        name: name,
        age: age
    }
}
Function.prototype.myCall=function(context){
    var context=context || window;
    let arg= [...arguments].slice(1)  //splice会改变原数组,返回被移除的元素组成的数组。slice不会改变原数组
    // arguments.slice(1)
    context.fn=this
    let resObj=context.fn(...arg)
    delete context.fn
    return resObj
}

console.log(bar.call(obj, 'kevin', 18));
//标准写法,防止原型上有fn这个属性,被覆盖。用Symbol创建唯一的属性
Function.prototype.call2 = function(context, ...args) {
  // 判断是否是undefined和null
  if (typeof context === 'undefined' || context === null) {
    context = window
  }
  let fnSymbol = Symbol()
  context[fnSymbol] = this
  let fn = context[fnSymbol](...args)
  delete context[fnSymbol] 
  return fn
}

apply 的实现跟 call 类似,只是入参不一样,apply为数组

var obj = {
    value: 1
}
function bar(name, age) {
  
    return {
        value: this.value,
        name: name,
        age: age
    }
}
Function.prototype.myApply=function(context,arg){
    var context=context || window;
 
    // arguments.slice(1)
    context.fn=this
    let resObj
    if(arg&&arg instanceof Array &&arg.length){
      resObj=context.fn(...arg)
    }else{
      resObj=context.fn()
    }
    delete context.fn
    return resObj
}
// console.log(bar.apply(obj,'kevin'));
console.log(bar.myApply(obj,'kevin'));
//标准简化写法
Function.prototype.apply2 = function(context, args) {
  // 判断是否是undefined和null
  if (typeof context === 'undefined' || context === null) {
    context = window
  }
  let fnSymbol = Symbol()
  context[fnSymbol] = this
  let fn ;
  if(args&&args instanceof Array &&args.length){
      fn = context[fnSymbol](...args) 
  }else{
     fn = context[fnSymbol]()
  }
  
  delete context[fnSymbol] 
  return fn
}

手写 bind

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的this,之后的一序列参数将会在传递的实参前传入作为它的参数。
call,apply改变了函数的this指向,且直接执行了函数。bind只改变this指向,不执行函数,返回一个函数

var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

// 返回了一个函数
var bindFoo = bar.bind(foo); 

bindFoo(); // 1

思路:
1、返回的是一个函数
2、函数改变this指向

var foo = {
    value: 1
};

function bar(name,age) {
     this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
  // return{
  //   name:name,
  //   age:age,
  //   value:this.value
  // }
}
bar.prototype.friend = 'kevin';

// 返回了一个函数
var bindFoo = bar.bind(foo,'张三'); 


let res=new bindFoo(18); // 1
console.log(res);

// 1返回函数
// 2改变函数指向

Function.prototype.mybind=function(context,...args){
  if (typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  }
  var that =this;
  var fBound=function(...args2){
    let res;
    if( this instanceof fBound){
      res =that.apply(this,[...args,...args2])
    }else{
      res =that.apply(context,[...args,...args2])
    }
     
    return res
  }
     // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
 //  fBound.prototype = this.prototype; 这么赋值,修改fBound.prototype时,也会影响bar的prototype
  function fnp(){
  }
  fnp.prototype=this.prototype 
  fBound.prototype =new fnp()
  
  //fBound.prototype.name23='李四' //测试用
  return fBound

}

// var bindFoo = bar.mybind(foo,'张三'); 
// bindFoo(18); // 1
var bindFoo2 = bar.mybind(foo,'张三'); 
let res2=new bindFoo2(18); // 1
console.log(res2);

最终手写代码

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

手写模拟 new

思路:
var person=new Person()//person 实例,Person 构造函数,Person.prototype 原型
1、创建一个对象,对象拥有Person的内部属性
2、对象的__proto__等于Person的prototype,让对象可以访问Person的原型对象

因为 new 是关键字,所以无法像 bind 函数一样直接覆盖
所以我们写一个函数
命名为 objectFactory,来模拟 new 的效果
function Person () {
    ……
}

// 使用 new
var person = new Person(……);
// 使用 objectFactory
var person = objectFactory(Person, ……)
 function Person (name,age) {
   this.name=name
   this.age=age
}
Person.prototype.habit='sing'
var name='李四'
var age=18
// 使用 new
var person1 = new Person(name,age);
console.log('person1',person1)
function objectFactory(fn,...args){
     var obj=new Object() //创建对象
     fn.call(obj,...args) //改变构造函数this指向,指向对象,使对象拥有Person的内部属性,实现思路1
     obj.__proto__=fn.prototype //继承原型上的属性,实现思路2
     return obj
}
// 使用 objectFactory
var person2 = objectFactory(Person,name,age)
console.log('person2',person2)

假设构造函数有返回值

function Person (name, age) {
    this.strength = 60;
    this.age = age;

    return {
        name: name,
        habit: 'Games'
    }
}
Person.prototype.habit22='sing'
var person = new Person('Kevin', '18');

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined
console.log(person.habit22) // undefined

//或者返回的不是一个对象
function Person (name, age) {
    this.strength = 60;
    this.age = age;

    return 'handsome boy';
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18

可以看出,当构造函数有返回值的时候,分为是对象和不是对象
当返回值是对象时,this指针失效,包括Person.prototype上的值,当返回值不是对象的时候,跟原来保持一致
最终版本

function Person (name,age) {
    this.strength = 60;
    this.age = age;
    return {
        name: name,
        habit: 'Games'
    }
}
Person.prototype.habit2='sing'
var name='李四'
var age=18
// 使用 new
var person1 = new Person(name,age);
console.log('person1',person1)

function objectFactory(fn,...args){
  var obj=new Object() //创建对象
    //  fn.call(obj,...args) //改变构造函数this指向,指向对象,使对象拥有Person的内部属性,实现思路1
    let res=fn.call(obj,...args)//接收返回值
    // 判断res是不是对象,是对象,返回对象,不是对象返回原来的
     obj.__proto__=fn.prototype //继承原型上的属性,实现思路2
     return typeof res === 'object' ? res :obj //没有返回值的时候,res是undefined
}
// 使用 objectFactory
var person2 = objectFactory(Person,name,age)
console.log('person2',person2)

类数组对象和 arguments

所谓的类数组对象:
拥有一个 length 属性和若干索引属性的对象
总结:

  • 类数组可以按数组方式正常读写,获取长度,循环
  • 但是不能调用数组方法push,join,map等
  • 类数组能通过 Function.call间接调用数组的方法
  • 类数组可以转成数组
var array = ['name', 'age', 'sex'];

var arrayLike = {
    0: 'name',
    1: 'age',
    2: 'sex',
    length: 3
}
//读写
console.log(array[0]); // name
console.log(arrayLike[0]); // name

array[0] = 'new name';
arrayLike[0] = 'new name';
//获取长度
console.log(array.length); // 3
console.log(arrayLike.length); // 3
//遍历
for(var i = 0, len = array.length; i < len; i++) {

}
for(var i = 0, len = arrayLike.length; i < len; i++) {
 
}
//通过Function.call 间接调用
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
Array.prototype.join.call(arrayLike, '&'); // name&age&sex
Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] // slice可以做到类数组转数组

Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
}); 
// ["NAME", "AGE", "SEX"]

// 类数组转数组
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"] 
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"] 
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"] 
// 4. apply
Array.prototype.concat.apply([], arrayLike)

在函数体中,arguments 指代该函数的 Arguments 对象。Arguments是一个类数组,指代函数的所有实参
在这里插入图片描述

创建对象的多种方式&优缺点

  • 对象字面量(常用赋值)
  • 使用内置构造函数(基本不使用,用new object创建)
  • 工厂模式(不推荐使用)
  • 构造函数模式(每次创建调用一次方法)
  • 原型模式 (属性和方法都共享,不能初始化参数)
  • 组合模式 (最广泛的使用,该共享共享(通常是方法,不需要每次创建调用),该私有私有(通常是属性,不用被共享))
  • 寄生构造函数模式 、稳妥构造函数模式(了解概念即可,寄生构造函数模式比工厂模式多个外面的new,稳妥构造函数模式比工厂模式少个里面的new)

对象字面量

     var obj={
          name:"赵六",
          age:18,
     }

优点:简单;
缺点:只能创建一次对象,复用性较差,如果要创建多个对象,代码冗余度太高

使用内置构造函数

var obj=new Object();
         obj.name="赵六";
         obj.age=18;

优点:简单;
缺点:只能创建一次对象,复用性较差,如果要创建多个对象,代码冗余度太高

工厂模式

function createPerson(name) {
    var o = new Object();
    o.name = name;
    o.getName = function () {
        console.log(this.name);
    };

    return o;
}
var person1 = createPerson('kevin');

优点:简单;
缺点:对象无法识别,因为所有的实例都指向一个原型Object,没有person类的概念
个人理解:new出来的实例(除了构造函数返回新对象的new,不具有构造函数的类型,后续寄生构造函数模式会具体介绍),原型链上有Person和Object ,工厂模式只有Object ,所以构造函数可以分类,工厂不行
在这里插入图片描述

构造函数模式

function Person(name) {
    this.name = name;
    this.getName = function () {
        console.log(this.name);
    };
}

var person1 = new Person('kevin');
//person1 instanceof Person   true

优点:实例可以识别为一个特定的类型;对比工厂模式
缺点:每次创建实例时,每个方法都要被创建一次;
构造函数模式优化:

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

function getName() {
    console.log(this.name);
}

var person1 = new Person('kevin');
//

解决了每个方法都要重新创建的问题,不过每个实例上依然都挂着方法getName
在这里插入图片描述

原型模式

function Person(name) {

}

Person.prototype.name = 'xianzao';
Person.prototype.getName = function () {
    console.log(this.name);
};

var person1 = new Person();

优点:方法不会重新创建;(同样有特定的类型)
缺点:

  1. 所有的属性和方法都共享;
  2. 不能初始化参数;

组合模式

为了兼容原型模式属性和方法都共享,构造函数属性和方法都私有(即方法都创建一遍,每个实例上都有属性和方法),最好的办法是:属性在实例上,每次创建实例都有自己的属性值,方法在原型上(构造函数.prototype上),这就是组合模式

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

Person.prototype = {
    constructor: Person,
    getName: function () {
        console.log(this.name);
    }
};

var person1 = new Person();

优点:该共享的共享,该私有的私有,使用最广泛的方式;

寄生构造函数模式 、稳妥构造函数模式

寄生构造函数模式

  function SpecialArray() {
    var values = new Array();

    for (var i = 0, len = arguments.length; i < len; i++) {
        values.push(arguments[i]);
    }

    values.toPipedString = function () {
        return this.join("|");
    };
    return values;
}

var colors = new SpecialArray('red', 'blue', 'green');//寄生构造函数模式
var colors2 = SpecialArray('red2', 'blue2', 'green2'); //工厂模式
console.log(colors instanceof SpecialArray) // false

console.log(colors);
console.log(colors.toPipedString()); // red|blue|green

console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2

寄生构造函数模式对比工厂模式,创建对象实例时,寄生构造函数模式采用new操作符。不过这对于创建出的对象来说,没有任何差别。
对于两者的差别,js高级程序作者在书中是这么说的:

“除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数时返回的值。“
“它们又是用在什么样的情境下呢?”至于这个问题。书里也给出了明确的回答:“我们建议在可以使用其他模式的情况下,不要使用这种模式。”

根据作者的意思,构造函数和普通函数的区别在于:当使用new+构造函数创建对象时,如果构造函数内部没有return语句,那么默认情况下构造函数将返回一个该类型的实例,但如果构造函数内部通过return语句返回了一个其它类型的对象实例,那么这种默认的设置将被打破,构造函数最终返回的实例类型将以return语句中对象实例的类型为准。因为寄生构造函数模式的构造函数返回了一个对象,因此可以说工厂模式和寄生构造函数模式在功能上是等价的

如果非要说两者的不同,new Person()(寄生构造函数模式)更能感觉是正在创建一个对象,而不是在调用一个函数(工厂模式)。

稳妥构造函数模式

function Person(para_name, para_age, para_job) {
    //创建要返回的对象
    var o = {};

    //在这里定义私有属性和方法
    var name = para_name;
    var age = para_age;
    var job = para_job;

    var sayAge = function() {
        alert(age);
    };

    //在这里定义公共方法
    o.sayName = function() {
        alert(name);
    };

    //返回对象
    return o;
}

var person1 = Person("Nicholas", 29, "Software Engineer");    //创建对象实例
person1.sayName();    //Nicholas
person1.name;         //undefined
person1.sayAge();     // 报错

稳妥构造函数模式对比工厂模式,在函数内部不使用new来创建一个对象,使用对象字面量赋值,这种设计模式最适合在一些安全的环境中使用(这些环境中会禁止使用this和new)

继承的多种方式&优缺点

  • 原型链继承
  • 借用构造函数
  • 组合继承
  • 原型式继承
  • 寄生式继承
  • 寄生组合继承
  • ES6 Class继承(这里先不讲,以后es6一起总结)
    关系间的分类关系:
    在这里插入图片描述

前提:根据创建对象时候的优缺点,可知,我们提倡:将属性封装在构造函数中,将方法定义在原型对象上。

function A(name){
	this.name = name;	//实例基本属性(该属性,强调私有,不共享)
	this.arr = [1];		//实例引用属性(该属性,强调私用,不共享)
	this.say = function(){	//实例引用属性(该属性,强调复用,需要共享)
		console.log('hello');
	}
}
//数组和方法都属于’实例引用属性’,但是数组强调私有不共享,方法需要复用共享。

修正constructor指向的意义:任何一个prototype对象都有一个constructor属性,指向它的构造函数(它本身),更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。
若是不将子类的 constructor指回到子类, 此时的子类实例的.constructor则指向父类,因为有的类型判断可以用constructor(实例.constructor==子类,不指回,会显示false) 来判断创建者的,根据这个去走不同的逻辑。instanceof检查的是原型链

原型链继承

    function Person(name){
    this.name=name 
    this.arr = [1];
   }
   Person.prototype.say=function(){
     console.log(this.name,'这是Person的say')
     return this.name
   }
   Student.prototype=new Person('张三')

   function Student(age){
     this.age=age 
   }
   Student.prototype.eat=function(){
     console.log(this.name,this.age,'这是Student的eat')
   }
   const s1=new Student(18)
   const s2=new Student(20)

   s1.__proto__.name='李四'
   s1.arr.push(2);
   console.log(s1.arr,s2.arr);	//[1,2],[1,2]
   console.log(s1.name,s2.name);	//李四 李四

   console.log('s1',s1)
   console.log('s2',s2)

在这里插入图片描述

原型链继承思路:让子类的原型等于父类的实例,如下图,原型链的关键在于子类的原型等于父类的实例Student.prototype=new Person(),形成原型链。
缺点:
1、引用类型的属性被所有实例共享(属性还在父类上,子类只是在自己身上没有的时候向上访问),代码中s1的name添加了Lisi,打印的s2中name也添加了Lisi,实例基本属性通过__proto__修改也会被改变(改变了原型上的值),直接修改不会 s1.name=‘李四’(相当于实例s1上新建一个name属性)
2、无法传参,new Student不能传参赋值name
3、继承单一,无法实现多继承
在这里插入图片描述

借用构造函数

 function PersonGo(name){
    console.log('PersonGo')
    this.name=name //实例基本属性(该属性,强调私有,不共享)
    this.arr = [1];	//(该属性,强调私有)
    this.getNmae = function(){	//方法都在构造函数中定义,每次创建实例都会创建一遍方法。缺点1
		console.log('hello);
	}
   }
   PersonGo.prototype.say=function(){
     console.log(this.name,'这是PersonGo的say')
   }
   function StudentGo(age,name){
      PersonGo.call(this,name)//构造函数继承的核心,拷贝了父类构造函数里的实例属性和方法
      this.age=age
   }
   StudentGo.prototype.eat=function(){
     console.log(this.name,this.age,'这是StudentGo的eat')
   }
   const s3=new StudentGo(18,'张三')//优点1:可向父类构造函数传参
   const s4=new StudentGo(20,'李四')
   //优点2:不共享父类构造函数的引用属性
	s3.arr.push(2);
   console.log(s3.arr,s4.arr);	//[1,2],[1]
   console.log('s3',s3)
   console.log(s3.say)//undefined,获取不到父类原型上的方法,缺点2 
    console.log('s4',s4)

思路:借用父类构造函数来增强子类实例,等于是复制父类的实例属性给子类(属于子类自己的了,没有PersonGo这个类概念,只单纯调用了PersonGo()函数)
优点:
1、解决了共享变量问题
2、可以传参
3、可实现多继承(通过多个call或apply继承多个父类)
缺点:
1、方法都在构造函数中定义,每次创建实例都会创建一遍方法。
2、只继承了父类的构造函数(因为只执行了构造函数,原型链继承里的new会把原型上东西也继承(不懂的可以看上面的new模拟实现)),导致父类原型(PersonGo.prototype)上定义的方法和属性不能继承
在这里插入图片描述

组合继承

思路:保留原型链继承和构造函数的优点,解决原型链继承和构造函数的缺点
原型链的优点:可以访问原型上的对象和方法。构造函数的优点:可以传参,属性私有(引用类型也私有)

function PersonZu(name){
    console.log('PersonZu')
    this.name=name 
    this.arr = [1];
   }
   PersonZu.prototype.say=function(){
     console.log(this.name,'这是PersonZu的say')
   }
   function StudentZu(age,name){
      PersonZu.call(this,name)//核心,构造函数继承,拷贝一份到实例上,每次new StudentZu都会调用一次
      this.age=age
   }
 
   StudentZu.prototype =new PersonZu() //核心,原型链继承,继承原型上属性方法 ,属性方法还在原型上 只会调用一次,这里因为没传name值,所以下面缺点讲述里原型上的那个name是undefined
   // 若是不将StudentZu constructor指回到StudentZu, 此时的StudentZu实例zs5.constructor则指向PersonZu,
   //因为有的判断是使用constructor来判断创建者的,根据这个去走不同的逻辑,instanceof检查的是原型链
   StudentZu.prototype.constructor = StudentZu;//修正constructor指向


   StudentZu.prototype.eat=function(){
     console.log(this.name,this.age,'这是StudentZu的eat')
   }
   
   const s5=new StudentZu(18,'张三')
   const s6=new StudentZu(20,'李四')
   s5.arr.push(2)
   console.log('s5',s5)
//    console.log(s5.say,s5.eat)
   console.log('s6',s6)
//    console.log(s6.say,s6.eat)
  console.log(s5 instanceof StudentZu) //true
    console.log(s5 instanceof PersonZu)//true
  console.log(s5.constructor==PersonZu) //false
  console.log(s5.constructor==StudentZu) //true

在这里插入图片描述
优点:
1、原型对象上方法复用,即继承了原型链
2、属性实例化私有,不共享父类属性,用构造函数拷贝了一份属性在实例上
缺点:
1、调用了2次父类构造,一次用构造函数拷贝了一份属性在实例上,一次new的时候原型上又有一份,不影响使用,每次寻找是寻找最近的属性,但是造成了属性多余,删除实例属性的时候,会有问题,原型上还有。如上面的s5属性里有name,arr。PersonZu上也有一份

组合继承优化:
为了优化调用了2次父类构造,我们需要替换 StudentZu.prototype =new PersonZu() 的继承,但是同时也要PersonZu原型(也就是PersonZu.prototype)上的属性和方法。所以优化方案:将StudentZu.prototype =new PersonZu() 替换成StudentZu.prototype=PersonZu.prototype,其他不变。

缺点:修正构造函数的指向之后,父类实例的构造函数指向,同时也发生变化(PersonZu的构造函数也指向了StudentZu),因为赋值是浅拷贝,StudentZu.prototype复制的是PersonZu.prototype的地址,改变StudentZu.prototype.constructor也同步修改了PersonZu.prototype.constructor

寄生组合继承

完美的继承方案
组合继承优化里 将StudentZu.prototype =new PersonZu()替换成了 StudentZu.prototype=PersonZu.prototype,依然存在问题

寄生组合将StudentZu.prototype =new PersonZu()替换成StudentZu.prototype = Object.create(PersonZu.prototype)

StudentZu.prototype = Object.create(PersonZu.prototype);
StudentZu.prototype.constructor = StudentZu;
//其中,Object.create()函数等价为:
function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
}
//所以也可以等价于
function object(o) {
	    function F(){}
	    F.prototype = o;
	    return new F();
	}
	StudentZu.prototype = object(PersonZu.prototype);
	
	StudentZu.prototype.constructor = StudentZu;
//原理是
  function F(){}	//创建一个函数
  F.prototype=PersonZu.prototype,//这个函数继承了PersonZu.prototype,拥有了PersonZu原型的方法和属性,但是没有PersonZu构造函数的属性
  let f1=new F()//创建一个F的实例
  StudentZu.prototype=f1
 // 让StudentZu.prototype等于这个实例,因为改变实例的值,不会影响到原型上,也影响不了PersonZu.prototype.constructor
 
function PersonZu(name){
    // console.log('PersonZu')
    this.name=name 
    this.arr = [1];
   }
   PersonZu.prototype.say=function(){
     console.log(this.name,'这是PersonZu的say')
   }
   function StudentZu(age,name){
      PersonZu.call(this,name)//核心,构造函数继承,拷贝一份到实例上
      this.age=age
   }
 
  //  StudentZu.prototype =new PersonZu() //核心,原型链继承,继承原型上属性方法 ,属性方法还在原型上
  StudentZu.prototype = Object.create(PersonZu.prototype);

   StudentZu.prototype.constructor = StudentZu;

   
   const s5=new StudentZu(18,'张三')
   const s6=new StudentZu(20,'李四')
   const p1=new PersonZu('小爸爸');

   console.log(s5.constructor);	//修复之后:Child,Parent
   console.log(p1.constructor);	//修复之后:Child,Parent

原型继承

缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

function createObj(o) {
    function Fn(){}
   Fn.prototype = o;
    return new Fn();
}
var person = {
    name: 'kevin',
    friends: ['daisy', 'kelly']
}

var person1 = createObj(person); //对比创建对象里的工厂模式,这里的原型继承,没有person概念,打印person1,会发现他属于Fn类,对属性的修改读写,还是对Fn的原型上去修改,除了没有类的概念,缺点上跟原型链一致,可以理解为跳过person,挂在了Object上
// console.log(person1 instanceof Fn)// Fn is not defined 但是由于Fn在createObj函数内部,所以他也用不了instanceof去判断是什么类,没有类的概念.
var person2 = createObj(person);

console.log(person1); 
console.log(person2); 

person1.name = 'person1';
console.log(person2.name); // kevin 没改变

person1.__proto__.age = 20;
console.log(person2.age); // kevin 跟着变了

person1.friends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"] 跟着变了

在这里插入图片描述

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
比原型继承多一个增强功能(可以添加属性和方法)
执行结果跟原型继承一样,只是多了个方法
这种继承只提供一个思路,没什么优点,缺点同原型继承

function createObj (o) {
    var clone = Object.create(o);//
    clone.sayName = function () {
        console.log('hi');
    }
    return clone;
}
//其中,Object.create()函数等价为:
function object(o) {
    function F(){}
    F.prototype = o;
    return new F();
}
//寄生继承
function createObj(o) {
    function Fn(){}
    Fn.prototype = o;
    return new Fn();
}
function createAnother(original){ 
    var clone=createObj(original);    //通过调用函数创建一个新对象
    clone.sayHi = function(){      //以某种方式来增强这个对象
        alert("hi");
    };
    return clone;                  //返回这个对象
}
var person = {
    name: 'kevin',
    age:18,
    friends: ['daisy', 'kelly']
}

var person1 = createAnother(person); 
// console.log(person1 instanceof Fn)// Fn is not defined
var person2 = createAnother(person);

console.log(person1); 
console.log(person2); 

person1.name = 'person1';
console.log(person2.name); // kevin 没改变

person1.__proto__.age = 20;
console.log(person2.age); // kevin 跟着变了

person1.friends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"] 跟着变了

在这里插入图片描述

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值