## JS实现继承的6种方式
JavaScript实现继承的6种方式
**1 、原型链继承**
将构造函数的原型设置为另一个构造函数的实例对象,这样就可以继承另一个原型对象的所有属性和方法,可以继续往上,最终形成原型链。
第一个问题是,当实现继承后,另一个原型的实例属性,变成了现在这个原型的原型属性,然后该原型的引用类型属性会被所有的实例共享,这样继承原型引用类型属性的实例之间不再具有自己的独特性了。
第二个问题是,在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数。
**2、借用构造函数继承**
为了解决原型中包含引用类型值的问题,开始使用借用构造函数,也叫伪造对象或经典继承
~~~
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
//继承SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
~~~
将SuperType函数在SubType构造函数中调用,在每个实例中执行,这样每个实例中都会有一份SuperType中的属性方法的副本,也就实现了继承SuperType。
这种模式的优势就是可以在子类型构造函数中向超类型构造函数传递参数。
存在的问题就是,所有的类型都只能使用构造函数模式(因为超类型的原型中定义的方法对于子类型不可见),因此方法都在构造函数中定义,函数复用就无从谈起了。
**3、组合继承**
也叫伪经典继承,将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。
~~~
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;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
~~~
将SubType的原型指定为SuperType的一个实例,大致步骤和原型链继承类似,只是多了在SubType中借调SuperType的过程。
实例属性定义在构造函数中,而方法则定义在构造函数的新原型中,同时将新原型的constructor指向构造函数。
可以通过instanceof和isPrototypeOf()来识别基于组合继承创建的对象。
避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JS中最常用的继承模式。
实际上是借用了构造函数,以覆盖的方式,解决了在原型链继承中原型的引用类型属性共享在所有实例中的问题。
因为在子类型中借调构造函数(SuperType.call(this))时,会在自己的所有实例中执行一遍SuperType中的代码,由于每个实例this都是不同的,因此SuperType中定义的属性会在每个实例中有一份副本,也就避免了原型链继承中,原型属性共享的问题(覆盖了原型属性)。
**4.、原型式继承**
不自定义类型的情况下,临时创建一个构造函数,借助已有的对象作为临时构造函数的原型,然后在此基础实例化对象,并返回。
~~~
function object(o){
function F(){}
F.prototype = o;
return new F();
~~~
本质上是object()对传入其中的对象执行了一次浅复制
~~~
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
~~~
原型的引用类型属性会在各实例之间共享。
当只想单纯地让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。
注意:
ES5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与这里的object()方法的行为相同。第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。
**5、寄生式继承**
其实就是在原型式继承得到对象的基础上,在内部再以某种方式来增强对象,然后返回。
~~~
function createAnother(original) {
var clone = object(original);
clone.sayHi = function() {
alert("hi");
};
return clone;
}
~~~
思路与寄生构造函数和工厂模式类似。
新的对象中不仅具有original的所有属性和方法,而且还有自己的sayHi()方法。
寄生式继承在主要考虑对象而不是自定义类型和构造函数的情况下非常有用。
由于寄生式继承为对象添加函数不能做到函数复用,因此效率降低。
**6、寄生组合式继承**
组合继承是JS中最常用的继承模式,但其实它也有不足,组合继承无论什么情况下都会调用两次超类型的构造函数,并且创建的每个实例中都要屏蔽超类型对象的所有实例属性。
寄生组合式继承就解决了上述问题,被认为是最理想的继承范式。
~~~
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(superType, subType) {
var prototype = object(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(SuperType, SubType); // 这一句,替代了组合继承中的SubType.prototype = new SuperType()
SubType.prototype.sayAge = function() {
alert(this.age);
};
~~~
既然在组合模式中我们通过借调构造函数来为每个实例定义实例属性,从而覆盖原型属性,影响了效率,那么是否可以把原型改变一下呢,不让它作为SuperType的实例,这样就不会有一些无用的原型属性了。
不必为了指定子类型的原型而调用超类型的构造函数,我们需要的只不过是超类型原型的一个副本。
在inheritPrototype()函数中所做的事:
在inheritPrototype函数中用到了原型式继承中的object()方法,将超类型的原型指定为一个临时的空构造函数的原型,并返回构造函数的实例。
此时由于构造函数内部为空(不像SuperType里面有实例属性),所以返回的实例也不会自带实例属性,这很重要!因为后面用它作为SubType的原型时,就不会产生无用的原型属性了,借调构造函数也就不用进行所谓的“重写”了。
然后为这个对象重新指定constructor为SubType,并将其赋值给SubType的原型。这样,就达到了将超类型构造函数的实例作为子类型原型的目的,同时没有一些从SuperType继承过来的无用原型属性。
- HTML & CSS
- 【01】CSS3有哪些新特性
- 【02】HTML5有哪些新特性
- 【03】link和import的区别
- 【04】清除浮动的方法有哪些?
- 【05】谈谈对BFC的理解
- 【06】如何去掉inline-block元素之间的间隙
- 【07】垂直水平居中的方法有几种?
- 【08】行内元素有哪些?块级元素有哪些?空(void)元素有哪些?
- 【09】less和sass的特点和区别
- 【10】css选择器有那些?CSS3新增了哪些
- 【11】rgba和opacity的透明何不同?
- 【12】box-sizing的属性和作用
- 【13】请列举5个html5/HTML5输入类型?并简要描述其用途?
- 【14】页面有哪三层构成是什么?作用是什么?
- 【15】margin-top父元素塌陷及解决办法
- 【16】手机端怎么适配?
- 【17】如何让英文小写转换为大写
- 【18】精灵图和base64 如何选择,或者字体图标什么时候使用?
- 【19】position定位有几种?有什么区别?哪几个是脱离文档流的?
- 【20】div固定宽度文字超出显示省略号的方法
- 【21】如何解决input和select的宽高不一致问题
- 【22】请书写css 万能浮动清除法
- 【23】HTML5里的video标签支持哪些视频格式?
- 【24】请书写透明度opacity的IE兼容写法
- 【25】如何解决div与内部img间隙问题
- 【26】input设置为readonly和disable有什么区别?
- 【27】谈谈对BFC的理解
- 【28】什么是外边距重叠?以及防止方法
- 【29】html常见兼容性问题
- 【30】Doctype的作用?严格模式与混杂模式的区别?
- 【31】display属性有哪些?
- 【32】CSS样式覆盖规则
- 【33】手机端怎么适配?
- 【34】opacity/visibility/display 隐藏对比
- 【35】JS中使用typeof能得到的哪些类型?
- JS基础
- 【01】ES6新特性有哪些?
- 【02】new操作符具体干了什么?
- 【03】谈谈你对原型链的理解
- 【04】DOM怎样添加、移除、移动、复制、创建和查找节点
- 【05】import和require的区别
- 【06】export与export default 的区别
- 【07】$(document).ready和window.onload的区别?
- 【08】判断数据类型的方法有哪些,有什么区别
- 【09】cookies、sessionStorage、localStrorage的区别?
- 【10】谈一下你对面向对象的理解?
- 【11】闭包是什么?有什么特性和优缺点?
- 【12】this一般的指向,以及如何改变指向
- 【13】同步和异步的区别?
- 【14】ajax的优点和缺点
- 【15】JS是单线程还是多线程?为什么?
- 【16】CommonJS,AMD,CMD区别
- 【17】简述ajax请求过程,以及注意事项。
- 【18】ajax请求的时候get 和post方式的区别
- 【19】控制台undefined和is not defined异常区别?
- 【20】正则验证,match()与test()函数的区别?
- 【21】数组的常用方法有那些?
- 【22】例举3种强制类型转换和2种隐式类型转换
- 【23】列举IE和标准下有哪些JS兼容性的写法
- 【24】请你谈谈cookie的弊端?
- 【25】window对象常用的属性和方法有那些
- 【26】函数声明和函数表达式的区别?
- 【27】怎么判断一个object是否是数组(array)?
- 【28】JS实现继承的6种方式
- 【29】什么是事件委派以及优缺点
- 【30】window对象常用的属性和方法有那些
- 【31】简述“==”与“===”的区别?
- 【32】javascript各类型在转换为boolean式的规则?
- 【33】Object.creat()和new对象有什么区别?
- 【34】Object.assign()函数的作用及用法
- 【35】Void(0)的作用是什么?
- 【36】JS 的基本类型 和 引用类型、数据类型、内置对象
- 【37】bind、call、apply的区别
- 【38】节流和防抖
- 【39】重绘和回流
- 【40】defer和async的区别
- 【41】var、let、const之间的区别
- 【42】说说你对作用域链的理解
- 【43】map与forEach的区别?
- 【44】日期和随机数
- VUE
- 【01】Vue的生命周期有哪些,第一次加载会触发哪些钩子函数
- 【02】Vue 为什么采用Virtual(虚拟) DOM?
- 【03】Vue导航守卫的钩子函数有哪些?
- 【04】Vue组件通信 父子、兄弟、非父子
- 【05】如何解决Vue 数组/对象更新 视图不更新的情况
- 【06】Vue编程式的导航跳转传参的方式有哪些?
- 【07】Vue实现数据双向绑定的原理是什么
- 【08】Vuex是什么?怎么使用?哪种功能场景使用它?
- 【09】Vue中的$.nextTick的理解
- 【10】vue的路由hash模式 和 history模式 区别
- 【11】v-if与v-show区别
- 【12】mvc和mvvm模式区别
- 【13】react和vue有哪些不同,说说你对这两个框架的看法
- 【14】<keep-alive></keep-alive>的作用是什么?
- 【15】v-for 为什么使用key?
- 【16】说出至少vue 5个常用指令?
- 【17】说出至少vue 3个常用事件修饰符?
- 【18】Vue中 watch computed的区别
- 【19】Vue实现数据双向绑定的原理
- 【20】Vue等单页面应用及其优缺点
- 微信小程序
- 【01】小程序的生命周期及其区别
- 【02】微信小程序主包和分包区别
- 【03】小程序解析富文本有哪些方式,以及区别
- 【04】简述小程序几种页面跳转方式的区别
- 【05】小程序的 wxss 和 css 有哪些不一样的地方?
- 【06】微信小程序与vue区别
- 【07】bindtap和catchtap的区别是什么
- 【08】小程序页面间有哪些传递数据的方法
- 【09】简述下 wx.navigateTo(), wx.redirectTo(), wx.switchTab(), wx.navigateBack(), wx.reLaunch()的区别
- 【10】微信小程序的优劣势?
- jQuery
- 【01】jQuery如何实现事件委托?
- web综合
- 【01】less和sass的特点和区别
- 【02】你如何优化自己的代码?
- 【03】Svn与Git的区别
- 【04】http请求方式有几种?分别是什么?
- 【05】一般情况下,端口号的取值范围是多少?
- 【06】浏览器缓存有哪些,通常缓存有哪几种
- 【07】请解释JSONP的工作原理,以及它为什么不是真正的AJAX
- 【08】对nodejs的理解?
- 【09】Node.js的优点和缺点
- 【10】线程与进程的区别是什么?
- 【11】什么是WEB标准?什么是W3C标准?
- 【12】浏览器是如何渲染页面的
- 【13】从输入url到显示页面,都经历了什么
- 【14】谈谈垃圾回收机制方式及内存管理
- 【15】http 和 https 有何区别?如何灵活使用?
- 【16】xml和json的区别
- 【17】如何防止XSS攻击?
- 【18】web页面重构怎么操作?
- 【19】你所了解的Web攻击技术
- 【20】简述http常见的状态码及含义
- 【21】webpack 和 gulp 的区别
- 【22】Git和SVN之间的区别
- 【23】MongoDB 与 MySQL的区别
- 【24】你所了解到的Web攻击技术
- 手写代码
- 【01】请计算一个对象的长度
- 【02】请手写冒泡排序
- 【03】请分别用ES5和ES6实现数组去重
- 【04】把一个div变成圆左边蓝色 右边绿色
- 【05】计算1-10000中出现的0 的次数
- 【06】写一个兼容的function,清除字符串前后的空格
- 【07】请对一个数组的数组从小到大进行排序
- 【08】以下代码输出什么[空函数返回值]
- 【09】下面代码输出什么?[各种类型拼接]
- 【10】看下面代码,给出输出结果[计时器]
- 【11】如何获取三个数中的最大值和最小值?
- 【12】求数组中的最大值
- 【13】求数组中的最小值
- 【14】获得每个月1号是星期几?
- 【15】将字符串转化为驼峰表示法
- 【16】以下代码的alert的值分别是多少?
- 逻辑题
- 【01】找出最重的球