0 什么是继承

  继承就是获得存在对象已有的属性和方法的一种方式。

  【2019.4.26 更新】今日又重新学习了一下JS的继承,在这里整理一下以前的笔记并补充一些新的感悟。

1 JS中继承的几种实现方法

  • 属性拷贝

  • 原型式继承

  • 原型链继承

  • call/apply方法继承(借用构造函数)

  • 组合式继承:借用构造函数 + 原型式继承

  • 圣杯模式

  • 深拷贝(递归)

2 继承的具体实现

2-0 属性拷贝

【实现方法】

  遍历对象中的key进行赋值

【问题】

  继承过来的引用类型在父子对象中是共享的,即对其修改会同时影响父子对象中的值。

【示例代码】

 // 继承方式1:属性拷贝(遍历对象中的key进行赋值)
// 创建父对象
var superObj = {
name : 'Xianxian',
age : 20,
friends : ['Shuoshuo', 'Srue' , 'YanShuo' ],
showName : function(){
console.log(this.name);
}
}
// 创建子对象
var subObj = {}; for(var i in superObj){
subObj[i] = superObj[i];
} subObj.friends.push('MyLove');
console.log(superObj);
console.log(subObj,subObj.friends);

2-1 原型式继承

【实现方法】

  •   借用构造函数的原型对象继承 即子类.prototype = 父类.prototype

  •   子类构造函数的原型被覆盖,其构造函数指向父类,需要修正其值指向子构造函数:  

      SubClass.prototype.constructor = SubClass;

【存在问题】

  •  继承过来的引用类型在父子对象中是共享的,即对其修改会同时影响父子对象中的值。
  •  只能继承父构造函数原型对象上的成员,不能继承父构造函数实例对象上的成员。

【样例代码】

  // 继承方式2:原型式继承
// 创建父类构造函数
function SuperClass(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
}
} // 设置父类构造原型对象
SuperClass.prototype.age = 20;
SuperClass.prototype.friends = ['Shuoshuo','Yanshuo'];
SuperClass.prototype.showAge = function(){
console.log(this.age);
} // 创建空的子类构造函数
function SubClass(){ } // 借用构造函数的原型对象继承 即子类.prototype = 父类.prototype
SubClass.prototype = SuperClass.prototype;
// 此时,子类构造函数的原型被覆盖,其构造函数指向父类,需要修正其值指向子构造函数,验证如下:
console.log(SubClass.prototype.constructor == SuperClass); // true
console.log(SuperClass.prototype.constructor == SuperClass); // true
// 修改如下,之后即完成继承
SubClass.prototype.constructor = SubClass;
var child = new SubClass();
console.log(child.friends);
child.friends.push('Mylove');
child.age = 10;
var father = new SuperClass("ChrisChen");
// child中继承的只有显示表示出的prototype部分的属性值,父构造函数中的属性不会被继承
// 即只能继承父构造函数原型对象上的成员,不能继承父构造函数实例对象上的成员
console.log(child); //没有name,sayName()这些属性
father.showAge();
// 同样,该继承方式存在引用属性的成员共享问题
console.log(father.friends); // 会多出'MyLove'

2-2 原型链继承

【实现方式】

  •   子构造函数.prototype = new 父构造函数();

  •   同原型式继承,也要对子构造函数构造器进行修改(由于子构造器原型被覆盖),从而实现继承:

      SubClass1.prototype.constructor = SubClass;

【存在问题】

  继承过来的引用类型在父子对象中是共享的,即对其修改会同时影响父子对象中的值。

【总结】

  • 会将父构造函数实例对象中的属性继承,过多的继承了没用的属性,继承冗余

  • 不支持多继承,只能继承自通过一个父类

  • 创建子类实例时,不能向父类构造函数传参

【样例代码】

 // 继承方式3:原型链继承
// 创建父构造函数
function SuperClass1(){
this.name = 'ChenQixian';
this.age = 20;
this.sayName = function(){
console.log(this.name);
}
} // 设置父构造函数的原型
SuperClass1.prototype.friends = ['YanShuo' , 'Sure!'];
SuperClass1.prototype.showAge = function(){
console.log(this.age);
} // 创建子构造函数
function SubClass1(){ } // 原型链继承方式:子构造函数.prototype = new 父构造函数()
SubClass1.prototype = new SuperClass1();
// 同原型式继承,也要对子构造函数构造器进行修改(由于子构造器原型被覆盖),从而实现继承
SubClass1.prototype.constructor = SubClass;
// 不同于原型式继承,这里会将父构造函数实例对象中的属性也继承
var child = new SubClass1();
console.log(child.name); // ChenQixian
console.log(child.friends); // ["YanShuo", "Sure!"]
child.sayName(); // ChenQixian
child.showAge(); //
// 同样存在父子对象中引用属性的共享问题
var father = new SuperClass1();
console.log(father.friends); // ["YanShuo", "Sure!"]
child.friends.push('myLove');
console.log(father.friends); // ["YanShuo", "Sure!", "myLove"]
console.log(child.friends); // ["YanShuo", "Sure!", "myLove"]

2-3 借用构造函数

【实现方式】

  在子构造函数中,使用call,apply函数:区别在于apply传参数数组,call传参数列表,作用为改变this指向

  /*apply()方法*/

  function.apply(thisObj[, argArray])

  /*call()方法*/

  function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);

 
  调用call借用父构造函数改变this指向:
    SuperClass.call(this , args*);

【存在问题】

  • 不能借用原型

  • 每次构造多调一个函数,增加了函数调用

  • 子类的功能需求必须完全涵盖父类提供的属性和方法

【总结】

     可以给父构造函数传参,同时引用成员不存在共享问题。

【样例代码】

// 继承方式4:call(),apply()借用构造函数
// 创建父构造函数
function Person(name){
this.name = name;
this.friends = ['ShuoShuo','Sure'];
this.sayName = function(){
console.log(this.name);
}
}
// 创建子构造函数
function Student(name){
console.log(this);
// 调用call借用Person构造函数改变this指向
Person.call(this , name);
}
// 可以给父构造函数传参(这里传递了name),同时引用成员不存在共享问题
var stu = new Student('ChenQixian');
stu.sayName(); // ChenQixian
var pcs = new Person('Person');
console.log(pcs.friends); // ["ShuoShuo", "Sure"]
stu.friends.push('My_Love');
console.log(pcs.friends); // ["ShuoShuo", "Sure"]
console.log(stu.friends); // ["ShuoShuo", "Sure", "My_Love"]

2-4 组合继承

【实现方式】

  结合借用式继承和原型式继承的方式

  在子构造函数里调用call

  在设置原型继承,并调整构造器:

    SubClass.prototype = SuperClass.prototype;

    Subclass.prototype.constructor = SubClass;

【存在问题】

  仍存在引用成员共享问题。

【总结】

  •  继承了父构造函数原型对象上的成员以及父构造函数上的属性

  •   实现了向父构造对象的参数传递

【样例代码】

 // 继承方式5:借用构造函数 + 原型式继承
// 创建父构造函数
function Person1(name , age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
// 设置父构造函数的原型对象
Person1.prototype.showAge = function(){
console.log(this.age);
}
Person1.prototype.friends = ['ShuoYan','SureYan'];
// 创建子构造函数
function Student1(name , age){
Person1.call(this , name , age);
}
// 设置原型式继承
Student1.prototype = Person1.prototype;
Student1.prototype.constructor = Student1;
// 验证如下
var stu1 = new Student1('ChenQixian' , 19);
stu1.sayName(); // ChenQixian
stu1.showAge(); //
var pcs = new Person1('Person' , 21);
console.log(pcs.friends); // ["ShuoYan", "SureYan"]
stu.friends.push('My_Love');
console.log(pcs.age);
console.log(pcs.friends); // ["ShuoYan", "SureYan", "My_Love"]
console.log(stu.friends); // ["ShuoYan", "SureYan", "My_Love"]

2-5 圣杯模式(特别重要)

【特别重要】

  圣杯模式可被称作是JS继承的“标准答案”。

【总结】

 圣杯模式是在原型式继承的基础上进行改进的。

  主要解决的问题,是子类与父类共享原型链,导致在子类原型链上的修改也会导致父类原型链上相应属性值的改变。

  解决问题的方式为:引入一个中间层构造函数function F(){},使用F与父类共享原型,子类是由构造函数F构造的一个全新的对象。从而在拷贝了父类的同时,又彻底剥离了与父类的关系。

  作为补充,应该设置子类的构造函数指向子类来代替默认值以避免混乱,此外使用uber存储超类(即该子类继承自谁)。

【基本代码】

 function inherit(origin , target){
function F(){};
F.prototype = origin.prototype;
target.prototype = new F();
target.prototype.constructor = target;
target.prototype.uber = origin.prototype;
}

【测试代码】

 inherit(Father,Son);
function Father(){}
function Son(){}
Father.prototype.lastName = "Chen";
var father = new Father();
var son = new Son();
Son.prototype.firstName = "Xianxian";
console.log(son);
console.log(father);

【测试结果】

  测试结果表明,在子类中成功添加了firstName属性,而不影响父类的原型。

【改进代码】

  在雅虎时代,雅虎封装了YUI3库实现圣杯模式。现今,已经有jQuery代替YUI3。但我们仍需致敬经典,来了解一下,YUI3库中封装好的继承方法(形成闭包实现F的变量私有化)。

  这种继承方式必须熟练!

 var inherit = (function(){
function F(){}
return function(origin,target){
F.prototype = origin.prototype;
target.prototype = new F();
target.prototype.constructor = target;
target.prototype.uber = origin.prototype;
}
})();

3 深拷贝(深度克隆)

【实现思路】

  • 先把所有的值都遍历一遍(看是引用值和原始值)用 for ( var prop in obj )。

  • 注意这里for_in的用法:对象和数组的遍历都可以使用该方法。prop取得的是在对象中的key值,结果为string类型。

  • 判断是否为该对象的私有属性,不拷贝系统自带的对象属性。使用origin.hasOwnProperty(prop)方法
  • 判断是原始值,还是引用值?

    • 先判断是否为null
    • 用 typeof 判断是不是 object
      • 如果是原始值就直接拷贝
      • 如果是引用值,判断是数组还是对象
    • 判断是数组还是对象
      • 使用Object.prototype.toString.call()来判断
      • 结果为"[object Array]"则是数组否则为对
  • 数组则建立一个空数组[],否则建立空对象{}
  • 递归深拷贝

【实现代码】

 function deepClone(origin,target){
var target = target || {},// 容错,防止忘记传入target
toStr = Object.prototype.toString,
arrStr = '[object Array]';// 用于判断object是否为一个数组
for(var prop in origin){
if(origin.hasOwnProperty(prop)){// 首先判断是否为origin的私有属性,不是的话不克隆
if(origin[prop] !== "null" && typeof(origin[prop]) == 'object'){
// 先判断是否为null,再判断该位置应该是数组还是对象
target[prop] = (toStr.call(origin[prop]) == arrStr) ? [] : {};
// 递归调用深拷贝
deepClone(origin[prop],target[prop]);
}
else{
// 递归调用出口
target[prop] = origin[prop];
}
}
}
return target;
}

【测试代码】

var obj = {
name : "ChenQX",
age : 18,
card : ['visa','unionpay','master'],
son :{
name : "LiuCJ",
age : 18,
card : ['unionpay'],
grade :{
math : 59,
english :58,
history : 57
}
}
};
var obj1 = {};
deepClone(obj,obj1);
obj1['card'].push('ICBC');
console.log(obj);
console.log(obj1);

【测试结果】

  从测试结果中看出,深拷贝后的对象obj1与原对象obj一致,且对obj1中的引用属性进行修改不会对obj产生影响。

4 后记

  继承是面向对象编程OOP里的一个重要概念,JavaScript是一种面向对象语言,因此有必要对JS里的继承机制有一个深度的了解。

  深拷贝是一种重要的对象拷贝机制,可以拷贝原始值和引用值,同时引用值又不与源对象关联。

  继承里的圣杯模式是JS发展到现在为止最重要的一种继承机制,可以被称为是所有JS继承问题的“标准答案”。

JavaScript继承的几种实现的更多相关文章

  1. JS学习笔记——JavaScript继承的6种方法(原型链、借用构造函数、组合、原型式、寄生式、寄生组合式)

    JavaScript继承的6种方法 1,原型链继承 2,借用构造函数继承 3,组合继承(原型+借用构造) 4,原型式继承 5,寄生式继承 6,寄生组合式继承 1.原型链继承. <script t ...

  2. javascript继承的三种模式

    javascript继承一般有三种模式:组合继承,原型式继承和寄生式继承: 1组合继承:javascript最为广泛的继承方式通过原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承,同 ...

  3. js(javascript) 继承的5种实现方式

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt240 js继承有5种实现方式:1.继承第一种方式:对象冒充  functio ...

  4. 总结javascript继承的两种方式的N中写法

    最近翻看博客园,总结了一下javascript的继承方式:prototype和copy继承方式. 一.prototype方式 当一个函数被创建时,Function构造函数产生的函数会隐式的被赋予一个p ...

  5. javascript继承的几种方法

    继承是面向对象编程中很重要的概念,在其它面向对象的语言中大都很简单,例如java中有关键词extends来实现 javascript语言在ES6也新增了extends关键词可以实现继承,用法与java ...

  6. javascript继承的6种方法

    1原型式继承 简介:对类式继承的封装,过渡对象相当于子类. function inheritObject(o) { //声明过渡函数对象 function F() {} //过渡对象的原型继承父类 F ...

  7. javascript 继承的两种方式

    js中继承可以分为两种:对象冒充和原型链方式 一.对象冒充包括三种:临时属性方式.call()及apply()方式1.临时属性方式 代码如下: function Person(name){     t ...

  8. javascript继承有5种实现方式

    1.对象冒充 function Parent(username){ this.username = username; this.hello = function(){ alert(this.user ...

  9. javaScript继承的几种实现方式?

    js继承总共分成5种,包括构造函数式继承.原型链式继承.组合式继承.寄生式继承和寄生组合式继承. 构造函数式继承 首先来看第一种,构造函数式继承,顾名思义,也就是利用函数去实现继承:构造函数继承,使用 ...

随机推荐

  1. Android为TV端助力 eclipse build project 出现major.minor version 52.0的问题

    那些网上说的JDK什么的的问题,我求你们不要误人子弟好吗? 出现在这个的原因就是ADT也就是你的SDK manager 的Tools版本跟你的SDK版本不兼容,如果你的是SDK 23.0.2那你的To ...

  2. Numpy库的下载与安装总结

    今天在做Plotly的散点图时,需要Numpy 这个库的使用 没有安装Numpy这个库的时候,报错一般是下图这样:ModuleNotFoundError: No module named 'numpy ...

  3. swiper 自定义分页器的使用

    网上关于swiper 自定义分页器的方法比较多,但是已经不适合使用.它的API又比较坑爹,什么都是点到为止,不说清楚.因为要做一个产品颜色切换的效果,有黑与白两种颜色,因此尝试使用Swiper的自定义 ...

  4. [20190409]pre_page_sga=true与连接缓慢的问题.txt

    [20190409]pre_page_sga=true与连接缓慢的问题.txt --//曾经遇到11g下设置pre_page_sga=true启动缓慢的问题(没有使用hugepages).--//链接 ...

  5. logback日志配置

    第一步:加入jar包.要加入slf4j和logback的jar包,slf4j需要的jar包为slf4j-api,logback需要2个jar包(logback-classic.logback-core ...

  6. c/c++ 重载运算符 类型转换运算符

    重载运算符 类型转换运算符 问题:能不能把一个类型A的对象a,转换成另一个类型B的对象b呢?? 是可以的.这就必须要用类型A的类型转换运算符(conversion operator) 下面的opera ...

  7. Ubuntukylin 14.04 系统语言改成中文[转]

    1.在左侧点击"system setting" 2.按在图中方法设置 3.重启系统   参考地址:http://hi.baidu.com/thj2080/item/ae8e5dce ...

  8. python_库学习_01

    一.python的库学习之 财经数据接口包 1.安装ThShare 直接pip install tushare 可能会出现缺少依赖库的情况,依次安装,大概有lxml,pandas,bs4,reques ...

  9. 错误ERROR datanode.DataNode (DataXceiver.java:run(278)) - hadoop07:50010DataXceiver error processing unknown operation src:127.0.0.136479 dst:127.0.0.150010

    原因: Ambari 每分钟会向datanode发送"ping"连接一下去确保datanode是正常工作的.否则它会触发alert.但是datanode并没有处理空内容的逻辑,所以 ...

  10. 步步深入:MySQL架构总览->查询执行流程->SQL解析顺序(转)

    文章转自   http://www.cnblogs.com/annsshadow/p/5037667.html https://www.cnblogs.com/cuisi/p/7685893.html