JavaScript高级程序设计学习笔记
1.对JavaScript实现各个组成部分的详尽解读
2.对JavaScript面向对象编程的全方位阐述
3.DOM,BOM及浏览器事件模型的透彻剖析
4.Web应用基本数据格式JSON,XML及存取
5.Ajax,Comet服务器端通信和基于File API的拖放式文件上传
6.ECMAScript 5定义的最核心语言特性
7.HTML 5涵盖的表单,媒体,Canvas(包括WebGL)
8.Selectors、Web Workers、地理定位及跨文档传递消息等最新API
9.离线应用及客户端存储(包括IndexedDB)
10.维护、性能、部署相关的最佳开发实践
11.新兴API及ECMAScript Harmony展望
对JavaScript面向对象编程的全方位阐述
理解对象
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);
Object.defineProperties();//2
_year前面的下划线是一种常用记号,用于表示只能通过对象方法访问的属性。
创建对象
由于使用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("ann",12,"asdf");
var person2=createPerson("jean",15,"erty");
person1.sayName();
person2.sayName();
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("ann",12,"asdf");
var person2=new Person("jean",15,"erty");
person1.sayName();//ann
person2.sayName();//jean
与工厂模式的不同
- 没有显式的创建数据
- 直接将属性和方法赋值给this对象
- 没有return语句
可以利用instanceof(可靠)和constructor来测试对象类型。
alert(person1.constructor==Person);//true
alert(person1 instanceof Person);//true
alert(person1 instanceof Object);//true
构造函数和普通函数的区别
构造函数的缺点
由于ECMAJavaScript中的函数是对象,因此定义一个函数也就是实例化一个对象,所以每个方法都要在每个实例上重新创建。
如何克服构造函数的缺点
将函数定义在构造函数的外侧
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("ann",12,"asdf");//ann
var person2=new Person("jean",15,"erty");//jean
3.原型模式
每个函数都存在prototype(原型)属性,这个属性是一个指针,指向对象。
对象的优点:可以共享所有实例的属性和方法
function Person(name,age,job) {
Person.prototype.name="ann";
Person.prototype.age=12;
Person.prototype.job="asdf";
Person.prototype.sayName=function () {
alert(this.name);
};
}
var person1=new Person();
person1.sayName();
这里创建的person2会覆盖掉person1.指针只指向person2
理解原型对象
每个函数都有一个prototype(原型)属性,这个属性指向原型对象,每个原型对象都会自动获取一个constructor(构造函数)属性。而构造函数的原型对象之间,存在_proto_属性。如图所示:
- 为对象实例添加属性,此时会自动屏蔽原型对象中保存的同名属性,换句话说,我们为实例添加属性只会阻止我们访问原型对象的同名属性,而不会修改原型对象的同名属性
- 可以使用hasOwnProperty()方法判断属性是否来自原型
- 更简单的写法:用一个包含所有属性和方法的对象字面量重写整个原型对象,但是这种方法无法使用constructor属性,即
alert(person.constructor==Person);//false
function Person() {
}
Person.prototype={
name:"ann",
age:24,
job:"artist",//可以加入constructor:Person,
sayName:function () {
alert(this.name);
}
};
var person1=new Person();
person1.sayName();
原型的动态性
- 在原型中查找值是一次搜索的过程
- 对原型对象的修改会反映在实例上。
- 由于实例和原型对象之间的松散关系,实例与原型对象创建的位置可以不确定
- 但是如果全部重写整个原型对象
var person=new Person();
则是修改了_proto_指针,则切断了原型对象和实例间的关系
原型对象的原型
原型模式重要性不仅体现在自定义方面,就连原生的引用类型,都是采用这种模式创建
原型对象的问题
- 省略构造函数传递初始化这一环节
- 共享特性导致无法单独修改其中一个实例的属性
4.组合使用构造函数模式和原型模式(目前使用最多)
function Person(name,age,job) {
this.name=name;
this.age=age;
this.job=job;
this.friends=["jean","mary"];
}
Person.prototype={
constructor:Person,
sayName:function () {
alert(this.name);
}
}
var person1=new Person("ann",12,"asd");
person1.sayName();
(1)将属性值利用构造函数写,这样就可以单独修改其中一个实例的属性值 (2)将函数放在原型模式中创建,这样就可以防止反复创建
5.动态原型模式
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);
}
}
}
var person1=new Person("ann",12,"asd");
person1.sayName();
这里对于函数进行判断,只有在sayName()方法不存在的时候,才开始执行这个函数,即只有在第一次才开始调用函数。
6.寄生构造函数模式(不建议使用)
function Person(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=new Person("ann",12,"asd");
person1.sayName();
6.稳妥构造函数模型(安全环境下使用)
- 稳妥:没有公共属性;
- 安全环境:禁止使用this和new;
function Person(name,age,job) {
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function () {
alert(name);
};
return o;
}
var person1= Person("ann",12,"asd");
person1.sayName();
继承(i`m important)
许多语言都支持两种继承方式:接口继承和实现继承。ECMAJavaScript只支持实现继承,而实现继承主要依靠原型链实现。
1.原型链【★★】
其主要思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。下面是构造函数、原型对象和实例间的关系:
当让原型对象等于另一个类型的实例,根据上图,会形成一个实例和原型的链条,这就是所谓的原型链。代码:SubTyp.prototype=new SuperType();
关系图如下:
- 读取模式访问一个实例属性,首先会在实例中搜索该属性。如果没找到会继续向上搜索,直到末端结束。
别忘记默认原型
所有函数默认原型是Object的实例,默认原型都包含一个内部指针,指向Object.prototype,职业是所有自定义类型都会继承toString()、valueOf()等默认方法的原因。
确认原型和实例的关系
- instanceof:用这个操作符测试实例和原型链中出现过的构造函数
alert(instance instanceOf Object);//true
- isPrototypeOf():用这个方法,只要是原型链中出现的原型,都可以说是该原型链所派生的实例的原型
alert(Object.prototype.isPrototypeOf(instance));//true
谨慎的定义方法
- 添加新方法:getSubValue()
subType.prototype.getSubValue=function(){return this.subproperty;}
- 重写后会覆盖掉原来的方法
- 不能使用对象字面量创建原型方法
原生链的问题
引用类型值的原型→包含引用类型值的原型属性会被所有实例共享→这就是为什么不在原型对象,而是在构造函数中定义属性的原因
也就是说继承后的SubType.prototype会变成SuperType的一个实例,而SubType所有实例都共享属性,无法单独给一个实例添加属性值
2.借用构造函数(也叫伪造函数或经典继承)【★】
借用构造函数的基本思想:在子类型构造函数的内部调用超类型构造函数。使用apply()和call()方法在新创建的对象上执行构造函数。
- 借用构造函数的优势:相对于原生链,借用构造函数最大的优势,可以在子类型构造函数向超类型构造函数传递参数
- 借用构造函数的问题:函数复用问题
function SuperType(name) {
this.name=name;
}
function SubType() {
//继承了SuperType,同时传递参数
SuperType.call(this,"ann");
this.color=["red","yellow"];
}
var instance1=new SubType();
alert(instance1.name);//ann
instance1.color.push("pink");
alert(instance1.color);//red,yellow,pink
var instance2=new SubType();
alert(instance2.color);//red,yellow
3.组合继承(也叫伪经典继承)【★★★】
意思是指:将原型链和借用构造函数的技术组合在一起。背后的思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承
function SuperType(name,age) {
this.name=name;
this.age=age;
this.color=["red","blue"];
}
SuperType.prototype.sayName=function () {
alert(this.name);
}
//构造函数继承属性,可以继续添加新属性
function SubType(name,age) {
SuperType.call(this,name,age);
}
//原生链继承方法
SubType.prototype=new SuperType();
SubType.prototype.sayAge=function () {
alert(this.age);
}
var instance1=new SubType("ann",29);
instance1.color.push("yellow");
alert(instance1.color);//red,blue,yellow
instance1.sayName();//ann
instance1.sayAge();//29
var instance2=new SubType("jean",34);
alert(instance2.color);red,blue
instanceof和isPrototypeOf()也能够识别基于组合继承创建的对象
4.原型式继承【☆】
借助原型可以基于已有对象创建新对象,同时还不必因此创建自定义类型
function object(o) {
function F() {}
F.prototype=o;
return new F();
}
var person={
name:"ann",
friend:["van","jean"]
}
var instance1=object(person);
instance1.name="asd";
alert(instance1.name);//asd
instance1.friend.push("123");
var instance2=object(person);
alert(instance2.name);//ann
alert(instance2.friend);//van,jean,123
5.寄生式继承【☆】
创建一个仅用于封装继承过程的函数
6.寄生组合式继承【★★】
由于组合继承无论什么情况都会调用超过两次超类型构造函数(一次在创建子类型原型的时候,另一次是在子类型构造函数内部)
所谓寄生组合式集成,即通过借用构造函数来继承属性,通过原型链的混合形式来继承方法。本质是使用寄生式继承来继承超类型的原型,然后将结果指定给子类型的原型
function object(o) {
function F() {}
F.prototype=o;
return new F();
}
function inheritPrototype(subType,superType) {
var prototype=object(superType.prototype);//创建对象
prototype.constructor=subType;//增强对象
subType.prototype=prototype;//指定对象
}
function SuperType(name,age) {
this.name=name;
this.age=age;
this.color=["red","blue"];
}
SuperType.prototype.sayName=function () {
alert(this.name);
}
function SubType(name,age) {
SuperType.call(this,name,age);
}
inheritPrototype(SubType,SuperType);
var instance1=new SubType("ann",29);
instance1.color.push("yellow");
alert(instance1.color);//red,blue,yellow
instance1.sayName();//ann
var instance2=new SubType("jean",34);
alert(instance2.color);red,blue
只调用了一次构造函数并且不用再SubType.prototype上创建不必要的、多余的属性,同时原生链保持不变。
小结
继承方法 | 主要步骤 |
---|---|
原生链 | SubType.prototype=new SuperType() |
借用构造函数 | function SubType(){ SuperType.call(this);} |
组合继承 | 构造函数继承属性;原生链继承方法 |
原型式继承 | function object(o) { function F() { F.prototype=o; return new F();} |
寄生式组合式继承 | function inheritPrototype(subType,superType) { var prototype=object(superType.prototype); prototype.constructor=subType; subType.prototype=prototype;} |
- 这其中有哪些是属性共享的?