JavaScript中没有类的概念,它不是严格意义上的面向对象语言,而是基于对象(Object-based)的编程语言。下面是读《JavaScript高级程序设计(第三版)》的学习笔记,总结一些常用的创建对象和继承的方法。
一、创建对象
1. 对象字面量
创建对象最简单的方式就是创建一个Object的实例。通过先创建一个对象,再为它添加属性和方法。此方法用对象字面量方式更为直观:
var animal = {
name : "mimi",
sayName : function(){
console.log(this.name);
}
//...
}
★ 这种方法在创建多个对象时会产生大量重复的代码,在创建单个对象时的首选模式。
2. 工厂模式
这种方法用函数来封装以特定接口创建对象的细节:
function animal(name) {
var o = new Object();//创建新对象
o.name = "mimi";//给这个对象添加属性
//...
return o;//返回这个对象
}
★ 这种方法没有解决对象识别的方法,一定程度上解决创建多个相似对象的问题吧,不常使用。
3. 构造函数模式
这种方法创建自定义的构造函数,从而自定义对象类型的属性和方法。
function Animal(name) {
this.name = name;//将属性和方法赋给this对象
this.sayName = function(){
console.log(this.name);
}
//...
}
它没有显式的创建对象也没有返回语句,但在使用new操作符调用后经历了四个步骤:
(1)创建一个对象
(2)将函数的作用域赋给新对象(this指向了新对象)
(3)执行函数中代码
(4)返回新对象
所有对象都有一个constructor属性,指向其构造函数。constructor可以用来标识对象类型,但是,要检测对象类型,instanceof操作符更可靠。
★ 这种方法的问题在于,每个方法都会在每个实例上重新创建一次。
4. 原型模式
function Animal() {
}
Animal.prototype.name = "mimi";//将方法属性添加到Animal的prototype属性中
Animal.prototype.sayName = function(){
console.log(this.name);
};
//...
无论何时,创建一个函数,都会自动创建一个prototype属性,指向其原型对象,正如前面所说,每个对象都有一个constructor属性,指向其构造函数。所以Animal.prototype.constructor指向Animal。判断原型对象与实例间关系可用isPrototypeOf()方法:
Animal.prototype.isPrototypeOf(animal1);
判断属性存在于实例中,还是存在与原型中:
属性存在于实例中时:
animal1.hasOwnProperty(name);//true
属性能通过对象访问:
name in animal1;//true
在实例中添加了与原型中同名的属性
我们将在实例中创建该属性,而屏蔽原型中的属性。当然,我们可以通过delete操作符完全删除实例中的该属性而让我们重新访问到原型中的属性(*  ̄︿ ̄)。
用字面量来实现更简单的原型语法
function Animal() {
}
Animal.prototype = {
constructor : Animal,//必须必须!因为这样相当于创建了一个新对象并赋值给Animal.prototype,
//此时这个新对象的constructor为默认的构造函数Object啊盆友们( ゚Д゚)ノ
name : "mimi",
sayName : function(){
console.log(this.name);
}
}
另外,很重要的一点:调用构造函数是会为实例添加一个指向最初原型的[[prototype]]指针,这个连接存在与实例和构造函数的原型对象之间,而不是实例与构造函数间。
我们将上面将上面的代码稍加修改:
function Animal(name) {
}
animal1 = new Animal()//创建实例1
console.log(animal1.smell);//undefined;此时原型中还未添加smell属性,理所当然。
Animal.prototype.smell = "good";//添加原型属性
console.log(animal1.smell);//可以访问smell属性。
然而,当用对象字面量来添加原型属性时:
function Animal(name) {
}
animal1 = new Animal()//创建实例1
console.log(animal1.smell);//undefined;此时原型中还未添加smell属性,理所当然。
Animal.prototype = {//添加原型属性
smell : "good";
}
console.log(animal1.smell);//undefined;
//因为animal1在创建时[[prototype]]指针指向的是最初的原型,而字面量法添加原型属性时将对象原型改变了,但[[prototype]]没有跟着一起变化,所以无法访问。
★ 这个方法的问题在于:1.它没办法传递初始化参数 2.对于引用类型值的属性(A)来说,改变一个实例的A属性会直接引起所有实例中的A属性的变化,因为实例中的A属性只是其原型中A属性的一个引用。
5. 组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性。
function Animal(name) {//添加实例属性
this.name = name;
this.color = ["yellow", "black"];
//...
}
Animal.prototype = {//添加方法和共享属性
constructor : Animal,
sayName : function(){
console.log(this.name);
}
//...
}
★ 这个方法就是使用最广泛、认同度最高的创建自定义类型的方法啦。
小总结:第一种和最后一种是比较常用的方法了,书中还有提到一些方法,是在前面这些方法都不适用时可以选择使用的,不过我还没碰上啦,所以等以后碰上了再来补充。
二、继承
JavaScript中不支持接口继承(继承方法签名),都是支持实现继承(继承实际的方法),主要依赖原型链来实现。
1. 原型链
将父类的实例赋值给子类的原型,此时,父类的实例中包含一个指向父类原型的指针。子类的原型通过这个指针访问到父类原型中的属性。
function Animal(name) {//父类
this.name = name;
}
Animal.prototype = {//将属性添加到父类原型
constructor : Animal,
sayName : function(){
console.log(this.name);
}
}
function Cat() {//子类
}
Cat.prototype = new Animal();//将父类实例赋值给子类原型。
Cat.prototype.constructor = Cat;//prototype被换成另一个对象,所以constructor属性要重写,否则会指向Animal.
var cat1 = new Cat();
其中的原型链:
用instanceof操作符判定可得出cat1是Cat、Animal、Object的实例。
★ 此方法存在问题前面已有提及,就是包含引用类型值的原型属性会被所有实例共享,且不能传参。
2. 借用构造函数
在子类型构造函数的内部调用超类型构造函数,通过apply()和call()方法来实现。
function Animal(name) {//父类
this.name = name;
}
function Cat(name) {//子类
Animal.call(this, name);//执行了一遍父类型构造函数代码
}
var cat1 = new Cat();
★ 此方法问题在于:1.方法都在构造函数中定义,函数复用无从谈起。2.在超类的原型中定义的方法对子类型也不可见。而且用instanceof也无法判定cat1与Animal的联系。
3. 组合继承
使用原型链实现对原型属性和方法的继承,使用借用构造函数实现对实例属性的继承。
function Animal(name) {//父类添加实例属性
this.name = name;
}
Animal.prototype = {//父类原型添加方法和属性
constructor : Animal,
sayName : function(){
console.log(this.name);
}
}
function Cat(name) {//子类
Animal.call(this, name);//继承属性
}
Cat.prototype = new Animal();//继承方法
Cat.prototype.constructor = Cat;//将constructor指回子类
★ 此方法是JS中最常用的继承模式。而且用instanceof 和isPrototypeOf() 也能识别。
目前有实际用的就这些了,总结的比较基础,希望也总结清楚了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。