《JavaScript高级程序设计》笔记第一部分(ECMA5)

JavaScript分三部分:

ECMAScript
BOM
DOM

浏览器组成:

shell
内核

主流浏览器:

IE				trident
Chrome			webkit/blink
firefox			Gecko
Opera			presto
Safari			webkit

编译性语言:看完再一次性编译成01的编译文件
优点:快
不足:移植性不好(不跨平台)

解释性语言:看一行编译一行,不需要生成执行文件
优点:跨平台
不足:稍微慢

JavaScript引擎是单线程的
ECMA制定JavaScript标准

原始值 stack(栈)
Number String Boolean undefined null
拷贝关系

引用值 heap(堆)
Array function Object data RegExp
栈存放地址,堆存放数据

在HTML中使用JavaScript:

script元素属性:

  • async(可选):表示应该立即下载脚本,但不应妨碍页面中的其他操作。
  • charset(可选):表示通过src属性指定的代码的字符集。
  • defer(可选):表示脚本可以延迟到文档完全被解析和显示之后再执行。
  • language(已废弃):原来用于表示编写代码使用的脚本语言(如JavaScript、JavaScript1.2或VBScript)。
  • src(可选):表示包含要执行代码的外部文件。 type(可选):可以看成是language的替代属性。

值:text/javascript和text/ecmascript(不被推荐使用)
服务器在传送JavaScript文件时使用的MIME类型通常是application/x-javascript

延迟脚本:defer属性:defer=“defer” 告诉浏览器立即下载,但延迟执行。
异步脚本:async属性:async 只适用于外部脚本文件,并告诉浏览器立即下载文件。标记位async的脚本并不保证按照指定他们的先后顺序执行。
建议异步脚本不要在加载期间修改DOM。
元素:在浏览器不支持JavaScript时显示的一段信息或内容。

基本概念要点

语法

类似C语言和Java。

区分大小写

ECMAScript中的一切(变量、函数名和操作符)都区分大小写。

数据类型

可以使用typeof操作符得知

  • undefined 这个值未定义
  • boolean 这个值是布尔值
  • number 这个值是数值
  • object 这个值是对象或null
  • function 这个值是函数
  • String 这个值是字符串

操作符

一元操作符

递增递减操作符
++ –
一元加和减操作符

+ -

位操作符

按位非(NOT):~
按位与(AND):&
按位或(OR):|
按位异或(XOR):^
左移:<<
有符号右移:>>
无符号右移:>>>

布尔操作符

逻辑非:!
逻辑与:&&
逻辑或:||

乘性操作符

乘法:*
无穷:Infinity;
Infinity * 0 = NaN;
有一个操作数是NaN,结果是NaN;
Infinity * Infinity = Infinity;

除法:/
有一个操作数是NAN,结果是NaN;
Infinity / Infinity = NaN;
0 / 0 = NaN;

求模(余数):%
被除数是Infinity,除数是有限大的数,结果是NaN;
除数是0,被除数是有限大,结果NaN;
Infinity / Infinity = NaN;

加性操作符
加法:+
一个操作数是NaN,结果是NaN;
Infinity + Infinity = Infinity;
-Infinity + (-Infinity) = -Infinity;
-Infinity + Infinity = NaN;
+0 + (+0) = (+0);
-0 + (-0) = (-0);
+0 + (-0) = (+0);

减法:-
Infinity - Infinity = NaN;
-Infinity - (-Infinity) = NaN;
+0 - (+0) = (+0);
-0 - (-0) = (+0);

关系操作符

小于(<)、大于(>)、小于等于(<=)、大于等于(>=)

相等操作符

相等(==)和不相等(!=)

值相等,会自动转换:
布尔值<——数值<——字符串
null == undefined;

全等(===)和不全等(!==)

类型和值都相同,是否是同一个引用。
条件操作符:? : ;
赋值操作符:=

*=
/=
%=
+=
-=
<<=
>>=
>>>=

语句

if语句
do-while语句
while语句
for语句
for-in语句
ECMAScript对象的属性没有顺序,因此,通过for-in循环输出的属性名的顺序是不可预测的。
label语句
使用label语句可以在代码中添加标签,以便将来使用。
语法:label:statement;

start:for (var i=0; i < count; i++){
    alert(i);
}

break和continue语句

with语句

将代码的作用域设置到一个特定的对象中。
语法:with (expression) statement;

with(location){
    var qs = search.substring(1);
    var hostName = hostname;
    var url = href;
}

严格模式下不允许使用with语句,否则将视为语法错误。
switch语句

可以在switch语句中使用任何数据类型。

函数

function
定义参数的数量和使用函数时传入的参数的数量可以不一样。
命名的参数只提供便利,但不是必需的。
通过访问argument对象的length属性可以获知有多少个参数传递给了函数。
argument的值永远与对应命名参数的值保持同步。
没有重载

基本类型

Undefined

Null

Boolean

Number

String

动态的属性

定义基本类型值和引用类型值的方式是类似的。
引用类型的值。可以添加属性和方法,基本类型的值则不可以。

复制变量值

在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。(相当于创建了一个副本)。
基本类型数据存在栈(steak)里面,引用数据地址存在栈(steak)里面,object内容存在堆(hook)里面,所以多个引用指向同一个object。

传递参数

ECMAScript中所有函数的参数都是按值传递的(基本类型复制模式)。
访问变量有按值和按引用两种方式,而参数只能按值传递。

function addTen(num){
    num += 10;
    return num;
}
var count = 20;
var result = addTen(count);
alert(count);       //20,没有变化
alert(result);      //30
    function setName(obj){
        obj.name = "Nicholas";
    }
    var person = new Object();
    setName(person);
    alert(person.name);     //"Nicholas"
function setName(obj){
    obj.name = "Nicholas";
    obj = new Object();
    obj.name = "Greg";
}
var person = new Object();
setName(person);
slert(person.name);     //"Nicholas"

instanceof

确认对象是什么类型的对象。

执行环境及作用域

全局执行环境是最外围的一个执行环境
当代码在一个环境中执行时,会创建变量对象的一个作用域链。
作用域链的用途:保证对执行环境有权访问的所有变量和函数的有序访问。
全局执行环境的变量对象始终都是作用域链中的最后一个对象。
每个函数都有自己的执行环境。
在局部作用域中定义的变量可以在局部环境中与全局变量互换使用。

延长作用域链

当执行流进入下列任何一个语句时,作用域链就会得到加长:(在作用域链前端临时增加一个变量对象,该变量对象会在代码执行后被移除)
1.try-catch语句的catch块;
2.with语句。
JavaScript没有块级作用域

if(true){
    var color = "blue";
}
alert(color);       //"blue"
    for(var i=0; i < 10; i++){
        doSomething(i);
    }
    alert(i);           //10

声明变量

如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境。
查询标识符

var color = "blue";
function getColor(){
    return color;
}
alert(getColor());  //"blue"

垃圾收集

JavaScript具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。

标记清除

JavaScript中最常用的垃圾收集方式是标记清除。

当变量进入环境时,就将这个变量标记为”进入环境“。当变量离开环境时,就将其标记为”离开环境“。
另一种不太常见的垃圾收集策略叫做引用计数。

引用计数的含义是跟踪记录每个值被引用的次数。
不足:循环引用的存在导致对象永远存在。

myObject.element = null;
element.someObject = null;

管理内存

分配给Web浏览器的可用内存数量通常要比分配给桌面应用程序的少。
一旦数据不再有用,最好通过将其值设置为null来释放其引用——这个做法叫做解除引用

引用类型

1.Object类型

var person = new Object();
person.name = "Nicholas";
person.age = 29;
    var person = {
        name : "Nicholas",
        "age" : 29,
        5 : true
    };
var person = {};
person.name = "Nicholas";
person.age = 29;

两种调用属性值的方法:

person["name"];
person.name;

1.通过方括号语法的主要优点是可以通过变量来访问属性

var propertyName = "name";
alert(person[propertyName]);

2.属性名中包含导致语法错误的字符,或者属性名使用的是关键字或保留字:

person["first name"] = "Nicholas";

通常,除非必须使用变量来访问属性,否则建议使用点表示法。

Array类型

ECMAScript数组的大小是可以动态调整的。
在使用Array构造函数时也可以省略new操作符。
数组的项数保存在其length属性中。
length属性不是只读的。

检测数组

Array.isArray()方法

转换方法

所有对象都具有toLocaleString()、toString()和valueOf()方法。
使用join()方法,则可以使用不同的分隔符来构建这个字符串:
join()方法只接收一个参数,即用作分隔符的字符串。

var colors = ["red","green","blue"];
alert(colors.join(","));    //red,green,blue
alert(colors.join("||"));   //red||green||blue

栈方法

LIFO(Last-In-First-Out,后进先出)
栈中项的插入(叫做推入)和移除(叫做弹出),只发生在一个位置——栈的顶部。
ECMAScript为数组专门提供了push()和pop()方法,以实现类似栈的行为。

队列方法

队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出)。
结合使用shift()和push()方法,可以像队列一样使用数组。

四个方法

push()方法:接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。
pop()方法:从数组末尾移除最后一项,减少数组的length值,然后返回移除的项。
shift()方法:从数组中取得第一项并返回该项,同时将数组长度减1.
unshift()方法:在数组前端添加任意个项并返回新数组的长度。

反方向队列

使用unshift()与pop()方法。

重排序方法

reverse()和sort()方法:
reverse()方法:反转数组项的顺序。
sort()方法:
默认:按升序排列数组项。会调用每个数组项的toString()转型方法,然后比较得到的字符串,以确定如何排序。
sort()方法可以接收一个比较函数作为参数,以便外面指定哪个值位于哪个值的前面。
升序的方法

function compare(value1,value2){
    if(value1 < value2){
        return -1;
    } else if(value1 > value2){
        return 1;
    } else {
        return 0;
    }
}
var values = [0,1,5,10,15];
values.sort(compare);
alert(values);      //0,1,5,10,15

降序

function compare(value1,value2){
    if(value1 < value2){
        return 1;
    } else if(value1 > value2){
        return -1;
    } else {
        return 0;
    }
}

reverse()和sort()方法的返回值是经过排序之后的数组。

操作方法

ECMAScript为操作已经包含在数组中的项提供了很多方法。
concat()方法:可以基于当前数组中的所有项创建一个新数组。
slice()方法:能够基于当前数组中的一或多个项创建一个数组。
只有一个参数:
返回从该参数指定位置开始到当前数组末尾的所有项。

两个参数
该方法返回起始和结束位置之间的项——但不包括结束位置的项。
splice()方法(最强大的数组方法):
主要用途是向数组的中部插入项。3种方式:

删除
可以删除任意数量的项,只需指定2个参数:
要删除的第一项的位置和要删除的项数。

插入
可以向指定位置插入任意数量的项,只需3个参数:
起始位置、0(要删除的项数)、要插入的项
要插入多个项,可以再传入第四、第五、第六,以至任意多个项。

替换
可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3个参数:
起始位置、要删除的项数和要插入的任意数量的项。
插入的项数不必与删除的项数相等。

位置方法

indexOf()和lastIndexOf()
使用全等操作符

迭代方法

ECMAScript5为数组定义了5个迭代方法,每个方法都接收两个参数:
1.要在每一项上运行的函数
2.运行该函数的作用域对象——影响this的值。(可选的)
传入这些方法中的函数会接收三个参数:
1.数组项的值
2.该项在数组中的位置
3.数组对象本身

5个方法

every():对数组中的每一项运行给定函数。如果该函数对每一项都返回true,则返回true。
filter():对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组。
forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。

归并方法

reduce()和reduceRight()
这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。
reduce()方法:从数组的第一项开始,逐个遍历到最后。
reduceRight()方法:从数组的最后一个开始,向前遍历到第一项。
接收两个参数:
1.在每一项上调用的函数
2.作为归并基础的初始值(可选的)
函数接收4个参数:
1.前一个值
2.当前值
3.项的索引
4.数组对象
这个函数返回的任何值都会作为第一个参数自动传给下一项。

var values = [1,2,3,4,5];
var sum = values.reduce(function(prev,cur,index,array){
    return prev + cur;
});
alert(sum);     //15

Date类型

Data类型的两个方法:
Data.parse()

var someData = new Date(Date.parse("May 25,2004"));

等价于

var someDate = new Date("May 25,2004");

Data.UTC()

var y2k = new Date(Date.UTC(2000,0));
//表示GMT时间2000年1月1日午夜零时
var y2k = new Date(2000,0);
//本地时间2000年1月1日午夜零时
var allFives = new Date(Date.UTC(2005,4,5,17,55,55));
//表示GMT时间2005年5月5日下午5:55:55
var allFives = new Date(2005,4,5,17,55,55);
//本地时间2005年5月5日下午5:55:55

ECMAScript5添加了Date.now()方法。

使用+操作符获取Date对象的时间戳
//获得开始时间
var start = +new Date();
//调用函数
doSomething();
//取得停止时间
var stop = +new Date();
    result = top - start;

日期格式化方法:

toDateString()
toTimeString()
toLocaleDateString()
toLocaleTimeString()
toUTCString()

RegExp类型

ECMAScript通过RegExp类型来支持正则表达式。

var expression = / pattern / flags;

三种模式:g、i、m
g:表示全局(global)模式。
模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止。
i:表示不区分大小写(ease-insensitive)模式。
在确定匹配项时忽略模式与字符串的大小写。
m:表示多行(multiline)模式。
在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。

/*
 *匹配字符串中所有“at”的实例
 */
var pattern1 = /at/g;

/*
 *匹配第一个“bat”或“cat”,不区分大小写
 */
var pattern2 = /[ba]at/i;

/*
 *匹配所有以“at”结尾的3个字符的组合,不区分大小写
 */
var pattern3 = /.at/gi;

正则表达式中的元字符:( [ { \ ^ $ | ) ? * + . ] }

/*
 *匹配第一个“[bc]at”,不区分大小写
 */
var pattern2 = /\[bc\]at/i;
/*
 *匹配所有“.at”,不区分大小写
 */
var pattern4 = /\.at/gi;

另一种创建正则表达式的方式是使用RegExp构造函数。

/*
 *匹配第一个"bat"或"cat",不区分大小写
 */
var pattern1 = /[bc]at/i;

/*
 *与pattern1相同,只不过是使用构造函数创建的
 */
var pattern2 = new RegExp("[bc]at","i");

所有元字符都必须双重转义。
ECMAScript5明确规定,使用正则表达式字面量必须像直接调用RegExp构造函数一样,每次都创建新的RegExp实例。

RegExp实例属性

global:布尔值,表示是否设置了g标志。
ignoreCase:布尔值,表示是否设置了i标志。
lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0算起。
multiline:布尔值,表示是否设置了m标志。
source:正则表达式的字符串表示,按字面量形式而非传入构造函数中的字符串模式返回。

RegExp实例方法

exec():该方法专门为捕捉组而设计的。
接收一个参数:要应用模式的字符串。
返回包含第一个匹配项信息的数组,没有匹配项的情况下返回null。
返回的Array实例包含两个额外属性:index和input。
input:表示应用正则表达式的字符串。

var text = "mom and dad and bady";
var pattern = /mom( and dad ( and bady)?)?/gi;

var matches = pattern.exec(text);
alert(matches.index);    //0
alert(matches.input);    //"mom and dad and bady"
alert(matches[0]);       //"mam and dad and bady"
alert(matches[1]);       //" and dad and bady"
alert(matches[2]);       //" and bady"

因为整个字符串本身与模式匹配,所以返回的数组matches的index属性值为0.
在同一个字符串上多次调用exec()将始终返回第一个匹配项的信息。
在设置全局标志的情况下,每次调用exec()则都会在字符串中继续查找新的匹配项。
RegExp实例继承的toLocaleString()和toString()方法都会返回正则表达式的字面量,与创建正则表达式的方式无关。

RegExp构造函数属性

input:$_ 最近一次要匹配的字符串。 
lastMatch:$& 最近一次的匹配项。 
lastParen:$+ 最近一次匹配的捕获组。 
leftContext:$ˋ input字符串中lastMatch之前的文本。 
multiline:$* 布尔值,表示是否所有表达式都使用多行模式。 
rightContext:$' Input字符串中lastMatch之后的文本。 
9个用于存储捕获组的构造函数属性:$1、$2、$3.....$9 

Function类型

函数实际上是对象。每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。
函数是对象,函数名是指针。

没有重载

将函数名想象为指针,也有助于理解为什么ECMAScript中没有函数重载的概念。

函数声明与函数表达式

解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。
函数声明提升(function declaration hoisting):读取并将函数声明添加到执行环境中。

作为值的函数

函数可以作为值来使用。
可以将一个函数作为另一个函数的结果返回。
可以从一个函数中返回另一个函数。

函数内部属性

在函数内部,有两个特殊的对象:argument和this。
argument:类数组对象,包含这传入函数中的所有参数。有一个名为callee的属性,该属性是一个指针,指向拥有这个argument对象的函数。

实例:
实现阶乘(消除紧密耦合的现象)

function factorial(num){
    if(num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1);
    }
}

this引用的是函数执行环境对象——或者也可以说是this值(当在网页的全局作用域中调用函数时,this对象引用的就是window)。
ECMAScript5也规范化了另一个函数对象的属性:caller。
这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null。
为了实现更松散的耦合,也可以通过arguments.callee.caller来访问相同的信息。

函数属性和方法

每个函数都包含两个属性:length和prototype。
length属性:函数希望接收的命名参数的个数。
对于ECMAScript中的引用类型而言,prototype属性是保存它们所有实例方法的真正所在。
在ECMAScript5中,prototype属性是不可枚举的,因此使用for-in无法发现。

每个函数都包含两个非继承而来的方法:apply()和call()
用途:都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
apply()方法接收两个参数:
1.在其中运行函数的作用域
2.参数数组(可以说Array的实例或arguments对象)
call()方法接收参数的方式:
1.this值
2.其余参数都直接传递给函数。
在使用call()方法的情况下,函数必须明确地传入每一个参数。
这个两个方法能够扩充函数赖以运行的作用域。
使用call()(或apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。
ECMAScript5还定义了一个方法:bind()
这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。

基本包装类型

3个特殊的引用类型:Boolean、Number和String。
每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。

var s1 = "some text";
var s2 = s1.substring(2);

(1)创建String类型的一个实例;
(2)在实例上调用指定的方法;
(3)销毁这个实例。

var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;

引用类型与基本包装类型的主要区别就是对象的生存期。

var s1 = "some text";
s1.color = "red";
alert(s1.color);    //undefined

对基本包装类型的实例调用typeof会返回"object",而且所有基本包装类型的对象在转换为布尔类型时指都是true。

面对对象的程序设计

ECMA-262对对象的定义:
无序属性的集合,其属性可以包含基本值、对象或者函数。

1.理解对象

创建自定义对象的最简单方式就是创建一个Object的实例,然后再为它添加属性和方法。
面向对象的程序设计
ECMA-262对对象的定义:
无序属性的集合,其属性可以包含基本值、对象或者函数。

var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
 	alert(this.name);
};

用对象字面量语法:

var person = {
 	name: "Nicholas";
 	age: 29;
 	job: "Software Engineer";
 	sayName: function(){
 		alert(this.name);
 	}
};

属性类型

ECMAScript中有两种属性:数据属性和访问器属性。
1.数据属性
数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性。
[[Configurable]]、[[Enumerable]]、[[Writable]]、[[Value]]。
要修改属性默认的特性,必须使用ECMAScript5的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"

一旦把属性定义为不可配置的,就不能再把它变回可配置了。

2.访问器属性
访问器属性不包含数据值;它们包含了一对儿getter和setter函数。
访问器属性4个特性:[[Configurable]]、[[Enumerable]]、[[Get]]、[[Set]]。
访问器属性不能直接定义,必须使用Object.defineProperty()来定义。

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

_year前面的下划线是一种常用的记号,用来表示只能通过对象方法访问的属性。
在defineProperty()方法之前,要创建访问器属性的两个方法:defineGetter()和__defineSetter__()。

var book = {
 	_year: 2004;
 	edition: 1;
};
//定义访问器的旧有方法
book.__defineGetter__("year",function(){
 	return this._year;
});
book.__defineSetter__("year",function(){
 	if(newValue > 2004) {
 		this._year = newValue;
 		this.edition += newValue - 2004;
 	}
});
book.year = 2005;
alert(book.edition);	//2

定义多个属性

Object.defineProperties()方法。

var book = {};
Object.defineProperties(book,{
 	_year: {
 		writable: true,
 		value: 2004
 	},
 	edition: {
 		writable: ture;
 		value: 1;
 	},
 	year: {
 		get: function(){
 			return this._year;
 		},
 		set: function(newValue){
 			if (newValue > 2004){
 				this._year = newValue;
 				this.edition += newValue - 2004;
 			}
 		}
 	}
});

这里的属性都是在同一时间创建的。

3.读取属性的特性
Object.getOwnPropertyDescriptor()方法。返回值是一个对象。
两个参数:
1.属性所在的对象,
2.要读取其描述符的属性名称。

var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
alert(descriptor.value);		//2004
alert(descriptor.configurable);		//false;
alert(typeof descriptor.get);		//"undefined"
var descriptor = Object.getOwnPropertyDescriptor(book,"year");
alert(descriptor.value);		//undefined
alert(descriptor.enumberable);		//false
alert(typeof descriptor.get);		//"function"

在JavaScript中,可以针对任何对象——包括DOM和BOM对象,使用Object.getOwnPropertyDescriptor()方法。

2.创建对象

Object构造函数或对象字面量创建单个对象的缺点:
使用同一个接口创建很多对象,会产生大量的重复代码。
1.工厂模式
ECMAScript中无法创建类:开发人员发明一种函数,用函数来封装以特定接口创建对象的细节。

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("Grey",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("Grey",27,"Doctor");

Person()函数与createPerson()函数不同:
1.没有显式地创建对象;
2.直接将属性和方法赋给了this对象;
3.没有return语句。

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
1.创建一个新对象;
2.将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
3.执行构造函数中的代码(为这个新对象添加属性);
4.返回新对象。

实例的constructor属性:指向Person。
构造函数模式胜过工厂模式的地方:
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。

1.将构造函数当作函数
任何函数,只要通过new操作符来调用,那它就可以作为构造函数。

//当作构造函数使用
var person = new Person("Nicholas", 29 , "Software Engineer");
person.sayName();	//"Nicholas"
//作为普通函数调用
Person("Grey",27,"Doctor"); 		//添加到window
window.sayName(); 			//"Grey"
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); 			//"Kristen"

2.构造函数的问题
每个方法都要在每个实例上重新创建一遍。

function Person(name, age, job){
 	this.name = name;
 	this.age = age;
 	this.job = job;
 	this.sayName = new Function("alert(this.name)"); 
 	//与声明函数在逻辑上是等价的
}

导致不同的作用域链和标识符解析,但创建Function新实例的机制仍然是相同的。
(不同实例上的同名函数是不相等的)

把函数定义转移到构造函数外部来解决这个问题

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("Grey",27,"Doctor");

新问题:
1.在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。
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

使用同一组属性和同一个sayName()函数。

1.理解原型对象
在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。
例:Person.prototype.constructor指向Person。

proto:这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
[[Prototype]]:当调用构造函数创建一个新实例后,该实例的内部所包含的一个指针(内部属性),指向构造函数的原型对象。
在所有实现中都无法访问到[[Prototype]]。
isPrototypeOf()方法:用来确定对象之间是否存在这种关系。

alert(Person.prototype.isPrototypeOf(person1)); 		//true

Object.getPrototypeOf()方法:返回[[Prototype]]的值。

可以通过对象实例访问保存在原型中的值,但不能通过对象实例重写原型中的值。
属性同名将会屏蔽原型中的那个属性。
使用delete操作符则可以完全删除实例属性,从而能够重新访问原型中的属性。

delete person1.name;

使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。
存在对象实例中:true

1.原型与in操作符

两种方式使用in操作符:单独使用 和 在for-in循环中使用。

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();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); 		//false
alert("name" in person1);			//true

无论属性存在于实例中还是存在于原型中。只要有都会返回true。

//判断属性是否是原型中的属性
function hasPrototypeProperty(object, name){
 	return !object.hasOwnProperty(name) && (name in object);
}

使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,包括存在于实例中的属性,也包括存在于原型中的属性。([[Enumerable]]标记为false的属性会在for-in循环中返回。)

ECMAScript5的Object.keys()方法:取得对象上所有可枚举的实例属性。
参数:一个对象。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
 	alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); 			//"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys);			//"name,age"

Object.getOwnPropertyNames()方法:得到所有实例属性,无论它是否可枚举。

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); 			//"constructor,name,age,job,sayName"

3.更简单的原型语法
用一个包含所有属性和方法的对象字面量来重写整个原型对象。

function Person(){
}
Person.prototype = {
 	name : "Nicholas",
 	age : 29,
 	job : "Software Engineer",
 	sayName : function () {
 		alert(this.name);
 	}
};

缺点:constructor属性不再指向Person了。通过constructor无法确定对象的类型。
注意:此时instanceof操作符还能返回正确的结果。

加强版:

function Person(){
}
Person.prototype = {
 	constructor : Person,
 	name : "Nicholas",
 	age : 29,
 	job : "Software Engineer",
 	sayName : function () {
 		alert(this.name);
 	}
};

不足:导致重设的constructor属性的[[Enumerable]](可枚举)特性被设置为true。

重设构造函数,只适用于ECMAScript5兼容的浏览器

Object.defineProperty(Person.prototype, "constructor", {
 	enumerable: false,
 	valus: Person;
});

4.原型的动态性
先创建了实例后修改原型也能够立即从实例上放映出来。

var friend = new Person();
Person.prototype.sayHi = function(){
 	alert("hi");
};
friend.sayHi(); 		//"hi"(没有问题!)

原因:实例于原型之间的松散连接关系。

重写整个原型对象的情况:
调用构造函数时会为实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改为另一个对象就等于切断了构造函数与最初原型之间的联系。
实例中的指针仅指向原型,而不指向构造函数。

function Person(){
}
var friend = new Person();
Person.prototype = {
 	constructor : Person,
 	name : "Nicholas",
 	age : 29,
 	job : "Software Engineer",
 	sayName : function () {
 		alert(this.name);
 	}
};
friend.sayName(); 		//error

5.原生对象的原型
原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都采用这种模式创建的。
不建议在产品化的程序中修改原生对象的原型。

6.原型对象的问题
1.省略了为构造函数传递初始化参数这一环节,所有实例在默认情况下都将取得相同的属性值。
2.原型中的所有属性是被很多实例共享的,对于包含引用类型值的属性不好。

function Person(){
}
var friend = new Person();
Person.prototype = {
 	constructor : Person,
 	name : "Nicholas",
 	age : 29,
 	job : "Software Engineer",
 	friends : ["Shelby", "Count"],
 	sayName : function () {
 		alert(this.name);
 	}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);			//"Shelby,Count,Van"
alert(person2.friends);			//"Shelby,Count,Van"
alert(person1.friends === person2.friends);		//true

4.组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。
每个实例都会有自己的一份实例属性的副本,同时又共享着对方法的引用。

function Person(name, age, job){
 	this.name = name;
 	this.age = age;
 	this.job = job;
 	this.friends = ["Shelby", "Count"],;
}
Person.prototype = {
 	constructor : Person,
 	sayName : function () {
 		alert(this.name);
 	}
}
var person1 = new Person("Nicholas", 29 , "Software Engineer");
var person2 = new Person("Grey",27,"Doctor");
person1.friends.push("Van");
alert(person1.friends);			//"Shelby,Count,Van"
alert(person2.friends);			//"Shelby,Count"
alert(person1.friends === person2.friends);		//false
alert(person1.sayName === person2.sayName);		//true

这种构造函数与原型混成的模式,是目前在ECMAScript中使用最广泛、认可度最高的一种创建自定义类型的方法。

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);
 		};
 	}
}

if里的代码只会在初次调用构造函数时才会执行。

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 friend = new Person("Nicholas", 29 , "Software Engineer");
friend.sayName(); 		//"Nicholas"

除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。
这个模式可以在特殊的情况下用来为对象创建构造函数。

function SpecialArray(){
 	//创建数组
 	var values = new Array();
 	//添加值
 	values.push.apply(values, arguments);
 	//添加方法
 	values.toPipedString = function(){
 		return this.join("|");
 	};
 	//返回数组
 	return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); 		//"red|blue|green"

说明:返回的对象与构造函数或者与构造函数的原型属性之间没有关系,不能依赖instanceof操作符来确定对象类型。

7.稳妥构造函数模式
稳妥对象:没有公共属性,其方法不引用this的对象。
应用场景:
1.安全的环境(这些环境中会禁止使用this和new)。
2.防止数据被其他应用程序(如Mashup程序)改动时使用。

与寄生构造函数模式的差异:
1.新创建对象的实例方法不引用this
2.不使用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 friend = Person("Nicholas", 29 , "Software Engineer");
friend.sayName(); 		//"Nicholas"

3.继承

由于函数没有签名,在ECMAScript中无法实现接口继承。
ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

1.原型链

基本思想:
利用原型让一个引用类型继承另一个引用类型的属性和方法。
原型对象等于另一个类型的实例。

实现原型链有一种基本模式,其代码大致如下:

function SuperType() {
 	this.property = true;
}
SuperType.prototype.getSuperValue = function() {
 	return this.property;
};
function SubType() {
 	this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
 	return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); 	//true

新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且内部还有一个指针,指向了SuperType的原型。
注意:
1.属性property在SubType Prototype里,getSuperValue()方法在SuperType Prototype里。
2.instance.constructor指向的是SuperType。

1.别忘记默认的原型
所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。

2.确定原型和实例的关系
两种方式:
1.instanceof操作符:测试实例与原型链中出现过的构造函数时返回true。
2.isPrototypeof()方法:测试实例与原型链中出现过的构造函数时返回true。

3.谨慎地定义方法

function SuperType() {
 	this.property = true;
}
SuperType.prototype.getSuperValue = function() {
 	return this.property;
};
function SubType() {
 	this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
//添加新方法
SubType.prototype.getSubValue = function() {
 	return this.subproperty;
};
//重写超类型中的方法
SubType.prototype.getSuperValue = function() {
 	return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); 	//false

给原型添加方法的代码一定要放在替换原型的语句之后。
必须在用SuperType的实例替换原型之后(原型对象等于另一个类型的实例),再定义这两个方法。
在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。

function SuperType() {
 	this.property = true;
}
SuperType.prototype.getSuperValue = function() {
 	return this.property;
};
function SubType() {
 	this.subproperty = false;
}
//继承SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
 	getSubValue : function (){
 		return this.subproperty;
 	},
 	someOtherMethod : function () {
 		return false;
 	}
};
var instance = new SubType();
alert(instance.getSuperValue()); 	/error!

4.原型链的问题
1.最主要的问题来自包含引用类型值的原型。
2.在创建子类型的实例时,不能向超类型的构造函数中传递参数。

借用构造函数

借用构造函数(constructor stealing)技术:为了解决原型中包含引用类型值所带来的问题。(有时候也叫做伪造对象或经典继承)。
思想:在子类型构造函数的内部调用超类型构造函数。

function SuperType() {
	this.colors = ["red","blue","green"];
}
function SubType() {
	//继承了SuperType
	SuperType.call(this);
}
var instancel = new SubType();
instancel.colors.push("black");
alert(instancel.colors);  
//"red blue,green,black"
var instancel2 = new SubType();
alert(instancel2.colors);
//"red blue,green"

1.传递参数
相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数。
2.借用构造函数的问题
有和构造函数一样的问题,没有函数复用。

组合继承

combination inheritance:有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块。

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 instancel = new SubType("Nicholas",29);
instancel.colors.push("black");
alert(instancel.colors);  
//"red blue,green,black"
instancel1.sayName();
//"Nicholas"
instancel1.sayAge();
//29
var instancel2 = new SubType("Greg",27);
alert(instancel2.colors);
//"red blue,green"
instancel2.sayName();
//"Greg"
instancel2.sayAge();
//27

原型式继承

道格拉斯·克罗克福
想法:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

function object(o) {
	function F(){}
	F.prototype = o;
	return new F();
}

object()对传入的对象执行了一次浅复制。
ECMAScript5通过新增Object.create()方法规范化了原型式继承。

var person = {
	name: "Nicholas",
	firends: ["Shelby", "Court", "Van"]
	};
var anotherPerson = Object.create(person, {
	name: {
		value: "Greg"
	}
});
alert(anotherPerson.name); 
//"Greg"

寄生式继承

parasitic:与原型式继承紧密相关的一种思路,并且同样也是由克罗克福推而广之的。
思路:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

function createAnother(original) {
	var clone = object(original);		//通过调用函数创建一个新对象
	clone.sayHi = function(){		//以某种方式来增强这个对象
		alert("hi");		
	};
	return clone;		//返回这个对象
}

object()函数不是必需的,任何能够返回新对象的函数都适用于此模式。

寄生组合式继承

组合继承是JavaScript最常用的继承模式。
组合继承最大是问题是无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
所谓寄生组合式继承,即通过构造函数来继承属性,通过原型链的混合形式来继承方法。

function inheritPrototype(subType, superType){
	var prototype = object(superType.prototype);		//创建对象
	prototype.constructor = subType;			//增强对象
	subType.prototype = prototype;			//指定对象
	}
  • 第一步,创建超类型原型的一个副本。
  • 第二步,为创建的副本添加constructor属性,从而弥补重写原型而失去的默认的constructor属性。
  • 最后一步,将新创建的对象(即副本)赋值给子类型的原型。

函数表达式

函数表达式是JavaScript中的一个既强大又容易令人困惑的特性。
定义函数的方式包括;函数声明和函数表达式。
函数声明,重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明。
函数声明:

function functionName(arg0,arg1,arg2){
	//函数体
}

函数表达式:

var functionName = function(arg0,arg1,arg2){
	//函数体
};

创建的函数叫做匿名函数(anonymous function),有时候也叫拉姆达函数
关于函数提升举例:

sayHi();		//错误,函数还不存在
var sayHi = function(){
	alert("Hi!");
};
sayHi();		//不会抛出错误
function sayHi(){
	alert("Hi!");
}

还要些代码的结果可能会让人意想不到

//不要这样做!
if(condition){
	function sayHi(){
		alert("Hi!");
	}
}else {
	function sayHi(){
		alert("Yo!");
	}
}

这在ECMAScript中属于无效语法,而且不同JavaScript引擎修改错误的做法并不一致。
下面是安全的代码

//可以这样做
var sayHi;
if(condition){
	sayHi = function(){
		alert("Hi!");
	};
}else {
	sayHi = function(){
		alert("Yo!");
	};
}

递归

递归函数是在一个函数通过名字调用自身的情况下构成的。
注意点:使用argument.callee代替函数名。在严格模式下,使用命名函数表达式。
最差的写法:

function factorial(num){
	if (num <= 1){
		return 1;
	} else {
		return num * factorial(num-1);
	}
}

如果把函数名置空就会发生错误:

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));		//出错!

改进代码:

function factorial(num){
	if (num <= 1){
		return 1;
	} else {
		return num * arguments.callee(num-1);
	}
}

严格模式下:

var factorial = (function f(num){
	if (num <= 1){
		return 1;
	} else {
		return num * f(num-1);
	}
});

闭包

分清匿名函数和闭包:
闭包:指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式,就是在一个函数内部创建另一个函数。
后台的每个执行环境都有一个表示变量的对象——变量对象。
AO 和 EO,全局变量对象和局部的活动对象。
作用域链被保存在内部的[[Scope]]属性中。
作用域链本质上是一个指向变量对象的指针对象。
一般活动对象在函数调用完就被销毁,闭包的作用可以使得活动对象不被销毁。
直到匿名函数被销毁后,活动对象才会被销毁。

//创建函数
var compareNames = crateComparisonFunction("name");
//调用函数
var result = compareNames({ name: "Nicholas" },{ name: "Greg" });
//解除对匿名函数的引用(以便释放内存)
compareNames = null;

闭包与变量

闭包副作用:闭包只能取得包含函数中任何变量的最后一个值。

function createFunctions(){
	var result = new Array();
	for(var i=0; i < 10; i++){
		result[i] = function(){
			return i;
		};
	}
	return result;
}

预计结果:函数数组,位置i的函数返回i。
实际:每个函数都返回10。
改进:

function createFunctions(){
	var result = new Array();
	for(var i=0; i < 10; i++){
		result[i] = function(num){
			return function(){
				return num;
			};
		}(i);
	}
	return result;
}

没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。

关于this对象

this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,当函数被作为某个对象的方法调用时,this等于那个对象。
匿名函数的执行环境具有全局性,因此this对象通常指向window。

var name = "The Window";
var object = {
	name : "My Object",
	getNameFunc : function(){
		var that = this;
		return function(){
			return that.name;
		};
	}
};
alert(object.getNameFunc()());
//"My Object"

如果想访问作用域中的arguments对象,必须将该对象的引用保存到另一个闭包能够访问的变量中。
特殊情况:

var name = "The Window";
var object = {
	name : "My Object",
	getName : function(){
		return this.name;
	}
};
object.getName();		//"My Object"
(object.getName)();		//"My Object"
(object.getName = object.getName)();		//"The Window",在非严格模式下

内存泄漏

如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。
内存永远不会被回收举例:

function assignHandler(){
	var element = document.getElementById("someElement");
	element.onclick = function(){
		alert(element.id);
	};
}

改进:

function assignHandler(){
	var element = document.getElementById("someElement");
	var id = element.id;
	element.onclick = function(){
		alert(id);
	};
	element = null;
}

模仿块级作用域

JavaScript没有块级作用域的概念。
下面的i存在函数的活动对象中:

function outputNumbers(count){
	for ( var i=0; i < count; i++){
		alert(i);
	}
	var i;  		//即使重新声明变量,也不会改变值,声明提升!
	alert(i);		//计数
}

匿名函数可以用来模仿块级作用域并避免这个问题

(function(){
	//这里是块级作用域
})();
var someFunction = function(){
	//这里是块级作用域
};
someFunction();
function(){
	//这里是块级作用域
}(); 		//出错!

加一个圆括号即可,因为函数声明后面不能跟圆括号,然而,函数表达式可以。

(function(){
	//这里是块级作用域
})(); 

无论在什么地方,只要临时需要一些变量,就可以使用私有作用域。

function outputNumbers(count){
	(function () {
		for (var i=0; i < count; i++){
			alert(i);
		}
	})();
	alert(i);		//导致一个错误!
}

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。

(function(){
	var now = new Date();
	if (now.getMonth() == 0 && now.getDate() == 1){
		alert("Happy new year!");
	}
})();

这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了

私有变量

JavaScript中没有私有成员的概念,所有对象属性都是公有的。
有私有变量的概念:任何在函数中定义的变量,都可以认为是私有变量。
使用闭包访问:创建用于访问私有变量的公有方法。
特权方法(privileged method):有权访问私有变量和私有函数的公有方法。

function MyObject(){
	//私有变量和私有函数
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//特权方法
	this.publicMethod = function(){
		peivateVariable++;
		return privateFunction();
	};
}

静态私有变量

通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。

(function(){
	//私有变量和私有函数
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//构造函数
	MyObject = function(){
	};
	//公有/特权方法
	MyObject.prototype.publicMethod = function(){
		privateVariable++;
		return privateFunction();
	};
})();

注意:这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。(全局变量,严格模式下给未经声明的变量赋值会导致错误)。

(function(){
	var name = "";
	Person = function(value){
		name = value;
	};
	Person.prototype.getName = function(){
		return name;
	};
	Person.prototype.setName = function(value){
		name = value;
	};
})();
var person1 = new Person("Nicholas");
alert(person1.getName()); 		//"Nicholas"
person1.setName("Greg");
alert(person1.getName()); 		//"Greg"
var person2 = new Person("Michael");
alert(person1.getName());		//"Michael"
alert(person2.getName());		//"Michael"

在这种模式下,变量name就变成了一个静态的、由所有实例共享的属性。
多查找作用域链中的一个层次,就会在一定程度上影响查找速度。这正是使用闭包和私有变量的一个显明的不足之处

模块模式

道格拉斯的模块模式(module pattern):为单例创建私有变量和特权方法。
单例(singleton):只有一个实例的对象。
JavaScript是以对象字面量的方式来创建单例对象的。

var singleton = {
	name : value;
	method : function(){
		//这里是方法的代码
	}
};

模块模式:

var singleton = function(){
	//私有变量和私有函数
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//公有/特权方法和属性
	return {
		publicProperty: true,
		publicMethod : function(){
			privateVariable++;
			return privateFunction();
	};
})();

在Web应用程序中,经常需要使用一个单例来管理应用程序级的信息。

var application = function(){
	//私有变量和函数
	var components = new Array();
	//初始化
	components.push(new BaseComponent());
	//公有
	return {
		getComponentCount : fonction(){
			return components.length;
		},
		registerComponent : function(component){
			if (typeof component == "object"){
				components.push(component);
			}
		}
	};
}();

getComponentCount():返回已注册的组件数目。
registerComponent():用于注册新组件。
如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。

增强的模块模式

var singleton = function(){
	//私有变量和私有函数
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//创建对象
	var object = new CustomType();
	//添加公有/特权方法和属性
	object.publicProperty = true,
	object.publicMethod = function(){
			privateVariable++;
			return privateFunction();
	};
	//返回这个对象
	return object;
})();
var application = function(){
	//私有变量和函数
	var components = new Array();
	//初始化
	components.push(new BaseComponent());
	//创建application的一个局部副本
	var app = new BaseComponent();
	//公共接口
	app.getComponentCount = fonction(){
			return components.length;
		},
	app.registerComponent = function(component){
			if (typeof component == "object"){
				components.push(component);
			}
	};
	//返回这个副本
	return app;
}();

猜你喜欢

转载自blog.csdn.net/canxuezhang/article/details/83213068