前端基本功(七):javascript中的继承(原型、原型链、继承的实现方式)
栏目: JavaScript · 发布时间: 5年前
内容简介:javascirpt没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承。继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。JavaScript中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它
javascirpt没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承。继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
2. 什么是原型、原型链
1. 分清楚prototype、__proto__、constructor
constructor
function Person() { } var person = new Person(); console.log(person.__proto__ === Person.prototype); //true; function Person() { } var person = new Person(); console.log(Person === Person.prototype.constructor); //true //其实 person 中并没有constructor 属性,当不能读取到constructor属性时,会从 person 的原型也就是 Person.prototype中读取,正好原型中有该属性 console.log(person.constructor === Person); // true 复制代码
2. 原型链
JavaScript中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链(prototype chain)。所有原型链的终点都是Object函数的prototype属性,因为在JavaScript中的对象都默认由Object()构造。Objec.prototype指向的原型对象同样拥有原型,不过它的原型是null,而null则没有原型。
3. js继承的实现方式
- 原型链继承: 拿父类实例来充当子类原型对象
function Super(){ this.val = 1; this.arr = [1]; } function Sub(){ // ... } Sub.prototype = new Super(); // 核心 var sub1 = new Sub(); var sub2 = new Sub(); sub1.val = 2; sub1.arr.push(2); alert(sub1.val); // 2 alert(sub2.val); // 1 alert(sub1.arr); // 1, 2 alert(sub2.arr); // 1, 2 // 优点: 简单,易于实现 // 缺点: 1. 修改sub1.arr后sub2.arr也变了,因为来自原型对象的引用属性是所有实例共享的。 2. 创建子类实例时,无法向父类构造函数传参 复制代码
- 借用构造函数继承:借父类的构造函数来增强子类实例,等于是把父类的实例属性复制了一份给子类实例装上了(完全没有用到原型)
function Super(val){ this.val = val; this.arr = [1]; this.fun = function(){ // ... } } function Sub(val){ Super.call(this, val); // 核心 // ... } var sub1 = new Sub(1); var sub2 = new Sub(2); sub1.arr.push(2); alert(sub1.val); // 1 alert(sub2.val); // 2 alert(sub1.arr); // 1, 2 alert(sub2.arr); // 1 alert(sub1.fun === sub2.fun); // false // 优点: 1. 解决了子类实例共享父类引用属性的问题 2. 创建子类实例时,可以向父类构造函数传参 // 缺点: 无法实现函数复用,每个子类实例都持有一个新的fun函数,太多了就会影响性能,内存爆炸。。 复制代码
- 组合继承(常用): 把实例函数都放在原型对象上,以实现函数复用。同时还要保留借用构造函数方式的优点。
// 通过Super.call(this);继承父类的基本属性和引用属性并保留能传参的优点;通过Sub.prototype = new Super();继承父类函数,实现函数复用 function Super(){ // 只在此处声明基本属性和引用属性 this.val = 1; this.arr = [1]; } // 在此处声明函数 Super.prototype.fun1 = function(){}; Super.prototype.fun2 = function(){}; //Super.prototype.fun3... function Sub(){ Super.call(this); // 核心 // ... } Sub.prototype = new Super(); // 核心 var sub1 = new Sub(1); var sub2 = new Sub(2); alert(sub1.fun === sub2.fun); // true // 优点: 1. 不存在引用属性共享问题 2. 可传参 3. 函数可复用 // 缺点:(一点小瑕疵)子类原型上有一份多余的父类实例属性,因为父类构造函数被调用了两次,生成了两份,而子类实例上的那一份屏蔽了子类原型上的。。。又是内存浪费,比刚才情况好点,不过确实是瑕疵 复制代码
- 寄生组合继承(最佳方式):切掉了原型对象上多余的那份父类实例属性。
function beget(obj){ // 生孩子函数 beget:龙beget龙,凤beget凤。 var F = function(){}; F.prototype = obj; return new F(); } function Super(){ // 只在此处声明基本属性和引用属性 this.val = 1; this.arr = [1]; } // 在此处声明函数 Super.prototype.fun1 = function(){}; Super.prototype.fun2 = function(){}; //Super.prototype.fun3... function Sub(){ Super.call(this); // 核心 // ... } // 用beget(Super.prototype);切掉了原型对象上多余的那份父类实例属性 var proto = beget(Super.prototype); // 核心 proto.constructor = Sub; // 核心 Sub.prototype = proto; // 核心 var sub = new Sub(); alert(sub.val); alert(sub.arr); 复制代码
- 原型式:用生孩子函数得到得到一个“纯洁”的新对象(“纯洁”是因为没有实例属性),再逐步增强之(填充实例属性)
function beget(obj){ // 生孩子函数 beget:龙beget龙,凤beget凤。 var F = function(){}; F.prototype = obj; return new F(); } function Super(){ this.val = 1; this.arr = [1]; } // 拿到父类对象 var sup = new Super(); // 生孩子 var sub = beget(sup); // 核心 // 增强 sub.attr1 = 1; sub.attr2 = 2; //sub.attr3... alert(sub.val); // 1 alert(sub.arr); // 1 alert(sub.attr1); // 1 //ES5提供了Object.create()函数,内部就是原型式继承,IE9+支持 // 优点:从已有对象衍生新对象,不需要创建自定义类型(更像是对象复制,而不是继承。。 // 缺点: 1. 原型引用属性会被所有实例共享,因为是用整个父类对象来充当了子类原型对象,所以这个缺陷无可避免 2. 无法实现代码复用(新对象是现取的,属性是现添的,都没用函数封装,怎么复用) 复制代码
- 寄生式:给原型式继承穿了个马甲而已,创建新对象 -> 增强 -> 返回该对象,这样的过程叫寄生式继承,新对象是如何创建的并不重要。
// 有缺陷的寄生式继承 + 不完美的组合继承 = 完美的寄生组合式继承 function beget(obj){ // 生孩子函数 beget:龙beget龙,凤beget凤。 var F = function(){}; F.prototype = obj; return new F(); } function Super(){ this.val = 1; this.arr = [1]; } function getSubObject(obj){ // 创建新对象 var clone = beget(obj); // 核心 // 增强 clone.attr1 = 1; clone.attr2 = 2; //clone.attr3... return clone; } var sub = getSubObject(new Super()); alert(sub.val); // 1 alert(sub.arr); // 1 alert(sub.attr1); // 1 // 优点: 还是不需要创建自定义类型 // 缺点: 无法实现函数复用(没用到原型,当然不行) 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 构造函数、原型、原型链、继承
- JavaScript原型链继承
- JavaScript 继承和原型链
- 彻底弄懂JS原型与继承
- 前端进击的巨人(七):走进面向对象,原型与原型链,继承方式
- 从原型聊到原型继承,深入理解 JavaScript 面向对象精髓
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
程序员2010精华本
程序员杂志社 / 电子工业 / 2011-1 / 49.00元
《程序员(2010精华本)》主要内容:《程序员》创刊10年来,每年末编辑部精心打造的“合订本”已经形成一个品牌,得到广大读者的认可和喜爱。今年,《程序员》杂志内容再次进行了优化整合,除了每期推出的一个大型专题策划,各版块也纷纷以专题、策划的形式,将每月的重点进行了整合,让内容非常具有凝聚力,如专题篇、人物篇、实践篇等。另外杂志的版式、色彩方面也有了很大的飞跃,给读者带来耳目一新的阅读体验。一起来看看 《程序员2010精华本》 这本书的介绍吧!