ECMAScript 继承机制实现
1.继承机制的实现
(1)基类
所有开发者定义的类都可作为基类。
出于安全原因,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击。
(2)子类
是否使用基类完全由开发者决定,有时,可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数。
在这种情况下,基类被看作抽象类。
创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现。
所有属性和方法都是公用的,因此子类可直接访问这些方法。
子类还可添加超类中没有的新属性和方法,也可以覆盖超类的属性和方法。
(3)继承的方式
ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。
这意味着所有的继承细节并非完全由解释程序处理。
2.几种具体的继承方式
(1)对象冒充
就是把基类的构造函数当成子类的一个方法,然后调用它。
因为基类中的this在这个时候指向类子类,也就等于子类构造了这些构造函数中定义的属性和方法。
注意:需要注意的是在调用完基类的构造方法后,需要删除子类中父类构造方法的引用,否则会有覆盖基类中相关属性和方法的危险。
1)实现方法
案例:
// 基类中属性和方法均有this指向
function ClassA(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
function ClassB(sColor, sName) {
// 引用ClassA的构造方法作为ClassB的一个普通方法
this.newMethod = ClassA;
// 执行这个方法,这时候ClassA中的this相当于指向ClassB,关键字 this 总是指向调用该方法的对象。
this.newMethod(sColor);
// 删除这个方法,防止意外覆盖基类中相关属性和方法
delete this.newMethod;
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
2)对象冒充可以实现多重继承
对象冒充可以支持多重继承。也就是说,一个类可以继承多个超类。
如果存在两个类 ClassX 和 ClassY,ClassZ 想继承这两个类,可以使用下面的代码:
function ClassZ() {
this.newMethod = ClassX;
this.newMethod();
delete this.newMethod;
this.newMethod = ClassY;
this.newMethod();
delete this.newMethod;
}
这里存在一个弊端,如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法,ClassY 具有高优先级。
因为它从后面的类继承。除这点小问题之外,用对象冒充实现多重继承机制轻而易举。
由于这种继承方法的流行,ECMAScript 的第三版为 Function 对象加入了两个方法,即 call() 和 apply()。
3)call() 方法
call() 方法是与经典的对象冒充方法最相似的方法。
它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.call(obj, "The color is ", "a very nice color indeed.");
说明:
sayColor() 在对象外定义,即使它不属于任何对象,也可以引用关键字 this。
对象 obj 的 color 属性等于 blue。
调用 call() 方法时,第一个参数是 obj,说明应该赋予 sayColor() 函数中的 this 关键字值是 obj对象。
第二个和第三个参数是字符串。它们与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配。
最后生成的消息 "The color is blue, a very nice color indeed." 将被显示出来。
要与继承机制的对象冒充方法一起使用该方法,只需将前三行的赋值、调用和删除代码替换即可:
function ClassB(sColor, sName) {
//this.newMethod = ClassA;
//this.newMethod(color);
//delete this.newMethod;
ClassA.call(this, sColor);
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
4)apply() 方法
apply() 方法有两个参数,用作 this 的对象和要传递给函数的参数的数组。
也就是将基类参数组装成Array传入。
案例:
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
调用 apply() 方法时,第一个参数仍是 obj。
第二个参数是由两个字符串构成的数组,与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配。
该方法也用于替换前三行的赋值、调用和删除新方法的代码:
function ClassB(sColor, sName) {
//this.newMethod = ClassA;
//this.newMethod(color);
//delete this.newMethod;
ClassA.apply(this, new Array(sColor));
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
(2)原型链(prototype chaining)
继承这种形式在 ECMAScript 中原本是用于原型链的。
prototype 对象是个模板,要实例化的对象都以这个模板为基础。
总而言之,prototype 对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。
把 ClassA 的实例赋予ClassB的prototype 属性
1)如果用原型方式重定义前面例子中的类,它们将变为下列形式:
function ClassA() {
}
ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB() {
}
// 重点语句
ClassB.prototype = new ClassA();
这里,把 ClassB 的 prototype 属性设置成 ClassA 的实例。
注意:调用 ClassA 的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。
2)与对象冒充相似,子类的所有属性和方法都必须出现在 prototype 属性被赋值后,因为在它之前赋值的所有方法都会被删除。
为什么?因为 prototype 属性被替换成了新对象,添加了新方法的原始对象将被销毁。
function ClassB() {
}
ClassB.prototype = new ClassA();
ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
alert(this.name);
};
3)在原型链中,instanceof 运算符的运行方式也很独特。对 ClassB 的所有实例,instanceof 为 ClassA 和 ClassB 都返回 true。
var objB = new ClassB();
alert(objB instanceof ClassA); //输出 "true"
alert(objB instanceof ClassB); //输出 "true"
在 ECMAScript 的弱类型世界中,这是极其有用的工具,不过使用对象冒充时不能使用它。
原型链的弊端是不支持多重继承。记住,原型链会用另一类型的对象重写类的 prototype 属性。
(3)混合方式
这种继承方式使用构造函数定义类,并非使用任何原型。
对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。
最好的方式为两者都用
function ClassA(sColor) {
this.color = sColor;
}
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB(sColor, sName) {
// 重点 用对象冒充基础ClassA的属性
ClassA.call(this, sColor);
this.name = sName;
}
// 重点 用原型链继承ClassA类的方法
ClassB.prototype = new ClassA();
ClassB.prototype.sayName = function () {
alert(this.name);
};
由于这种混合方式使用了原型链,所以 instanceof 运算符仍能正确运行。
注意:如果定义对象用的动态原型方法,想要得到基类方法必须有原型链,或者混合实现。
一个定义对象:动态原型方法,继承:对象冒充 + 原型链混合方式的例子
// 定义对象:动态原型方法
function ClassA(name) {
this.name = name;
if (typeof ClassA._name == "undefined") {
ClassA.prototype.getName = function() {
if (typeof this.name == "undefined") {
return "";
}
return this.name;
};
ClassA.prototype.setName = function(name) {
this.name = name;
}
}
ClassA._name = true;
}
var a1 = new ClassA();
a1.setName("a1");
document.write(a1.getName());
document.write("</br>")
var a2 = new ClassA("a2");
document.write(a2.name)
document.write("</br>")
// 继承:对象冒充 + 原型链混合方式
function ClassB(name, age) {
ClassB.prototype = new ClassA();
ClassA.call(this, name);
this.age = age;
if (typeof ClassB._age == "undefined") {
ClassB.prototype.getAge = function() {
if (typeof this.age == "undefined") {
return "";
}
return this.age;
};
ClassB.prototype.setAge = function(age) {
this.age = age;
}
}
ClassB._age = true;
}
var b1 = new ClassB("b1", 11);
document.write("b1=name:" + b1.name + ",age:" + b1.age + "</br>");
var b2 = new ClassB();
b2.setName("b2");
b2.setAge(22);
document.write("b2=name:" + b2.getName() + ",age:" + b2.getAge());
由于JS缺少私有作用域,所以上面的get/set方法只是一个测试方法。
可以封装setDate/getDate方法传入ajax拿到的JSON数据,然后解析赋值。
分享到:
相关推荐
7. 函数的扩展 8. 数组的扩展 9. 对象的扩展 10. Symbol 11. Set 和 Map 数据结构 12. Proxy 13. Reflect 14. Promise 对象 15. Iterator 和 for...of 循环 16. Generator 函数的语法 17. Generator 函数的异步应用 ...
1. ECMAScript 6简介 2. let 和 const 命令 3. 变量的解构赋值 4. 字符串的扩展 5. 正则的扩展 6. 数值的扩展 7. 函数的扩展 8. 数组的扩展 9. 对象的扩展 10. Symbol 11. Set 和 Map 数据结构 12. Proxy 13. ...
7. **原型与继承**:JavaScript使用原型链实现继承,每个对象都有一个`__proto__`属性指向其构造函数的原型。ES5中的`Object.create()`和ES6的类继承都是基于原型链的。 8. **闭包**:闭包是一种函数特性,它可以...
7. **剩余与扩展运算符**:剩余运算符(...)用于收集剩余的参数或元素,而扩展运算符(...)用于将数组或可迭代对象展开成各自的元素。 8. **模块系统**:通过`import`和`export`关键字,实现了模块化,方便代码...
随后的两年,又分别发布了2.0和3.0版本,其中ECMAScript 3.0版是一个重要的里程碑,它奠定了JavaScript语言的基础,并被后续版本完全继承。在此之后,ES4.0开始酝酿,尽管最终未能通过,但它的一些特性被后续的ES6...
7. **类(Class)和继承**: - ES6 引入了基于原型的面向对象编程的类语法,使得代码更加清晰,同时支持继承、构造函数、方法重写等特性。 8. **解构赋值**: - 可以从数组或对象中提取值,直接赋给新的变量,...
- 对象方法和原型链,实现继承和多态。 5. **数组** - 数组是一种特殊的对象,其属性名是整数。 - `length`属性返回数组元素的数量。 - `push()`, `pop()`, `shift()`, `unshift()`等方法用于操作数组元素。 -...
7. **参考资料**:ECMAScript的官方文档和参考资料是理解语言特性的关键,包括ECMA-262规范的各个版本,它们详细阐述了语言的所有细节。 在实际开发中,理解ECMAScript的这些核心概念对于编写高效、可维护的...
### ECMAScript 5 版本特性解析 #### 引言 随着互联网技术的迅猛发展,JavaScript 作为一种广泛应用于浏览器端的脚本语言,其重要性日益凸显。为了更好地满足开发者的实际需求并提高编程效率,ECMAScript 第五版...
**ECMAScript 6(简称ES6)是JavaScript语言的一个重大升级版本,它引入了许多新的特性和改进,为开发者提供了更强大的工具和更简洁的语法。阮一峰老师的《ES6标准入门》第三版是一本深入讲解这些新特性的权威著作,...
7. **Promise 对象:** Promise 是处理异步操作的一种方式,它代表一个异步操作的最终完成或失败的状态以及相应的结果。Promise 的链式调用解决了回调地狱的问题,使异步代码更加清晰。 8. **生成器(Generators)...
**7. 模块导入导出** - **模块系统**:通过`import`和`export`关键字实现模块化,使得代码可重用性更强,结构更清晰。 **8. 新增数据类型与方法** - **Set 和 Map**:提供了新的数据结构,Set不允许重复元素,Map...
7. **剩余参数与扩展运算符**:剩余参数(`...`)用于将多个参数打包成一个数组,而扩展运算符(`...`)则用于将数组展开为参数列表。 8. **模块系统**:通过`import`和`export`关键字,实现了模块化,便于代码组织和...
7. **默认参数** - 函数参数可以设置默认值,如`function foo(x = 1) { ... }`,使得函数调用时无需提供所有参数。 8. **Promise** - `Promise`对象用于异步编程,解决了回调地狱问题,提供了链式调用和错误处理...
**ECMAScript 6(简称ES6)是JavaScript语言的一个重大升级版本,它引入了许多新的特性和改进,为开发者提供了更强大的工具和更简洁的语法。阮一峰先生的《ECMAScript 6 入门》是一本深入浅出介绍这一版本的权威指南...
2. **类与继承**:ES6 提供了基于原型的面向对象编程的新语法,引入了`class`关键字来定义类,以及`extends`关键字支持继承,使JavaScript的面向对象编程更加简洁和直观。 3. **箭头函数**:箭头函数提供了一种更...
7. **Promise对象:** 用于异步编程,解决了回调地狱问题,提供了链式调用的方式处理异步操作。 8. **Set和Map数据结构:** `Set`不包含重复元素,`Map`则是键值对的集合,两者提供了更丰富的数据管理方式。 9. **...
ECMAScript 2018快速...13.3.7配置Gulp 114 13.3.8添加DOM操作 118 13.3.9添加样式操作 119 13.3.10添加样式类操作 122 13.3.11添加属性操作 123 13.3.12添加事件操作 125 13.3.13添加Ajax功能 127 13.4小结 129 [2]
ECMAScript 7(ES7,ES2016)添加了对异步操作的支持,如async/await关键字。 在ECMAScript规范中,重要知识点包括: 1. **基本语法**:变量声明(var, let, const)、数据类型(原始类型:Number, String, ...
1. **类和模块**:在ES2015中,JavaScript引入了类的概念,虽然本质上仍然是基于原型的继承,但语法上更接近传统的面向对象语言。同时,模块系统允许开发者更好地组织和管理代码,避免全局变量污染,通过`import`和`...