为什么把这个单独列出来,主要是因为我觉得从这里开始已经有点看不懂了。这里有几个十分重要的概念,我有些没搞清楚,理解后面的闭包实在是有些困难。单独列出来也是再整理一遍加深记忆吧。已经到了第六章,前面都是一些偏记忆的东西,这章开始有些偏理解了。另外想说的是《JavaScript高级程序设计》真是一本好书。。。
<h1 id="6">第6章 面向对象的程序设计</h1>
<h2 id="6.1">6.1 理解对象</h2>
字面量创建对象: 这个东西很有用。后面还有很多涉及到
var person = {
name: "xjh",
job: "student",
sayName: function(){
alert(this.name);
}
};
创建一个Object实例:
var person = new Object();
person.name = "xjh";//定义属性
person.sayName = function(){//定义方法
...
};
<h3 id="6.1.1">6.1.1 属性类型</h3>
-
数据属性
- 包含一个数据值的位置,在这个位置可以读取和写入值。
- [[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。直接在对象上定义的属性默认为true
- [[Enumerable]]:能否通过for-in循环返回属性
- [[Writable]]:能否修改属性的值
- [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为undefined
- Object.defineProperty()
- 作用:修改属性默认的特性
- 参数:三个。属性所在的对象、属性的名字和一个描述符对象(就是上面四个)
- 用这个方法创建的属性默认是false
-
访问器属性
- 不包含数据值,包含一对getter和setter函数
- [[Configurable]]
- [[Enumerable]]
- [[Get]]
- [[Set]]
- 访问器属性是必须用上面的Object.defineProperty()定义的
- 以前创建访问器属性用的是_defineGetter()和_defineSetter()
<h3 id="6.1.2">6.1.2 定义多个属性</h3>
- Object.defineProperties()
-
参数:两个对象。第一个是要添加或修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应
var book = {}; Object.defineProperties(book, { ... });
-
<h3 id="6.1.3">6.1.3 读取属性的特性</h3>
- Object.getOwnPropertyDescriptor()
- 参数:属性所在的对象和要读取其描述符的属性名称
- 返回:对象
<h2 id="6.2">6.2 创建对象</h2>
this关键字:
- 总是指向调用该方法的对象
- 使用this可以在任意多个地方重用同一个函数
- 如果不引用的话会当作局部或全局变量处理
<h3 id="6.2.1">6.2.1 工厂模式</h3>
用函数封装以特定接口创建对象的细节
解决了创建多个相似对象的问题,但没有解决对象识别的问题
function createPerson(name, age, job){
var o = new Object();
o.name = name;
...//age和job
return o;
}
var person1 = createPerson("xjh",21,"stu");
其实就是把创建一个对象的过程放到了一个函数里面,还是一样的,没啥区别,只不过就是要重复创建的时候不用复制粘贴了!所以当然也看不出来是什么对象,因为都是用new Object()的
<h3 id="6.2.2">6.2.2 构造函数模式</h3>
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
var person1 = new Person("xjh",21,"stu");
-
用new操作符调用构造函数时:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(就是说this指向新对象)
- 执行构造函数中的代码
- 返回新对象
-
将构造函数当作函数
- 构造函数和其他函数的唯一区别就是调用方式不同
- 任何函数通过new来调用就是构造函数
-
构造函数的问题
每个方法都要在每个实例上重新创建一遍
-
可以把函数定义转移到构造函数外面,就不用每次都重复创建
function Person(name, age, job){ ...//name,age,job赋值 this.sayName = sayName; } function sayName(){ alert(this.name); }
封装性不好
我是这么理解,反正new了就是构造函数,这个形式和java里面的新建一个对象也差不多。new操作符会有一系列的活动总之就是帮助你创建这个新对象然后新对象执行一下构造函数里面的代码。所以构造函数和函数就是new不new的区别。这个缺点也很明显就是每次新建对象都得执行一遍构造函数里面的代码。这样方法就不能重用了,但是要是把函数放在外面又感觉东一块西一块,封装性不好。然后就有下面的原型了
<h3 id="6.2.3">6.2.3 原型模式</h3>
-
理解原型对象:这个原型对象很重要啊
只要创建一个新函数,就会自动为该函数创建一个prototype属性,这个属性指向函数的原型对象。
-
在默认情况下,所有原型对象会自动获得constructor属性,这个属性包含一个指向prototype属性所在函数的指针:Person.prototype.constructor指向Person
就是说函数自带的prototype和实例的内部属性prototype都是指向原型对象。而原型对象的constructor是指向函数的(很容易记反正constructor是构造的意思),至于实例和函数没啥关系
[[Prototype]]无法访问
-
isPrototype()
Person.prototype.isPrototypeOf(person1);//true
- 虽然没办法访问[[Prototype]],但是上面的方法可以判断某个原型和某个实例有没有关系
-
Object.getPrototypeOf()
- 返回:[[Prototype]]
- 可以方便地取得一个对象的原型
当代码读取某个对象的某个属性时,先从对象实例本身开始,没有再搜索原型对象
对象实例只能访问原型中的值,不能重写原型中的值。如果对象实例添加了一个同名属性,就会屏蔽原型中的那个属性
-
delete操作符
- 可以删除实例属性,从而能重新访问原型中的属性
delete person1.name;
-
hasOwnProperty()
- 检测一个属性是否存在于实例中
- 是一个从Object继承的方法
- 只有当给定属性存在于对象实例中时,返回true(属性一定是自己的,不是原型的)
-
原型与in操作符
-
in:
- 对象能访问给定属性时就会返回true,不管该属性在哪里(这个和前面的hasOwnProperty()不一样了,这个只要“在”就行)
"name" in person1;//true
同时使用hasOwnProperty()和in确定属性在对象中还是在原型中
-
for-in
- 返回的是所有能够通过对象访问的、可枚举的属性。包括实例和原型。屏蔽了原型中不可枚举属性的实例属性也会返回
-
Object.keys()
- 参数:对象
- 返回:包含所有可枚举属性的字符串数组
-
Object.getOwnPropertyNames()
- 得到所有实例属性,无论是否可枚举
-
-
更简单的原型语法
- 用对象字面量
- 有一点是constructor属性会变成新对象的constructor属性(指向Object构造函数)
- 如果重设constructor,[[enumerable]]会被设置为true
-
原型的动态性
- 对原型对象所做的任何修改都能立即从实例上反映出来。即使是先创建了实例后修改原型也是如此
- 如果是创建实例后又重写整个原型,就会切断构造函数与最初原型之间的联系
-
原生对象的原型
- 所有原生的引用类型,都是用原型模式创建的(像Object之类的)。所有原生引用类型都在其构造函数的原型上定义了方法。
- 可以定义新方法,但是不建议用
-
原型对象的问题
- 如果有引用类型值的属性,只要有一个实例修改原型就会修改。而原型都是共享的,所以别的实例也会有这个引用类型
<h3 id="6.2.4">6.2.4 组合使用构造函数模式和原型模式</h3>
创建自定义类型最常见的方法
-
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["xjh", "xkld"]; } Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); } }
<h3 id="6.2.5">6.2.5 动态原型模式</h3>
-
把初始化原型也写在构造函数里面
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; if(typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } }
<h3 id="6.2.6">6.2.6 寄生构造函数模式</h3>
其实和工厂模式一样的,就是用构造函数调用了,因为不能用instanceof确定对象类型,所以尽量不要使用。
<h3 id="6.2.7">6.2.7 稳妥构造函数模式</h3>
与寄生相类似,但1.不引用this,2.不使用new调用构造函数
function Person(name, age, job){
var o = new Object();
...//定义变量
o.sayName = function(){
alert(name);
};
return o;
}
var friend = Persin("xjh", 21, "stu");
<h2 id="6.3">6.3 继承</h2>
只支持实现继承,而且主要依靠原型链继承
<h3 id="6.3.1">6.3.1 原型链</h3>
主要就是创建一个SuperType实例,并赋给SubType.prototype。本质是重写原型对象
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
-
别忘记默认的原型
所有函数的默认原型都是Object的实例。因此默认原型都会包含一个内部指针,指向Object.prototype
-
确定原型和实例的关系
- 只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型
- instanceof操作符
- isPrototypeOf
-
谨慎地定义方法
给原型添加方法的代码一定要放在替换圆形的语句之后
在通过原型链实现继承时,不能使用对象字面量创建原型方法。这样做会重写原型链 -
原型链的问题
依然是那个引用类型值共享的问题
<h3 id="6.3.2">6.3.2 借用构造函数</h3>
基本思想就是在子类型构造函数的内部调用超类型构造函数
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了SuperType
SuperType.call(this);
}
- 传递参数
有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数 - 借用构造函数的问题
函数不能复用
<h3 id="6.3.3">6.3.3 组合继承</h3>
思路是使用原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承
最常用的继承模式
function SuperType(name,colors){
this.name = name;
this.colors = colors;
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
//借用构造函数,继承属性
function SubType(name,age){
SuperType.call(this, name);
this.age = age;
}
//原型链,继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType("xjh", 21);
instance1.colors = ["red", "green"];
var instance2 = new SubType("xkld", 21);
instance2.colors = ["yellow", "black"];
<h3 id="6.3.4">6.3.4 原型式继承</h3>
因为原型可以基于已有的对象(原型)创建新对象(实例),所以可以把一个对象先作为一个原型,然后另一个对象作为实例
function object(o){
function F(){} //随便来一个函数,这样就会自动生成原型
F.prototype = o; //把o赋给这个F的原型
return new F(); //返回F的实例,这个实例就共享了F原型(o)里面的属性和方法
}
- Object.create()
- ES5,规范了原型式继承
- 参数:一个用作新对象原型的对象、一个为新对象定义额外属性的对象(可选)
- 在传入一个参数的情况下,与之前的object()行为相同
- 第二个参数格式与Object.defineProperties()的第二个参数格式相同:每个属性都通过自己的描述符定义。以这种方式指定的任何属性都会福海原型对象上的同名属性
<h3 id="6.3.5">6.3.5 寄生式继承</h3>
和寄生构造函数类似。
由于不能做到函数复用,会降低效率
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone;
}
var anotherPerson = createAnother(person);
<h3 id="6.3.6">6.3.6 寄生组合式继承</h3>
组合继承有不足:会调用两次超类型构造函数。一次是子类型原型,另一次是子类型构造函数内部调用超类型构造函数。因此SubType原型上和实例上都有实例属性
所以要引出寄生组合式继承:借用构造函数来继承属性,通过原型链的混成形式来形成方法
本质:用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
function inheritPrototype(subTyoe, superType){ //接收两个参数,子类型构造函数和超类型构造函数
var prototype = object(superType.prototype);//这个prototype是一个超类型的实例
prototype.constructor = subType;
subType.prototype = prototype;//把这个实例作为子类型的原型
}
inheritPrototype(SubType, SuperType);
/*
SubType.prototype = new SuperType();其实也就是说把这个省略了,不要让这个再调用一次*/
SubType.prototype.sayAge = function(){
alert(this.age);
}
<h2 id="6.4">6.4 小结</h2>
这章主要说的是对象和继承
关于创建对象主要以下几种:
- 工厂模式
- 构造函数模式
- 原型模式
- 组合继承。就是原型模式+构造函数模式
关于继承:
- 原型链
- 借用构造函数(解决了原型链对象实例共享所有继承属性和方法的问题)
- 组合继承:使用最多的继承模式
- 原型式继承(浅复制)
- 寄生式继承
- 寄生组合式继承:实现基于类型继承的最有效方