JavaScript高级程序设计
# 第六章 面向对象的程序设计
# 6.1 理解对象
# 6.1.1 属性类型
ECMAScript 有两种属性:
数据属性:
- Configurable:能否使用 delete 删除属性
- Enumerable:能否使用 for-in
- Writable:能否修改属性值
- Value:包含属性的数据值
访问器属性:
- Configurable:同上
- Enumerable:同上
- Get
- Set
一般使用
object.__defineGetter__('attributeName',funcion())
# 6.1.2 定义多个属性
var book = {}
Object.defineProperties(book, {
a:{
writable:true,
value:1
},
b:{...}
})
# 6.1.3 读取属性
Object.getOwnPropertyDescriptor()
返回值是一个对象
# 6.2 创建对象
# 6.2.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");
person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"
# 6.2.2 构造函数模式
如 Object 和 Array 就是原生的构造函数
工厂模式解决了创建多个相似对象的问题**(太单一)**却没有解决 对象识别的问题
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");
person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"
# 与工厂模式不同之处:
没有显示创建对象
直接将属性方法赋值给 this
没有 return
使用 new 创建并经历以下四个步骤:
1.创建一个新对象
2.将构造函数作用域赋给新对象 (this 指向新对象)
3.执行构造函数中的代码
4.返回新对象
# 检测对象类型:
创建的对象既是 Object 的实例也是 Person 的实例
person1 person2 分别保存着 Person 的一个不同的实例,这两个对象都有一个 constructor 属性,该属性指向 Person (构造函数特有?)
alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Person); //true
alert(person1.constructor == Person); //true alert(person2.constructor == Person); //true
alert(person1.sayName == person2.sayName); //false
# 将构造函数当做函数:
任何函数只要通过 new 来调用就是构造函数 与普通函数的唯一区别就是调用方式
// 构造函数
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"
// 普通函数
Person("Greg", 27, "Doctor"); //adds to window
window.sayName(); //"Greg"
// 在另一对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"
# 6.2.3 原型模式
构造函数模式的问题:导致不同的作用域链和标识符解析
我们创建的每个函数都有一个 prototype 属性,这个属性是一个指针,指向原型对象
# 原型模式创建对象:
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();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
读取对象的某个属性时,都会执行一次搜索。先从实例本身开始再到原型对象。
# 原型链:
Person.prototype → 原型对象
Person.prototype.constructor → Person
person1.[[prototype]] = person1.proto → 原型对象
person1 与 Person 没有直接联系
# 检测:
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
//only works if Object.getPrototypeOf() is available
if (Object.getPrototypeOf){
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
}
使用
hasOwnProperty()
方法可以检测一个属性是否存在于实例中使用
in
操作符只要该属性能够被访问即返回 true e.g:'name' in person1
要取得对象上所有可枚举的 实例 属性可以使用
Object.keys()
var keys = Object.keys(Person.prototype); alert(keys); //"name,age,job,sayName"
要取得对象上所有 实例 属性无论可否枚举使用
Object.getOwnPropertyNames()
# 更简单的原型语法:
省去每次都要敲一遍 Person.prototype 的麻烦
// 原本的原型语法
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
// 更简单的原型语法
function Person(){
}
Person.prototype = {
// consructor: Person, 如不设置则constructor不再指向Person而是指向Object
name:'Nicolas',
age:29
};
# 原型的动态性:
重写原型对象会切断实例与原有原型的联系
/*
重写前
friend.[[prototype]] -> Person Prototype
Person.prototype -> Person Prototype
Person Prototype.constructor -> Person
*/
function Person(){
}
var friend = new Person();
/*
重写后
Person.prototype -> New Person Prototype
friend.[[prototype]] -> Person Prototype
*/
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error
friend.__proto__.constructor.prototype.sayName(); //Nicolas
# 原型模式的问题:
修改一个实例也会同时修改另一个实例
# 6.2.4 构造函数 + 原型模式
创建自定义类型的最常见方式,就是组合使用构造函数与原型模式。构造函数用于定义实例属性,原型用于定义方法和共享属性。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor: Person, // 重点!!!
sayName : function () {
alert(this.name);
}
};
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
# 6.3 继承
# 6.3.1 原型链
原型和原型链的关系 instance.constructor.prototype = instance.__proto__
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//inherit from SuperType
//原型 继承 原型
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
var instance = new SubType();
# 完整的原型链:
instance 是一个实例 他的proto等于 SubType.prototype
instance.proto → subType Prototype = subType.prototype
subType Prototype.proto → superType Prototype = superType.prototype
superType Prototype.proto → Object Prototype = Object.prototype
# 谨慎定义方法:
覆盖超类的某个方法或添加超类不存在的方法时,一定要放在替换原型语句之后。
//inherit from SuperType
SubType.prototype = new SuperType();
//new method
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
//override existing method
SubType.prototype.getSuperValue = function (){
return false;
};
不要使用对象字面量创建原型方法,会重写原型链。
SubType.prototype = {
getSubValue : function (){
return this.subproperty;
},
someOtherMethod : function (){
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!
# 6.3.2 借用构造函数
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//inherit from SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
# 6.3.3 组合继承
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。
// 父类
// 构造函数
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.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
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 第十三章 事件
JavaScript 与 HTML 之间的交互是通过事件实现的。
可以使用侦听器来预订事件,以便事件发生时执行相应的代码。(观察员模式)
# 13.1 事件流
事件流描述的是从页面中接收事件的顺序
冒泡 | 捕获 | DOM |
---|---|---|
内->外 | 外->内 | 外->内->外 |
# 13.1.1 事件冒泡
IE 的事件流叫做事件冒泡
事件开始时由最具体的元素(嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点。
事件冒泡 :<div> -> <body> -> <html> -> document
所有现代浏览器都支持事件冒泡,但在具体实现上还是有一些差别。
# 13.1.2 事件捕获
Netscape Communicator 提出的另一种事件流叫事件捕获
事件捕获 :document -> <html> -> <body> -> <div>
# 13.1.3 DOM 事件流
DOM2 级事件流规定事件流包含三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
整个流程:
事件捕获 | 处于目标 | 事件冒泡 |
---|---|---|
document->html->body | div | div->body->html->document |
# 13.2 事件处理程序
事件就是用户或浏览器自身执行的某种动作。响应某个事件的函数就叫做事件处理程序(或事件侦听器)
事件的名字 | 事件处理程序的名字 |
---|---|
click/load | onclick/onnload |
DOM2✅ | HTML✅ DOM0✅ IE✅ |
# 13.2.1 HTML 事件处理程序
用一个 HTML 特性来指定 嵌入在标签中
<input type="button" value="Click" onclick="alert(this.value)" />
- this 值等于事件的目标元素
- 很多 HTML 事件处理程序会被封装在一个 try-catch 块中
# 13.2.2 DOM 0 级 事件处理程序
每个元素都有自己的事件处理程序属性,通常全部小写,例如 onclick 将这种属性的值设置为一个函数即可指定事件处理程序
var btn = document.getElementById("myBtn");
btn.onclick = function() {};
2
- DOM 0 级 指定的事件处理程序被认为是元素的方法
# 13.2.3 DOM 2 级 事件处理程序
DOM 2 级事件定义了两个方法:addEventListener()和 removeEventListener()
btn.addEventListener("click", handler, false);
- true = 捕获 / false = 冒泡 (默认)
- 通过 add 添加的事件处理程序只能通过 remove 移除
# 13.2.4 IE 事件处理程序
IE 实现了类似 DOM 中的方法 attachEvent()/detachEvent()
btn.attachEvent("onclick", handler);
与 DOM 的区别:attachEvent()的 this = window!!
# 13.2.5 跨浏览器 事件处理程序
依次判断顺序:
- DOM2
- IE
- DOM0
// 参数:注册元素,事件种类,事件处理程序
function addHandler(ele, type, handler) {
if (element.addEventListener) {
element.addEventListner(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
}
2
3
4
5
6
7
8
9
10
# 13.3 事件对象
在触发 DOM 上某个事件时,会产生一个事件对象 event,包含着与事件有关的信息。包括导致事件的元素、事件的类型及其他与特定事件相关的信息。
# 13.3.1 DOM 中的事件对象
兼容 DOM 的浏览器会将 event 对象传入事件处理程序中.
var btn = document.getElementById("myBtn");
btn.onclick = function(e) {};
btn.addEventListener("click", e => {}, false);
2
3
- this / event.currentTarget 都指向注册元素
- event.target 指向目标元素
- preventDefault():如阻止链接被点击会跳转等默认行为
- stopPropagation():立刻停止事件在 DOM 层次的传播
- event.eventPhase = 1/捕获 2/处于目标 3/冒泡
event 对象只有事件处理程序执行期间才会存在
# 13.3.2 IE 中的事件对象
# 13.3.3 跨浏览器 的事件对象
# 13.4 事件类型
# 13.5 内存和性能
i