注意
- 聲明提前(hoisting)
var scope = "global";
function f() {
console.log(scope); // -> undefined, 該作用域中scope已定義,但在這個(gè)地方還未賦值
var scope = "local";
console.log(scope); // -> local
}
- null和undefined不能包含屬性
- with語(yǔ)句
o = {"x" : 0};
with(o) x = 1; // o.x = 1
with(o) y = 2; // 定義了全局變量y
// 即with語(yǔ)句提供訪問對(duì)象屬性的簡(jiǎn)便方法,但并不創(chuàng)建對(duì)象屬性
- 函數(shù)的call方法
f = (function (x) {
return this.a + x;
})
(function () {
this.a = 20;
f.call(this, 30); // 50
f.apply(this, [22]) // 42
} ())
對(duì)象
JavaScript類型有數(shù)字,字符串,布爾(true/false), null和undefined. 所有其它值均為對(duì)象。
Object Literals
- 空對(duì)象: {}
- 非空對(duì)象為大括號(hào)擴(kuò)住的鍵值對(duì): {name : value, ...}
- 值value部分可以是Object Literal
- 鍵name部分可以是任意字符串, 若name是一個(gè)合法的JavaScript標(biāo)志符且不是保留字,則引號(hào)可以省略
- 原型為Object.prototype
取值
- obj["name"]
- obj.name
更新值
- obj["name"] = value
- obj.name = value
引用
- 對(duì)象通過引用傳遞
原型
每個(gè)對(duì)象關(guān)聯(lián)到一個(gè)原型對(duì)象,并且它可以從該對(duì)象繼承屬性. 所有創(chuàng)建自O(shè)bject Literal的對(duì)象均關(guān)聯(lián)到Object.prototype.
當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),你可以選擇作為它原型的對(duì)象.
// create方法接收一個(gè)對(duì)象,并以改對(duì)象為原型創(chuàng)建一個(gè)新的對(duì)象
if (typeof Object.create !== 'function') {
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
}
原型關(guān)系只在取值操作中起作用. 當(dāng)試圖獲取對(duì)象的某個(gè)不存在的屬性,JavaScript轉(zhuǎn)而從該對(duì)象的原型獲取該屬性,若仍不存在,繼續(xù)從原型的原型獲取,以此類推,若一直到Object.prototype都沒有該屬性,則返回undefined
反射
typeof "hello" // 'string'
typeof 1 // 'number'
typeof {} // 'object'
typeof undefined // 'undefined'
typeof ''.toString // 'function'
typeof 會(huì)沿著原型鏈查找屬性,若不希望這樣可以使用hasOwnProperty屬性
枚舉
for (var in aobject) {...}會(huì)枚舉對(duì)象aobject的所有屬性,包括函數(shù)和原型屬性,且對(duì)屬性的枚舉順序不定。當(dāng)然也可以通過將想要枚舉的屬性放在列表中使用for而不是for in進(jìn)行枚舉:
var i;
var properties = [
'first-name',
'middle-name',
'last-name',
'profession'
];
for (i = 0; i < properties.length; i += 1) {
document.writeln(properties[i] + ': ' + another_stooge[properties[i]]);
}
刪除
delete操作符用于刪除對(duì)象的屬性,它不會(huì)干涉原型
減少全局變量的使用
減少全局變量的使用可以通過創(chuàng)建一個(gè)單一的全局變量作為你應(yīng)用的容器:
var MyApp = {};
MyApp.var1 = ...;
MyApp.var2 = ...;
還可以使用閉包減少全局變量的使用
函數(shù)
函數(shù)對(duì)象
- JavaScript中的函數(shù)是對(duì)象
- 原型為Function.prototype,F(xiàn)unction.prototype的原型為Object.prototype
Function Literals
函數(shù)通過function literal創(chuàng)建
var add = function(a, b) {
return a+b;
};
調(diào)用
在調(diào)用函數(shù)時(shí),除了聲明的形參之外,還接收了兩個(gè)參數(shù): this和arguments。this的值取決于調(diào)用模式,在JavaScript中有四種調(diào)用模式:
- 方法調(diào)用模式(method invocation pattern)
當(dāng)一個(gè)函數(shù)作為一個(gè)屬性存儲(chǔ)在對(duì)象中,稱其為方法,當(dāng)方法被調(diào)用時(shí),this綁定到該對(duì)象上
var myObject = {
value: 0,
increment: function (inc) {
this.value += typeof inc === 'number' ? inc : 1;
} };
myObject.increment( );
document.writeln(myObject.value); // 1
myObject.increment(2);
document.writeln(myObject.value); // 3
方法可以通過this訪問對(duì)象的屬性, this和對(duì)象的綁定時(shí)在方法調(diào)用時(shí)完成的
- 函數(shù)調(diào)用模式(function invocation pattern)
若函數(shù)不是對(duì)象的屬性,對(duì)其的調(diào)用為函數(shù)調(diào)用。這種模式的調(diào)用,this被綁定到全局對(duì)象上(global object),這種設(shè)計(jì)使得嵌套函數(shù)中內(nèi)部函數(shù)調(diào)用時(shí)不能直接使用this獲取調(diào)用它的函數(shù)的上下文,當(dāng)然這可通過簡(jiǎn)單的方法解決
myObject.double = function () {
var that = this; // Workaround.
var helper = function () {
that.value = add(that.value, that.value);
};
helper(); // Invoke helper as a function.
};
myObject.double( ); // Invoke double as a method.
document.writeln(myObject.getValue()); // 6
- 構(gòu)造函數(shù)調(diào)用模式(constructor invocation pattern)
如果一個(gè)函數(shù)通過new前綴調(diào)用,一個(gè)新的對(duì)象被創(chuàng)建,該對(duì)象隱式關(guān)聯(lián)到該函數(shù)的原型成員,this被綁定到該新建的對(duì)象
var Quo = function(string) {
this.status = string;
};
Quo.prototype.get_status = function() {
return this.status;
};
var myQuo = new Quo("Confused");
document.writeln(myQuo.get_status()); // "Confused"
通過前綴new調(diào)用的函數(shù)被稱為構(gòu)造函數(shù)(constructor)
- apply調(diào)用模式(apply invocation pattern)
JavaScript中函數(shù)可以有方法,函數(shù)的apply方法可以用來(lái)進(jìn)行函數(shù)調(diào)用,它接收兩個(gè)參數(shù),第一個(gè)時(shí)this要綁定的對(duì)象,第二個(gè)是調(diào)用函數(shù)所需的參數(shù)列表
var add = function(a, b) {return a+b;};
var array = [1,2];
var sum = add.apply(null, array); // sum = 3
var statusObject = {"status" : "OK"};
Quo.prototype.get_status.apply(statusObject) // "OK"
(function () {return this.status;}).apply(statusObject) // "OK"
arguments
函數(shù)的隱式參數(shù)arguments里存放了所有的調(diào)用參數(shù),它不是數(shù)組,只是一個(gè)類似列表的對(duì)象,它有l(wèi)ength屬性,但缺少數(shù)組的其它屬性
返回
函數(shù)可以通過return語(yǔ)句返回一個(gè)值,若是通過構(gòu)造函數(shù)模式調(diào)用(new),沒有通過return返回對(duì)象,則this被返回
異常
var add = function (a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw {
name: 'TypeError',
message: 'add needs numbers'
};
}
return a + b;
}
var try_it = function () { try {
add("seven");
} catch (e) {
document.writeln(e.name + ': ' + e.message);
}
}
try_it( );
throw語(yǔ)句應(yīng)該接收一個(gè)異常對(duì)象,它至少包含兩個(gè)屬性,name表明異常類型和一個(gè)描述異常的message屬性。異常對(duì)象可以被catch語(yǔ)句捕捉
作用域
var foo = function() {
var a = 3, b = 5; // 此時(shí)a=3, b=5
var bar = function() {
var b = 7, c = 11; // 此時(shí)a=3, b=7, c=11
a += b + c; // 此時(shí)a=21, b=7, c=11
};
bar(); // 此時(shí)a=21, b=5
}
在函數(shù)內(nèi)部定義的變量,函數(shù)外部不可見,對(duì)函數(shù)內(nèi)的所有區(qū)塊都可見。
閉包
對(duì)于JavaScript的作用域,其好的部分是,一個(gè)函數(shù)的內(nèi)部定義的函數(shù)能夠獲取該函數(shù)的除this和arguments的所有參數(shù)和變量的值。
一種極為有趣的情形是內(nèi)部函數(shù)可以有比它外部函數(shù)更長(zhǎng)的生命周期:
var myObject = function () {
var value = 0;
return {
"increment": function(inc) {
value += typeof inc === "number" ? inc : 1;
},
"getValue": function() {
return value;
}
};
}();
Callbacks
// 異步處理請(qǐng)求
request = prepare_the_request(); // 準(zhǔn)備請(qǐng)求
send_request_asynchronously(request, function(response) {
deal_with(response);
}); // 異步發(fā)送請(qǐng)求,將處理請(qǐng)求響應(yīng)的回調(diào)函數(shù)作為參數(shù)傳入
模塊
一個(gè)模塊是提供接口但是隱藏狀態(tài)和實(shí)現(xiàn)的函數(shù)或者對(duì)象??梢酝ㄟ^函數(shù)和閉包構(gòu)造模塊。通過函數(shù)構(gòu)造模塊可以幾乎完全消除全局變量,因而可以規(guī)避JavaScript最糟糕的特性。
例如我們要給字符串增加一個(gè)方法deentityify,它用于翻譯HTML實(shí)體到對(duì)應(yīng)的字符。最直觀的實(shí)現(xiàn)方式就是將實(shí)體和對(duì)應(yīng)的字符存放在一個(gè)對(duì)象中,但是在什么地方存放這個(gè)對(duì)象呢?放在全局變量中?呵...,放在函數(shù)中?每次調(diào)用函數(shù)時(shí)解釋器該對(duì)象要重新求值。理想的方式是放在閉包中,甚至可以添加一個(gè)方法用于添加新的HTML實(shí)體
// 給Function.prototype添加一個(gè)新的方法method,該方法接收兩個(gè)參數(shù)
// 字符串name和函數(shù)func。于是所有的函數(shù)都增加了這樣一個(gè)方法(因?yàn)樗?// 函數(shù)的原型鏈的底端都是Function.prototype)
// 當(dāng)一個(gè)函數(shù)調(diào)用這個(gè)method方法時(shí),該函數(shù)為自己的原型添加了一個(gè)
// 名為name的方法(參考上面的方法調(diào)用模式)
Function.prototype.method = function (name, func) {
this.prototype[name] = func;
return this;
};
// 為String的原型添加deentityify方法
String.method("deentityify", function(){
var entity = {
quot: '"',
lt: '<',
gt: '>'
};
return function () {
return this.replace(/&([^&;]+);/g,
function (a, b) {
var r = entity[b];
return typeof r === 'string' ? r : a;
});
};
}());
'<">'.deentityify() // -> <">
該實(shí)現(xiàn)中entity變量?jī)Hdeentityfy方法可以訪問
模塊的一般模式----將一個(gè)函數(shù)作為模塊,在該模塊函數(shù)中可以定義一些私有變量和函數(shù),另有一些特權(quán)函數(shù),這些函數(shù)作為模塊函數(shù)的返回值或者被存放在外部可見的地方,這些特權(quán)函數(shù)可以通過閉包訪問模塊函數(shù)中的私有變量和函數(shù)
這種模式可以消除全局變量,還可以用于生成安全的對(duì)象
var serial_maker = function () {
var prefix = '',
var seq = 0;
return {
set_prefix: function (p) {
prefix = String(p);
},
set_seq: function (s) {
seq = s; },
gensym: function () {
var result = prefix + seq; seq += 1;
return result;
}
};
};
var seqer = serial_maker();
seqer.set_prefix = ('Q';)
seqer.set_seq = (1000);
var unique = seqer.gensym();
如果將上面的seqer.gensym提供給第三方使用,該函數(shù)可以生成唯一的標(biāo)識(shí)符但是前綴和序列號(hào)不可更改。
級(jí)聯(lián)(cascade)
如果一個(gè)方法沒有返回值,我們可以令其返回this(即返回調(diào)用該方法的對(duì)象自己),這樣我們就開啟了級(jí)聯(lián)模式。
Curry
JavaScript中可以這樣實(shí)現(xiàn)curry
Function.method("curry", function() {
// 之前提到過arguments不是數(shù)組,所以這里需要將它轉(zhuǎn)換為數(shù)組(為了使用concat方法)
var slice = Array.prototype.slice,
args = slice.apply(arguments),
that = this;
return function() {
return that.apply(null, args.concat(slice.apply(arguments)));
};
});
Memorization
考慮遞歸求解Fibonacci數(shù)列
var fibonacci = function (n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};
這種求解方法非常低效,太多的工作被重做,記住已做的工作:
var fibonacci = function() {
var memo = [0, 1];
var fib = function(n) {
var result = memo[n];
if (typeof result !== 'number') {
result = fib(n-1) + fib(n-2);
memo[n] = result;
}
return result
};
return fib;
}();
這種方式可以一般化成memorizer,自行實(shí)現(xiàn)吧
func = memorizer(init_values, func)
繼承
創(chuàng)建函數(shù)對(duì)象時(shí),生成函數(shù)對(duì)象的構(gòu)造函數(shù)執(zhí)行類似下面的操作:
this.prototype = {constructor: this};
生成的函數(shù)對(duì)象被賦予了一個(gè)屬性prototype,其(屬性prototype)值為一個(gè)對(duì)象,該對(duì)象有值為生成的函數(shù)對(duì)象的屬性constructor。
函數(shù)的構(gòu)造函數(shù)模式調(diào)用(new前綴調(diào)用)若以函數(shù)的方法實(shí)現(xiàn)的話,可以這樣實(shí)現(xiàn):
Function.method("new", function() {
# 創(chuàng)建一個(gè)以構(gòu)造函數(shù)原型為原型的對(duì)象
var that = Object.create(this.prototype);
# 調(diào)用構(gòu)造構(gòu)造函數(shù),綁定構(gòu)造函數(shù)的this到新建對(duì)象that
var other = this.apply(that, arguments);
# 構(gòu)造函數(shù)返回的是對(duì)象返回該對(duì)象,否則返回新建對(duì)象that
return (typeof other === 'object' && other) || that;
})