创建对象的模式和继承模式
ECMA-262 把对象定义为:“无序属性的集合,其属性可以
包含基本值、对象或者函数。
”可以把 ECMAScript 的对象想象成散列表
:就是一组名值对
,其中值可以是数据或函数
属性类型(数据属性和访问器属性)
- 数据属性:
- [[Configurable]] :表示能否通过 delete 删除属性从而重新定义属性
- [[Enumerable]] :表示能否通过 for-in 循环返回属性
- [[Writable]] :表示能否修改属性的值。
- [[Value]] :包含这个属性的数据值。
要修改属性默认的特性用Object.defineProperty() 方法。这个方法
接收三个参数
:属性所在的对象、属性的名字和一个描述符对象描述符对象的属性必须是: configurable 、 enumerable 、 writable 和 value
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
一旦把属性定义为不可配置的,就不能再把它变回可配置了在把 configurable等特性设置为 false 之后就会有限制了。
- 访问器属性
访问器属性
不包含数据值
;它们包含一对getter 和 setter 函数
(不过,这两个函数都不是必需的)
- 访问器有4个特性:
- [Configurable]] :表示能否通过 delete 删除属性从而重新定义属性
- [[Enumerable]] :表示能否通过 for-in 循环返回属性
- [[Get]] :在读取属性时调用的函数
- [[Set]] :在写入属性时调用的函数
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); //2
下划线_是一种记号,用于表示只能通过
对象方法
来访问的属性()
创建对象
用字面量或者Object构造函数有个明显缺点:使用同一个接口创建很多对象会有很多重复代码,于是出现了多种模式
1、工厂模式
- 例子
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
以上例子创建了一个函数根据
调用这个函数返回
一个包含三个对象和一个方法的对象
,但是这种模式解决了创建多个相似对象的问题,却解决不了对象识别
(即不知道一个对象的类型)
2、构造函数模式
- 例子
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
1 构造函数名字用一个大写字母开头,非构造函数用小写字母开头
2 创建该例子Person实例的时候,用new,因为这个和上面工厂模式区别有它没有返回值
- 调用构造函数会经历四个阶段:
- 1、 创建一个新对象
- 2、将构造函数的作用域赋给新对象(
因此this指向这个新对象
) - 3、为新对象添加属性
- 4、返回新对象
- 该模式可以检测出对象类型(用
constructor
或者instance of
),并且上个例子中person1/2是object的实例,因为对象均继承object - 将构造函数当做函数
任何函数,
只要能通过new
来调用,它就可以作为构造函数,而任何函数,如果不通过new调用,那么它和普通函数没有什么不同
- 构造函数的缺点
- 每个方法都要在每个
实例上重新创建一遍例子中的person1和2都有一个方法,但那两个方法不是同一个 Function 的实例
- 即那个方法还可以这样定义:
this.sayName = new Function("alert(this.name)");
// 与声明函数在逻辑上是等价的 - 以这种方式创建函数,会导致不同的作用域链和标识符解析
- 解决办法:
通过把函数定义转移到构造函数外部
- 每个方法都要在每个
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
由于 sayName 包含的是一个指向函数的指针,因此 person1 和 person2 对象就共享了在
全局作用域
中定义的同一个 sayName() 函数
如果对象需要定义很多方法,那么就要定义很多个全局函数
所以看下面这个原型模式
3、原型模式
- 每个函数都有一个prototype (原型)属性,这个属性
是一个指针,
指向一个对象, prototype 就是通过调用构造函数而创建的那个对象实例的原型对象,可以让所有对象实例
共享它所包含的属性和方法- 例子1
结果如下:
- 例子2
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
1 给普通函数找原型没有很大作用,原型主要用在构造函数
2
(引用于《JavaScript高级程序设计》)
-
例子2各个对象之间的关系:
-
可以通过以下代码验证指针的指向:
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
- 当读取某个对象的某个属性时,搜索
先从对象实例本身开始
,如果找到了则返回,否则继续搜索指针指向的原型对象
,即两次搜索 - 而这正是多个对象实例共享原型所保存的属性和方法的基本原理
- 能
通过实例访问
原型但是不能重写原型中的值,如果实例中添加一个属性,并且与原型中的同名,那么该属性会屏蔽原型中的属性
- 通过使用
hasOwnProperty()
方法,可以知道自己访问的是实例属性还是原型属性
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例
alert(person1.hasOwnProperty("name")); //true
alert(person2.name); //"Nicholas"——来自原型
alert(person2.hasOwnProperty("name")); //false
- 原生的引用类型也是瞎用原型模式创建的,eg:在Array.prototype中可以找到sort()方法
alert(typeof Array.prototype.sort); //"function"
alert(typeof String.prototype.substring); //"function"
还可以组合使用构造函数模式和原型模式,以及动态原型模式,寄生构造函数模式,稳妥构造函数模式等
继承
- 很多OO语言(面向对象)都支持两种继承方式:
接口继承和实现继承
ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的 - 原型链:
- 基本思想:让一个引用类型继承另一个引用类型的属性和方法
- 基本概念:
每个构造函数
有个原型对象
,原型对象都包含一个指向构造函数的指针
,实例
都包含一个指向原型对象的内部指针,如果让原型对象
=另一个
类型的实例
,那么此时的原型对象将包含指另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数假如另一个原型又是另一个类型的实例,层层递进,就构成了实例与原型的链条的指针
- 如上述例子1:
如果
console.log(people1.__proto__.__proto__.constructor);//Object(){……}
- 继承模式(想要继承就要提供一个父类)
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
此段也可以参考https://www.cnblogs.com/ranyonsue/p/11201730.html
- 组合继承(伪经典继承)
- 使用
原型链
实现对原型
属性和方法的继承,借用构造函数
实现对实例
属性的继承 - 一个例子:
- 使用
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
组合继承避免了原型链和借用构造函数的缺陷
instanceof
和isPrototypeOf()
也能够用于识别基于组合继承创建的对象
- 原型式继承
- 必须有一个对象可以作为另一个对象的基础本质是执行对给定对象的
浅复制
。而复制得到的副本
还可以得到进一步改造
。 - 一个例子:
- 必须有一个对象可以作为另一个对象的基础本质是执行对给定对象的
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
也可用object.create()
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
- 寄生式继承
- 与原型式继承非常相似,也是基于某个对象或某些信息
创建一个对象
,然后增强对象
,最后返回对象
- 与原型式继承非常相似,也是基于某个对象或某些信息
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
- 寄生组合式继承
- 能解决组合继承模式由于
多次调用
超类型构造函数而导致的低效率问题,并且集寄生式继承和组合继承的优点与一身 - 寄生组合式的模型:
- 能解决组合继承模式由于
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
1、本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
3、开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式