说在前面:
为了使代码更为简洁方便理解, 本文中的代码均将“非核心实现”部分的代码移出。


一、原型链方式
关于原型链,可点击《深入浅出,JS原型链的工作原理》,本文不再重复叙述。

思路:让子构造函数的原型等于父构造函数的实例


function A() {
}
A.prototype.fn = function (){
console.log("in A");
} function B() {
}
B.prototype = new A(); // 让子构造函数的原型等于父构造函数的实例 var b = new B();
b.fn(); // in A
console.log(b instanceof B); // true
console.log(b instanceof A); // true
console.log(b instanceof Object); // true

缺陷:如果父构造函数中的属性为引用类型,则子构造函数的实例会出现相互影响的情况;


function A() {
this.prop = ['1',"2"];
}
A.prototype.fn = function (){
console.log(this.prop);
} function B() {
}
B.prototype = new A(); var b1 = new B();
var b2 = new B();
b1.fn(); //  ["1", "2"]
b2.fn(); //  ["1", "2"] b1.prop.push('3'); // 子构造函数实例b1修改继承过来的属性
b2.prop.push('4'); // 子构造函数实例b2修改继承过来的属性 b1.fn(); // ["1", "2", "3", "4"] // b2上的修改影响了b1
b2.fn(); // ["1", "2", "3", "4"] // b1上的修改影响了b2

*导致缺陷原因:引用类型,属性变量保存的是地址指针而非实际的值,这个指针指向了一块用来保存实际内容的地址。实例化后,所有实例中变量保存了同一个指针,均指向同一个地址,当任何一个实例通过指针修改地址的内容(并非重新赋予新的指针地址或者修改指针指向)时,其他实例的也会受到影响。


二、借用构造函数方式
为了解决“原型链方式”继承的缺陷,引入的一种“继承”方案。

思路:通过call/apply,在子构造函数中调用父类的构造函数


function A() {
this.prop = ['1',"2"]; this.fn2 = function () {
console.log(this.prop);
}
}
A.prototype.fn = function (){
console.log(this.prop);
} function B() {
A.call(this); // 通过call/apply,在子构造函数中调用父类的构造函数
} var b1 = new B();
var b2 = new B();
b1.fn2(); // ["1", "2"]
b2.fn2(); // ["1", "2"] b1.prop.push('3');
b2.prop.push('4'); b1.fn2(); // ["1", "2", "3"]
b2.fn2(); // ["1", "2", "4"] b1.fn(); // 提示异常:b1.fn is not a function
console.log(b1 instanceof B); // true
console.log(b1 instanceof A); // false
console.log(b1 instanceof Object); // true

缺陷:由于“继承”过程中,A仅充当普通函数被调用,使得父构造函数A原型无法与形成子构造函数B构成原形链关系。因此无法形成继承关系:"b1 instanceof A"结果为false,B的实例b1亦无法调用A原型中的方法。实际意义上,这种不属于继承。


三、组合继承
结合“原型链方式”和“借用构造函数方式”的有点,进行改进的一种继承方式。

思路:原型上的属性和方法通过“原型链方式”继承;父构造函数内的属性和方法通过“借用构造函数方式”继承


function A() {
this.prop = ['1',"2"];
}
A.prototype.fn = function (){
console.log(this.prop);
} function B() {
A.call(this); // 借用构造函数方式
}
B.prototype = new A(); // 原型链方式 var b1 = new B();
var b2 = new B();
b1.fn(); // ["1", "2"]
b2.fn(); // ["1", "2"] b1.prop.push('3');
b2.prop.push('4'); b1.fn(); // ["1", "2", "3"]
b2.fn(); // ["1", "2", "4"]
console.log(b1 instanceof B); // true
console.log(b1 instanceof A); // true
console.log(b1 instanceof Object); // true

缺陷:子构造函数的原型出现一套冗余“父构造函数非原型上的属性和方法”。上述代码在执行“A.call(this);”时候,会给this(即将从B返回给b1赋值的对象)添加一个“prop”属性;在执行“B.prototype = new A();”时,又会通过实例化的形式给B的原型赋值一次“prop”属性。显然,由于实例属性方法的优先级高于原型上的属性方法,绝大多数情况下,原型上的“prop”是不会被访问到的。


四、寄生组合式继承
为了解决“组合继承”中子构造函数的原型链出现冗余的属性和方法,引入的一种继承方式。

思路:在组合继承的基础上,通过Object.create的方式实现原型链方式


function A() {
this.prop = ['1',"2"];
}
A.prototype.fn = function (){
console.log(this.prop);
} function B() {
A.call(this);
}
B.prototype = Object.create(A.prototype); // Object.create的方式实现原型链方式 var b1 = new B();
var b2 = new B();
b1.fn(); // ["1", "2"]
b2.fn(); // ["1", "2"] b1.prop.push('3');
b2.prop.push('4'); b1.fn(); // ["1", "2", "3"]
b2.fn(); // ["1", "2", "4"] console.log(b1 instanceof B); // true
console.log(b1 instanceof A); // true
console.log(b1 instanceof Object); // true

最后补充
1、因为子构造函数的实例自身没有constructor属性,当我们访问实例的constructor属性时,实际是访问原型的constructor属性,该属性应该指向(子)构造函数。到那时上述例子中的代码均会指向父构造函数。为了与ECMAScript规范保持一致,在所有的“原型链继承”后,应当将原型的constructor属性指向子构造函数本身:


B.prototype = ....
--> B.prototype.constructor = B; <--
...

2、Object.create是ECMAScript 5中加入的一个函数,这个函数的功能是:将入参(需为一个对象)作为原型,创建并返回一个新的(只有原型的)的对象。此功能等价于:


function object(o){
function F(){}
F. prototype = o;
return new F();
} // 来源于《JavaScript高级程序设计(第3版)》

来源:https://segmentfault.com/a/1190000017522714

JS中继承方式总结的更多相关文章

  1. js 中继承方式小谈

    题外话 前段时间面试中笔试题有这道题目: 请实现一个继承链,要求如下: 构造函数A():构造函数中有consoleA方法,可以实现console.log("a") 实例对象 a:a ...

  2. js各种继承方式和优缺点的介绍

    js各种继承方式和优缺点的介绍 作者: default 参考网址2 写在前面 本文讲解JavaScript各种继承方式和优缺点. 注意: 跟<JavaScript深入之创建对象>一样,更像 ...

  3. javascript中继承方式及优缺点(三)

    文以<JavaScript高级程序设计>上的内容为骨架,补充了ES6 Class的相关内容,从我认为更容易理解的角度将继承这件事叙述出来,希望大家能有所收获. 1. 继承分类 先来个整体印 ...

  4. js中继承的几种用法总结(apply,call,prototype)

    一,js中对象继承 js中有三种继承方式 1.js原型(prototype)实现继承 <SPAN style="BACKGROUND-COLOR: #ffffff">& ...

  5. js中继承的方法总结(apply,call,prototype)

    一,js中对象继承 js中有三种继承方式 1.js原型(prototype)实现继承 代码如下: <SPAN style="<SPAN style="FONT-SIZE ...

  6. js各种继承方式汇总

    js中的各种继承实现汇总 首先定义一个父类: function Animal(name) { this.name = name || '动物' this.sleep = function () { c ...

  7. 谈谈JavaScript中继承方式

    聊一聊js中的继承 一.简单继承---使用原型赋值的方式继承,将实例化的对象,赋值给子级的原型 父级构造函数 function Parent(param) { this.name = 'parent' ...

  8. js的继承方式分别适合哪些应用场景?

    一.原型链 利用 Person.prototype = new Animal("Human") 实现继承: static式继承.能继承Animal.prototype.不可多重继承 ...

  9. 浅谈js中继承的理解和实现

    一.前言 java.C#等正统面向对象语言都会提供类似extend之类的处理类的继承的方法,而javascript并没有提供专门的方法用于继承,在javascript中使用继承需要一点技巧.js中实例 ...

随机推荐

  1. 下载SCI论文

    今天帮汪博下了一下午SCI论文,见到了很多不知道的网站.顺便了解了一下: 首先进学校图书馆的网站,然后选择    WOS核心合集(含SCIE.SSCI.A&HCI.CPCI数据库) ---&g ...

  2. Apache OFBIZ高速上手(三)--文件夹&amp;&amp;配置文件介绍

    1.OFBiz简单介绍,什么是OFBiz           OFBiz is an Apache Software Foundation top level project.           A ...

  3. k8s学习(二)——etcdctl工具的使用

    k8s的实现核心实际上就是通过读写etcd数据库实现对资源的存储,管理和控制. k8s所有资源的本源都是存储在etcd中的一个个键值对. 理论上可以观察到etcd数据库中的数据变化.具体的使用方式如下 ...

  4. vue sync

    1.使用vue cli建立工程 2.在APP.vue中: <template> <div class="details"> <myComponent ...

  5. 转 FreeBSD通过PORTS安装软件的几个常用命令

    1.怎样找到我想安装的包路径:# cd /usr/ports# make search name=mysql2.仅仅下载源码包,而不安装:# cd /usr/ports/directory# make ...

  6. Realm多线程中的那些坑...

    个人在开发中遇到的一些小坑... 可能会持续更新... 1.RealmObject自带线程保护功能.仅仅能在创建它的线程中訪问.在子线程中不能訪问. 也就是说.假设你在主线程中new了一个RealmO ...

  7. ArcMap中使用ArcPy实现Geometry与WKT的相互转换

    在Web GIS迅猛发展的今天,使用浏览器来进行交互以其方便性.快捷性被广大用户所推崇,那么在传输格式方面,都已比較简单的JSON或者WKT来解决网络带宽带来的数据压力. 在ArcGIS10.2版本号 ...

  8. 说说我的web前端之路,分享些前端的好书(转)

    WEB前端研发工程师,在国内算是一个朝阳职业,这个领域没有学校的正规教育,大多数人都是靠自己自学成才.本文主要介绍自己从事web开发以来(从大二至今)看过的书籍和自己的成长过程,目的是给想了解Java ...

  9. SpringMvc自动代理

    自动配置的好处是不需要挨个 实现[org.springframework.aop.framework.ProxyFactoryBean] ,只需要 advisor 配置和 <bean id=&q ...

  10. 规范-Git打标签与版本控制

    Git打标签与版本控制规范 前言 本文适用于使用Git做VCS(版本控制系统)的场景. 用过Git的程序猿,都喜欢其分布式架构带来的commit快感.不用像使用SVN这种集中式版本管理系统,每一次提交 ...