由于javascript没有类的概念,因此无法通过接口继承,只能通过实现继承。实现继承是继承实际的方法,javascript中主要是依靠原型链要实现。
原型链继承
原型链继承是基本的继承模式,其本质是重写原型对象,使其为新对象的实例。代码实现如下:
function Person(){ this.name = "default"; var temp = "temp"; } Person.prototype.age=0; Person.prototype.getName = function(){ return this.name; } Person.prototype.getAge = function(){ return this.age; } console.log(Person.prototype.age);//0 console.log(Person.age);//undefined console.log(Person.prototype.name);//undefined console.log(Person.name);//Person, if other property, should be undefined function Student(){ this.type = "student"; } //inheritance Student.prototype = new Person(); console.log(Student.prototype.constructor);//Person(){} console.log(Student.prototype.name);//default Student.prototype.constructor = Student; var student1 = new Student(); console.log(student1.getName());//default console.log(student1.name);//default console.log(student1.getAge());//0 console.log(student1.age);//0 console.log(student1.__proto__.age);//0 console.log(student1.temp);//undefined console.log(student1 instanceof Object);//true console.log(student1 instanceof Person);//true console.log(student1 instanceof Student);//true console.log(Student instanceof Person);//false
以上代码主要注意两个问题:
1.函数局部变量,内部属性及原型属性的区别。var temp定义了一个局部变量,this.name定义了一个内部属性,prototype.age则定义了一个原型属性。
对于局部变量,无法在函数以外的地方调用,包括实例。
之前说过,函数本身的prototype属性仅仅用于函数实例的属性继承,而函数本身不会使用这个关联的prototype,在prototype中设置的属性将直接作用于所有实例。(比如Person的实例Student.prototype和student1,注意Student并不是Person的实例)
而对于函数内部属性,函数实例将直接拥有对应的内部属性(初始值),而无法通过函数本身使用内部属性。这一点其实跟prototype属性有所区别。
2.利用重写原型对象实现继承的时候,Student.prototype = new Person(), Student.prototype将指向了另一个对象Person.prototype,因此此时Student.prototype.constructor将指向Person函数。通过Student.prototype.constructor = Student 可以将其constructor重新指向Student。
通过原型链可以更好的理解上面的代码:
原型链继承的缺点
关于原型链继承的问题,其实就是跟通过原型方式创建对象的问题一样,就是原型中包含引用类型所带来的共享问题。
还有就是创建实例的时候,无法向构造器中传递参数。
构造函数继承
另一种经典的继承便是通过构造函数实现继承,即通过apply()和call()方法在子类构造函数内部调用父类构造函数。具体实现如下:
function Person(name){ this.name = name; this.friends = new Array(); } Person.prototype.age = 0; function Student(name){ Person.call(this, name); } var student1 = new Student("Huge"); student1.friends.push("Alan"); console.log(student1.name);//Huge console.log(student1.age);//undefined console.log(student1.friends);//["Alan"] var student2 = new Student("Heri"); student2.friends.push("Amly"); console.log(student2.name);//Heri console.log(student2.friends);//["Amly"] console.log(student1 instanceof Person);//false console.log(student1 instanceof Student);//true
通过构造函数继承的问题除了构造函数模式本身存在的缺点之外(重复实例化方法),也无法类型识别,因此在父类原型中定义的方法和属性无法在子类中调用。
组合继承
由于通过原型链继承和构造函数继承都有其优缺点,因此将这两种继承方式组合起来,使用原型链继承实现原型中方法和属性的继承,通过构造函数继承实现参数传递和引用类型继承,是javascript中最常用的继承模式。代码实现如下:
function Person(name, age){ this.name = name; this.age = age; this.friends = new Array(); } Person.prototype.getName = function(){ return this.name; } function Student(name, age){ this.type = "student"; Person.call(this, name, age); } Student.prototype = new Person(); Student.prototype.constructor = Student; var student1 = new Student("Huge", 15); student1.friends.push("Alan"); console.log(student1.name);//Huge console.log(student1.age);//15 console.log(student1.friends);//["Alan"] console.log(student1.getName());//Huge console.log(student1 instanceof Person);//true console.log(student1 instanceof Student);//true var student2 = new Student("Heri", 16); student2.friends.push("Amly"); console.log(student2.name);//Heri console.log(student2.age);//16 console.log(student2.friends);//["Amly"] console.log(Student.prototype.name);//undefined console.log(Student.prototype.friends);//[]
从代码可以看出,组合继承会调用两次父类的构造函数:创建子类原型的时候和在子类构造函数内部调用。实际上,第一次创建子类原型的时候,子类已经包含了父类对象的全部实例属性,因此当通过调用子类构造函数创建实例的时候,将会重写这些属性。即同时存在两组属性,一组在实例上,一组在子类原型中,如上代码中Student.prototype.friends返回的空数组。这就是调用两次父类构造函数的结果。
其他继承方式
Crockford曾经提出了prototypal inheritance以及与之结合的parasitic inheritance。通过原型创建基于原有对象的新对象,并为新对象增加功能。
//prototypal inhertance function createObject(obj){ function F(){} F.prototype = obj; return new F(); } //parasitic inheritance function enhanceObject(obj){ var enhanceObj = createObject(obj); enhanceObj.getName = function(){ return this.name; } return enhanceObj; } var person = { name : "Alan" }; var person1 = enhanceObject(person); console.log(person1.getName());//Alan
更进一步,为了避免组合继承模式两次调用父类构造函数的问题,可以利用parasitic inheritance来继承父类的原型,再将其指定给子类的原型。如下代码:
//prototypal inhertance function createObject(obj){ function F(){} F.prototype = obj; return new F(); } //parasitic inheritance function inheritPrototype(superObj, subObj){ var obj = createObject(superObj.prototype); obj.constructor = subObj; subObj.prototype = obj; } function Person(name, age){ this.name = name; this.age = age; this.friends = new Array(); } Person.prototype.getName = function(){ return this.name; } function Student(name, age){ this.type = "student"; Person.call(this, name, age); } inheritPrototype(Person, Student); var student1 = new Student("Huge", 15); student1.friends.push("Alan"); console.log(student1.name);//Huge console.log(student1.age);//15 console.log(student1.friends);//["Alan"] console.log(student1.getName());//Huge console.log(student1 instanceof Person);//true console.log(student1 instanceof Student);//true var student2 = new Student("Heri", 16); student2.friends.push("Amly"); console.log(student2.name);//Heri console.log(student2.age);//16 console.log(student2.friends);//["Amly"]\ console.log(Student.prototype.name);//undefined console.log(Student.prototype.friends);//undefined
可以看出,子类只调用了父类一次构造函数,避免在子类原型中创建不必要的属性。同时,原型链也保持不便,可以说是实现类型继承的最有效方式。
相关推荐
主要简单谈谈javascript代码复用模式,主要详细介绍了类式继承模式中的默认模式,希望大家能够喜欢。
在谈及代码复用的时候,我们首先可以想到的是继承性。代码复用的原则是: 优先使用对象组合,而不是类继承 在js中,由于没有类的概念,因此实例的概念也就没多大意义,js中的对象是简单的键-值对,可以动态的创建和...
简单谈谈JavaScript寄生式组合继承 组合继承也被称为伪经典继承,它综合了我们昨天说的原型链和盗用构造函数,将俩者的有点结合在了一起。它的基本思想是使用原型链继承原型上的属性和方法,通过盗用构造函数继承...
第6章 代码复用模式 传统与现代继承模式的比较 使用类式继承时的预期结果 类式继承模式#1——默认模式 类式继承模式#2——借用构造函数 类式继承模式#3——借用和设置原型 类式继承模式#4——共享原型...
主要介绍了深入理解JavaScript系列(46):代码复用模式(推荐篇)详解,本文讲解了原型继承、复制所有属性进行继承、混合(mix-in)、借用方法等模式,需要的朋友可以参考下
4.4.3 挂多个class还是新建class——多用组合,少用继承 4.4.4 如何处理上下margin 4.5 低权重原则——避免滥用子选择器 4.6 CSS sprite 4.7 CSS的常见问题 4.7.1 CSS的编码风格 4.7.2 id和class 4.7.3 CSS ...
第6章 代码复用模式 传统与现代继承模式的比较 使用类式继承时的预期结果 类式继承模式#1——默认模式 类式继承模式#2——借用构造函数 类式继承模式#3——借用和设置原型 类式继承模式#4——共享原型 类式继承模式#...
能学到什么:理解原型链对于 JavaScript 开发者来说非常重要,因为它影响了对象的属性访问、继承和代码复用等方面。通过掌握原型链的概念和工作原理,开发者可以更好地利用 JavaScript 的面向对象特性。 这是一个可...
这意味着网页可以更快地加载并且用户无需安装额外的软件才能运行网页上的JavaScript代码。此外,与HTML和CSS紧密结合,可以直接在HTML文档中嵌入,使得网页的开发变得非常便捷。 JavaScript具有动态性,它可以在...
“继承”是面向对象编程里面经常提及到的概念,它的目的是实现代码复用。JavaScript并没有“类”的概念,那么,它如何实现继承呢? (ES6有关键字class和extend,继承的语法与Java等面向对象语言类似,但是,ES6 ...
看了不少js继承的东西也该总结总结了。 先说一下大概的理解,有不对的还望指正,也好更正一下三观。另外说明下,下面的例子并非原创基本就是改了个...真正是用来干啥的呢,主要是用来复用我们之前写过的代码。比如写过
难道把所有的共有逻辑都拷贝一遍,实现最低级的代码复用? 答案当然是——NO,所以,我们要自己实现继承! 目标 最关键的目标当然是继承——子类自动拥有父类的所有公共属性和方法。 支持instanceof,例如c是子类的...
代码结构混乱,不易维护,不易复用,不易扩展。 面向对象 面向对象有封装、继承、多态性的特性,所以具有易维护、易复用、易扩展的特点。 类的调用需要实例化,开销较大,因此性能方面较面向过程低。 6.1.1 面向过程...
这样,即通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。 下面来看一个例子 function SuperType(name) { this.name = name; this.color = ['red', 'blue', 'green']; }; // ...
深入介绍的面向对象特性,包括原型、代码复用和继承。学习内建的API并了解其全局函数、属性和对象。学习最新的ECMAScript5标准的更新之处。在为大型应用程序编码的时候,使用常用的设计模式。 致谢 前言 第1章简介 ...
源码中并未采取面向接口、继承等思想来实现工具方法的复用,笔者认为不应该将编程语言的学习成本带入数据结构,笔者的意愿是:学习者拿到每个数据结构,都可以做到信手使用,而不是还要反复查询其继承、实现结构。
谈到代码复用的时候,很有可能想到的是代码的继承性(inheritance),但重要的是要记住其最终目标——我们要复用代码。继承性只是实现代码复用的一种手段,而不是唯一的方法。复制属性也是一种复用模式,它跟继承性是...