JavaScript继承的实现方式:原型语言对象继承对象原理剖析
面向对象编程:继承、封装、多态。
对象的继承:A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
在经典的面向对象语言中,您可能倾向于定义类对象,然后您可以简单地定义哪些类继承哪些类(参考C++ inheritance里的一些简单的例子),JavaScript使用了另一套实现方式,继承的对象函数并不是通过复制而来,而是通过原型链继承
回顾《再谈javascriptjs原型与原型链及继承相关问题》
什么是原型语言
只有对象,没有类;对象继承对象,而不是类继承类。
“原型对象”是核心概念。原型对象是新对象的模板,它将自身的属性共享给新对象。一个对象不但可以享有自己创建时和运行时定义的属性,而且可以享有原型对象的属性。
每一个对象都有自己的原型对象,所有对象构成一个树状的层级系统。root节点的顶层对象是一个语言原生的对象,只有它没有原型对象,其他所有对象都直接或间接继承它的属性。
原型语言创建有两个步骤
使用”原型对象”作为”模板”生成新对象 :这个步骤是必要的,这是每个对象出生的唯一方式。以原型为模板创建对象,这也是”原型”(prototype)的原意。
初始化内部属性 :这一步骤不是必要的。通俗点说,就是,对”复制品”不满意,我们可以”再加工”,使之获得不同于”模板”的”个性”。
所以在JavaScript的世界里,万物皆对象这个概念从一而终。
JavaScript里面没有类这个概念,es6中class虽然很像类,但实际上只是es5上语法糖而已
function People(name){
//属性
this.name = name || Annie
//实例方法
this.sleep=function(){
console.log(this.name + '正在睡觉')
}
}
//原型方法
People.prototype.eat = function(food){
console.log(this.name + '正在吃:' + food);
}
JavaScript的基础方式,首推的就是原型继承
原型链继承
父类的实例作为子类的原型
function Woman(){
this.name= "SubType"; // 子类属性
}
// 如果此处有Woman的原型对象上的方法,由于原型重定向,下面的代码会覆盖此方法
Woman.prototype= new People();// 重写原型对象,代之以一个新类型的实例
// 这里实例化一个 People时, 实际上执行了两步
// 1,新创建的对象复制了父类构造函数内的所有属性及方法
// 2,并将原型 __proto__ 指向了父类的原型对象
Woman.prototype.name = 'haixia';//子原型的属性
Woman.prototype.name = ()=>{};//子原型方法
let womanObj = new Woman();
原型链继承优点:
父类新增原型方法/原型属性,子类都能访问到
非常纯粹的继承关系,实例是子类的实例,也是父类的实例
简单易于实现
原型链继承缺点:
可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面
无法实现多继承
来自原型对象的所有属性被所有实例共享,子类可以重写父类原型上的方法
创建子类实例时,不能向父类构造函数中传参数
解释原型重定向: Woman.prototype= new People();
自己开辟的堆内存中没有constructor属性,导致类的原型构造函数缺失(解决:自己手动在堆内存中增加constructor属性)
当原型重定向后,浏览器默认开辟的那个原型堆内存会被释放掉,如果之前已经存储了一些方法或者属性,这些东西都会丢失(所以:内置类的原型不允许重定向到自己开辟的堆内存,因为内置类原型上自带很多属性方法,重定向后都没了,这样是不被允许的)
原型继承经典笔试题
function Parent () {
this.a = 1;
this.b = [1, 2, this.a];
this.c = {demo: 5};
this.show = function () {
console.log(this.a + ' ' + this.c.demo + ':' + this.b + '\n');
};
}
function Child () {
this.a = 2;
this.change = function () {
this.b.push(this.a);
this.a = this.b.length;
this.c.demo = this.a++;
};
}
Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
child1.change();
child2.change();
parent.show();
child1.show();
child2.show();
思考原型公用问题
经典继承
function extendObj(obj) {
if (Object.create) {
return Object.create(obj)
} else {
function F () { }
F.prototype = obj;
return new F()
}
}
var obj = { name: "smd", age: 26, sayHi: function () { } }
var newObj = createObj(obj)
/* Extend Function */
function extend(subClass,superClass){
var Func = function(){} ;
Func.prototype = superClass.prototype ;
subClass.prototype = new Func() ;
subClass.prototype.constructor = subClass ;
} ;
newObj继承了obj的属性和方法,但是同样出现了共享父类中引用类型属性的问题
实例继承(原型式继承)
function Wonman(name){
let instance = new People();
instance.name = name || 'wangxiaoxia';
return instance;
}
let wonmanObj = new Wonman();
// 父类
function People (name) {
this.colors = ["red", "blue", "green"];
this.name = name; // 父类属性
}
People.prototype.sayName = function () { // 父类原型方法
return this.name;
};
/** 第一步 */
// 子类,通过 call 继承父类的实例属性和方法,不能继承原型属性/方法
function Woman (name, subName) {
People.call(this, name); // 调用 People 的构造函数,并向其传参
this.subName = subName;
}
/** 第二步 */
// 解决 call 无法继承父类原型属性/方法的问题
// Object.create 方法接受传入一个作为新创建对象的原型的对象,创建一个拥有指定原型和若干个指定属性的对象
// 通过这种方法指定的任何属性都会覆盖原型对象上的同名属性
Woman.prototype = Object.create(People.prototype, {
constructor: { // 注意指定 Woman.prototype.constructor = Woman
value: Woman,
enumerable: false,
writable: true,
configurable: true
},
run : {
value: function(){ // override
People.prototype.run.apply(this, arguments);
// call super
// ...
},
enumerable: true,
configurable: true,
writable: true
}
})
/** 第三步 */
// 最后:解决 Woman.prototype.constructor === People 的问题
// 这里,在上一步已经指定,这里不需要再操作
//Woman.prototype.constructor = Woman;
var instance = new Woman('An', 'sistenAn')
实例继承优点:
不限制调用方式
简单,易实现
实例继承缺点:
不能多次继承
拷贝继承
function Wonman (name) {
let instance = new People();
for (var p in instance) {
Wonman.prototype[p] = instance[p];
}
Wonman.prototype.name=name||'Tom'
}
let wonmanObj = new Wonman();
特点:
支持多继承
缺点:
效率较低,内存占用高(因为要拷贝父类的属性)
无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
对象冒充继承
function Woman(name, age) {
//3行关键代码 此三行用于获取父类的成员及方法
//用子类的this去冒充父类的this,实现继承
//父类People中的this.name、sleep,分别成为了子类的成员、子类的方法
this.method = People;
//接收子类的参数 传给父类
this.method(name);
//删除父类
delete this.method;
//此后的this均指子类
this.age = age;
this.sayWorld = function() {
alert(age);
}
}
因为对象冒充的留下,才有call apply的兴起
借用构造函数继承(伪造对象、经典继承)
复制父类的实例属性给子类
函数只不过是在特定环境中执行代码的对象,所以这里使用 apply/call 来实现。
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Woman(name){
//继承了People,子类的this传给父类
People.call(this); //People.call(this,'zhoulujun');
this.name = name || 'andy'
}
let womanObj = new Woman();
通过这种调用,把父类构造函数的this指向为子类实例化对象引用,从而导致父类执行的时候父类里面的属性都会被挂载到子类的实例上去。
但是通过这种方式,父类原型上的东西是没法继承的,因此函数复用也就无从谈起
Woman无法继承Parent的原型对象,并没有真正的实现继承(部分继承)
借用构造函数继承优点:
解决了子类构造函数向父类构造函数中传递参数
解决了子类实例共享父类引用属性的问题
可以实现多继承(call或者apply多个父类)
借用构造函数继承缺点:
方法都在构造函数中定义,无法复用
不能继承原型属性/方法,只能继承父类的实例属性和方法
组合式继承
调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用
function Woman(name,age){
People.call(this,name,age)
}
Woman.prototype = new People();
Woman.prototype.constructor = Woman;
let wonmanObj = new Woman(ren,27);
wonmanObj.eat();
缺点:
由于调用了两次父类,所以产生了两份实例
第一次是 Woman.prototype = new People()
第二次是 在实例化的时候,People.call(this,name,age)
优点:
函数可以复用
不存在引用属性问题
可以继承属性和方法,并且可以继承原型的属性和方法
寄生组合继承
通过寄生的方式来修复组合式继承的不足,完美的实现继承
function Woman(name,age){
//继承父类属性
People.call(this,name,age)
}
//继承父类方法,可以简化为:Woman.prototype = Object.create(People.prototype);
(function(){
// 创建空类
function Super (){};
Super.prototype = People.prototype;
//父类的实例作为子类的原型
Woman.prototype = new Super();
})();
//修复构造函数指向问题
Woman.prototype.constructor = Woman;
let womanObj = new Woman();
其实还是有两次执行
es6继承
//class 相当于es5中构造函数
//class中定义方法时,前后不能加function,全部定义在class的protopyte属性中
//class中定义的所有方法是不可枚举的
//class中只能定义方法,不能定义对象,变量等
//class和方法内默认都是严格模式
//es5中constructor为隐式属性
class People{
constructor(name='wang',age='27'){
this.name = name;
this.age = age;
}
eat(){
console.log(`${this.name} ${this.age} eat food`)
}
}
// 继承父类属性,super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
// 子类必须在constructor方法中调用super方法,否则新建实例时会报错。如果子类没有定义constructor方法,这个方法会被默认添加,不管有没有显式定义,任何一个子类都有constructor方法。
class Woman extends People{
constructor(name = 'ren',age = '27'){
super(name, age);
}
eat(){
//继承父类方法
super.eat()
}
}
let wonmanObj=new Woman('xiaoxiami');
wonmanObj.eat();
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6 的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
/**
* 继承
* @param {*} subClass 子类
* @param {*} superClass 父类
*/
function _inherits(subClass, superClass) {
// 类型检测
if (!superClass || typeof superClass !== "function" ) {
throw new TypeError("Super expression must either be null or a function, not " +typeof superClass);
}
/**
* Object.create 接受两个参数
* 指定原型创建对象
* @param {*} 目标原型
* @param {*} 添加属性
*/
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass, // subClass.prototype.constructor 指向 subClass
enumerable: false, // constructor 不可枚举
writable: true,
configurable: true
}
}); /**
* Object.setPrototypeOf 方法
* 设置子类的 __proto__ 属性指向父类
* @param {*} 子类
* @param {*} 父类
*/
if (superClass) {
// 设置子类的__proto__ 让 Child 能访问父类静态属性
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
}
参考文章:
JavaScript深入之继承的多种方式和优缺点 #16 https://github.com/mqyqingfeng/Blog/issues/16
JavaScript 中的继承 https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Inheritance
JavaScript常见的六种继承方式 https://segmentfault.com/a/1190000016708006
js继承的几种方式 https://zhuanlan.zhihu.com/p/37735247
深入浅出js实现继承的7种方式 https://cloud.tencent.com/developer/article/1536957
前端面试必备之JS继承方式总结 https://www.imooc.com/article/20162
转载本站文章《JavaScript继承的实现方式:原型语言对象继承对象原理剖析》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/js6/2015_0520_8494.html
JavaScript继承的实现方式:原型语言对象继承对象原理剖析的更多相关文章
- 《JAVASCRIPT高级程序设计》根植于原型链的继承
继承是面向对象的语言中,一个最为津津乐道并乐此不疲的话题之一.JAVASCRIPT中的继承,主要是依靠原型链来实现的.上一篇文章介绍过,JAVASCRIPT中,每一个对象都有一个prototype属性 ...
- Javascript函数、构造函数、原型、类和对象
函数 函数是JavaScript中特殊的对象,对函数执行typeof运算会返回字符串"function",因为函数也是对象,他们可以拥有属性和方法. 静态方法 函数在JS中定义了类 ...
- JavaScript高级内容笔记:原型链、继承、执行上下文、作用域链、闭包
最近在系统的学习JS深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助.如有错误请留言指正,tks. 了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些 ...
- [转]深入javascript——原型链和继承
在上一篇post中,介绍了原型的概念,了解到在javascript中构造函数.原型对象.实例三个好基友之间的关系:每一个构造函数都有一个“守护神”——原型对象,原型对象心里面也存着一个构造函数的“位置 ...
- js对象的几种创建方式和js实现继承的方式[转]
一.js对象的创建方式 1. 使用Object构造函数来创建一个对象,下面代码创建了一个person对象,并用两种方式打印出了Name的属性值. var person = new Object(); ...
- JS继承的实现方式
JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一.那么如何在JS中实现继承呢?让我们拭目以待. JS继承的实现方式 既然要实现继承,那么首先我们得有一个父类,代码如下: // 定义一个动物类 ...
- Java对象拷贝原理剖析及最佳实践
作者:宁海翔 1 前言 对象拷贝,是我们在开发过程中,绕不开的过程,既存在于Po.Dto.Do.Vo各个表现层数据的转换,也存在于系统交互如序列化.反序列化. Java对象拷贝分为深拷贝和浅拷贝,目前 ...
- 详解JavaScript对象继承方式
一.对象冒充 其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式).因为构造函数只是一个函数,所以可使 Parent 构造函数成为 Children 的方法,然 ...
- JavaScript 的对象继承方式,有几种写法?
JavaScript 的对象继承方式,有几种写法? 一.对象冒充 其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式).因为构造函数只是一个函数,所以可使 Pa ...
- JavaScript对象继承方式
一.对象冒充 其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式).因为构造函数只是一个函数,所以可使 Parent 构造函数 成为 Children 的方法, ...
随机推荐
- Python 批量合并图片到word文档
这段代码是一个用Python编写的功能,它将指定文件夹中的所有图片插入到Word文档中并保存.以下是代码的主要步骤和功能: 导入必要的库 Python中的docx库用于操作Word文档,glob库用于 ...
- idea java项目启动后访问html页面乱码
最近在做一个较久的项目,用的还是servlet+html(jsp),代码拉到本地后运行,访问登录页login.html既然乱码,先看个乱码的效果 怎么样,是不是很经典的乱码,别着急,我们一点点来分析乱 ...
- Python 利用pandas和matplotlib绘制柱状折线图
创建数据可视化图表:柱状图与折线图结合 在数据分析和展示中,经常需要将数据可视化呈现,以便更直观地理解数据背后的趋势和关联关系.本篇文章将介绍如何使用 Python 中的 Pandas 和 Matpl ...
- BFS广搜小谈
个人认为BFS比DFS难度要大一些,所以来这里做个笔记. 比较可怜的是本蒟蒻并没有找到BFS这个东西解题有什么规律,所以我只能粘上3个代码. 模板 当然一个差不多点儿的模板还是要有的. //模板1 # ...
- hive报错Execution Error, return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask[已解决]
我的报错信息 Execution Error, return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask 解决1(可行):不走ya ...
- Linux速查备忘手册
速查手册 网盘文档PDF资料: 链接: https://pan.baidu.com/s/111rqKfPaAiOHSHDo1SnckA 提取码: mhkv 1. 2. 3. 4. 5. ...
- 【封装】Trie
#include<cstdio> const int N = 1e6 + 5; struct Trie{ int root, id; bool bit[32]; struct Node{ ...
- 使用funcgraph-retval和bpftrace/kprobe快速定位并解决cpu控制器无法使能的问题
版本 Linux 6.5 背景 在学习cgroupv2的时候,想给子cgroup开启cpu控制器结果失败了: # 查看可以开启哪些控制器 root@ubuntu-vm:/sys/fs/cgroup# ...
- springBoot——多环境开发
不常用的application.properties版的 常用的:application.yml版 #多环境开发,设置启用环境 spring: profiles: active: test --- # ...
- LeetCode 503:下一个更大的元素|| (单调栈 or 线段树)
解题思路: 1.单调栈:因为是循环数组,因此把数组复制三遍,ans 数组复制为2倍长,维护一个单调非递增的栈,栈保存的元素是元组(a[i] , i ),如果后面的值有比栈顶元素的值大,栈顶元素出栈,更 ...