摘要:構(gòu)造函數(shù)調(diào)用使用操作符來(lái)調(diào)用函數(shù)則視其為構(gòu)造函數(shù)。構(gòu)造函數(shù)的主要職責(zé)是初始化該新對(duì)象。使用方法定義高階函數(shù)允許使用者給回調(diào)函數(shù)指定接收者。當(dāng)給高階函數(shù)傳遞對(duì)象方法時(shí),使用匿名函數(shù)在適當(dāng)?shù)慕邮照呱险{(diào)用該方法。
參考書籍:《Effective JavaScript》
使用函數(shù) 理解函數(shù)調(diào)用、方法調(diào)用及構(gòu)造函數(shù)之間的不同函數(shù)、方法和構(gòu)造函數(shù)是單個(gè)構(gòu)造對(duì)象的三種不同的使用模式。
函數(shù)調(diào)用
function hello(username) { return "hello, " + username; } hello("Keyser Soze"); // hello, Keyser Soze
方法調(diào)用(JavaScript中的方法指的是對(duì)象的屬性恰好是函數(shù))
var obj = { hello: function () { return "hello, " + this.username; }, username: "Hans Gruber" }; obj.hello(); // hello, Hans Gruber
在方法調(diào)用中由調(diào)用表達(dá)式自身來(lái)確定this變量的綁定。綁定到this變量的對(duì)象被稱為調(diào)用接收者(receiver)。表達(dá)式obj.hello()在obj對(duì)象中查找名為hello的屬性,并將obj對(duì)象作為接收者,然后調(diào)用該屬性。
構(gòu)造函數(shù)調(diào)用
function User(name, passwordHash) { this.name = name; this.passwordHash = passwordHash; } var u = new User("sfalken", "0ef33ae791068ec64b502d6cb0191387"); u.name; // sfalken
使用new操作符來(lái)調(diào)用函數(shù)則視其為構(gòu)造函數(shù)。
構(gòu)造函數(shù)調(diào)用將一個(gè)全新的對(duì)象作為this變量的值,并隱式返回這個(gè)新對(duì)象作為調(diào)用結(jié)果。構(gòu)造函數(shù)的主要職責(zé)是初始化該新對(duì)象。
提示:
方法調(diào)用將被查找方法屬性的對(duì)象作為調(diào)用接收者。
函數(shù)調(diào)用將全局對(duì)象(處于嚴(yán)格模式下則為undefined)作為接收者。一般很少使用函數(shù)調(diào)用語(yǔ)法來(lái)調(diào)用方法。
構(gòu)造函數(shù)需要通過(guò)new運(yùn)算符調(diào)用,并產(chǎn)生一個(gè)新的對(duì)象作為接收者。
熟練掌握高階函數(shù)高階函數(shù)指的是將函數(shù)作為參數(shù)或返回值的函數(shù)。
[3, 1, 4, 1, 5, 9].sort(function (x, y){ if (x < y) { return -1; } if (x > y) { return 1; } return 0; }); // [1, 1, 3, 4, 5, 9]
var names = ["Fred", "Wilma", "Pebbles"], upper = names.map(function (name){ return name.toUpperCase(); }); upper; // ["FRED", "WILMA", "PEBBLES"]
創(chuàng)建高階函數(shù)抽象有很多好處。實(shí)現(xiàn)中存在的一些棘手部分,比如正確地獲取循環(huán)邊界條件,它們可以被放置在高階函數(shù)的實(shí)現(xiàn)中。這使得你可以一次性地修復(fù)所有邏輯上的錯(cuò)誤,而不必去搜尋散布在程序中的該編碼模式的所有實(shí)例。如果你發(fā)現(xiàn)需要優(yōu)化操作的效率,你也可以僅僅修改一處。
當(dāng)發(fā)現(xiàn)自己在重復(fù)地寫一些相同的模式時(shí),學(xué)會(huì)借助于一個(gè)高階函數(shù)可以使代碼更簡(jiǎn)潔、更高效和更可讀。
var aIndex = "a".charCodeAt(0), alphabet = ""; for (var i = 0; i < 26; i++) { alphabet += String.fromCharCode(aIndex + i); } alphabet; // "abcdefghijklmnopqrstuvwxyz" var digits = ""; for (var i = 0; i < 10; i++) { digits += i; } digits; // "0123456789"
function buildString(n, callback) { var result = ""; for (var i = 0; i < n; i++) { result += callback(i); } return result; } var alphabet = buildString(26, function (i){ return String.fromCharCode(aIndex + i); }); alphabet; // "abcdefghijklmnopqrstuvwxyz" var digits = buildString(10, function (i) { return i; }); digits; // "0123456789"
提示:
高階函數(shù)時(shí)那些將函數(shù)作為參數(shù)或返回值的函數(shù)。
熟悉掌握現(xiàn)有庫(kù)中的高階函數(shù)。
學(xué)會(huì)發(fā)現(xiàn)可以被高階函數(shù)所取代的常見(jiàn)的編碼模式。
使用call方法自定義接收者的調(diào)用方法通常,函數(shù)或方法的接收者(即綁定到特殊關(guān)鍵字this的值)是由調(diào)用者的語(yǔ)法決定的。然而,有時(shí)需要使用自定義接收者來(lái)調(diào)用函數(shù),因?yàn)樵摵瘮?shù)可能并不是期望的接收者對(duì)象的屬性。
幸運(yùn)的是,函數(shù)對(duì)象具有一個(gè)內(nèi)置的方法call來(lái)自定義接收者。
f.call(obj, arg1, arg2, arg3);
當(dāng)調(diào)用的方法被刪除、修改或者覆蓋時(shí),call方法就派上用場(chǎng)了。
var hasOwnProperty = {}.hasOwnProperty; dict.foo = 1; delete dict.hasOwnProperty; hasOwnProperty.call(dict, "foo"); // true hasOwnProperty.call(dict, "hasOwnProperty"); // false
定義高階函數(shù)時(shí)call方法也特別實(shí)用。
var table = { entries: [], addEntry: function (key, value) { this.entries.push({ key: key, value: value }); }, forEach: function (f, thisArg) { var entries = this.entries; for (var i = 0, n = entries.length; i < n; i++) { var entry = entries[i]; f.call(thisArg, entry.key, entry.value, i); } } };
上述例子允許table對(duì)象的使用者將一個(gè)方法作為table.forEach的回調(diào)函數(shù)f,并為該方法提供一個(gè)合理的接收者。例如,可以方便地將一個(gè)table的內(nèi)容復(fù)制到另一個(gè)中。
table1.forEach(table2.addEntry, table2);
提示:
使用call方法自定義接收者來(lái)調(diào)用函數(shù)。
使用call方法可以調(diào)用在給定的對(duì)象中不存在的方法。
使用call方法定義高階函數(shù)允許使用者給回調(diào)函數(shù)指定接收者。
使用apply方法通過(guò)不同數(shù)量的參數(shù)調(diào)用函數(shù)函數(shù)對(duì)象配有一個(gè)類似的apply方法。
var scores = getAllScores(); average.apply(null, scores);
如果scores有三個(gè)元素,那么以上代碼的行為與average(scores[0], scores[1], scores[2])一致。
apply方法也可用于可變參數(shù)方法。
var buffer = { state: [], append: function () { for (var i = 0, n = arguments.length; i < n; i++) { this.state.push(arguments[i]); } } };
借助于apply方法的this參數(shù),我們可以指定一個(gè)可計(jì)算的數(shù)組調(diào)用append方法:buffer.append.apply(buffer, getInputString())。
提示:
使用apply方法指定一個(gè)可計(jì)算的參數(shù)數(shù)組來(lái)調(diào)用可變參數(shù)的函數(shù)。
使用apply方法的第一個(gè)參數(shù)給可變參數(shù)的方法提供一個(gè)接收者。
使用arguments創(chuàng)建可變參數(shù)的函數(shù)function averageOfArray(a) { for (var i = 0, sum = 0, n = a.length; i < n; i++) { sum += a[i]; } return sum / n; } averageOfArray([2, 7, 1, 8, 2, 8, 1, 8]); // 4.625
JavaScript給每個(gè)函數(shù)都隱式地提供了一個(gè)名為arguments的局部變量。arguments對(duì)象給實(shí)參提供了一個(gè)類似數(shù)組的接口。它為每個(gè)實(shí)參提供了一個(gè)索引屬性,還包含一個(gè)length屬性用來(lái)指示參數(shù)的個(gè)數(shù)。
function average() { for (var i = 0, sum = 0, n = arguments.length; i < n; i++) { sum += arguments[i]; } return sum / n; } average([2, 7, 1, 8, 2, 8, 1, 8]); // 4.625
可變參數(shù)函數(shù)提供了靈活的接口。但是,如果使用者想使用計(jì)算的數(shù)組參數(shù)調(diào)用可變參數(shù)的函數(shù),只能使用apply方法。好的經(jīng)驗(yàn)法是,如果提供了一個(gè)便利的可變參數(shù)的函數(shù),也最好提供一個(gè)需要顯式指定數(shù)組的固定元數(shù)的版本。我們可以編寫一個(gè)輕量級(jí)的封裝,并委托給固定元數(shù)的版本來(lái)實(shí)現(xiàn)可變參數(shù)的函數(shù)。
function average() { return averageOfArray(arguments); }
提示:
使用隱式地arguments對(duì)象實(shí)現(xiàn)可變參數(shù)的函數(shù)。
考慮對(duì)可變參數(shù)的函數(shù)提供一個(gè)額外的固定元數(shù)的版本,從而使得使用者無(wú)需借助apply方法。
永遠(yuǎn)不要修改arguments對(duì)象function callMethod(obj, method) { var shift = [].shift; // 移除arguments的前兩個(gè)元素 shift.call(arguments); shift.call(arguments); // 使用剩余的參數(shù)調(diào)用對(duì)象的指定方法 return obj[method].apply(obj, arguments); } var obj = { add: function (x, y) { return x + y; } }; callMethod(obj, "add", 17, 25); // error: cannot read property "apply" of undefined
上述代碼出錯(cuò)的原因是arguments對(duì)象并不是函數(shù)參數(shù)的副本。特別是,所有的命名參數(shù)都是arguments對(duì)象中對(duì)應(yīng)索引的別名。因此,即使通過(guò)shift方法移除arguments對(duì)象中的元素之后,obj仍然是arguments[0]的別名,method仍然是arguments[1]的別名。
在ES5嚴(yán)格模式下,函數(shù)參數(shù)不支持對(duì)其arguments對(duì)象取別名。
function strict(x) { "use strict"; arguments[0] = "modified"; return x === arguments[0]; } function nonstrict(x) { arguments[0] = "modified"; return x === arguments[0]; } strict("unmodified"); // false nonstrict("unmodified"); // true
因此,永遠(yuǎn)不要修改arguments對(duì)象。通過(guò)一開(kāi)始復(fù)制參數(shù)中的元素到一個(gè)真正的數(shù)組的方式,可以避免修改arguments對(duì)象。
function callMethod(obj, method) { /* 當(dāng)不適用額外的參數(shù)調(diào)用數(shù)組的slice方法時(shí),它會(huì)復(fù)制整個(gè)數(shù)組,其結(jié)果是一個(gè)真正的標(biāo)準(zhǔn)Array類型實(shí)例 */ var args = [].slice.call(arguments, 2); return obj[method].apply(obj, args); } var obj = { add: function (x, y) { return x + y; } }; callMethod(obj, "add", 17, 25); // 42
提示:
永遠(yuǎn)不要修改arguments對(duì)象。
使用[].slice.call(arguments)將arguments對(duì)象復(fù)制到一個(gè)真正的數(shù)組中再進(jìn)行修改。
使用變量保存arguments的引用迭代器(iterator)是一個(gè)可以順序存取數(shù)據(jù)集合的對(duì)象。其一個(gè)典型的API是next方法,該方法獲得序列中的下一個(gè)值。假設(shè)我們編寫一個(gè)函數(shù),它可以接收任意數(shù)量的參數(shù),并為這些值建立一個(gè)迭代器。
function values() { var i = 0, n = arguments.length; return { hasNext: function () { return i < n; }, next: function () { if (i >= n) { throw new Error("end of iteration"); } return arguments[i++]; // wrong arguments } } } var it = values(1, 4, 1, 4, 2, 1, 3, 5, 6); it.next(); // undefined it.next(); // undefined it.next(); // undefined
一個(gè)新的arguments變量被隱式地綁定到每個(gè)函數(shù)體內(nèi)。我們感興趣的arguments對(duì)象是與values函數(shù)相關(guān)的那個(gè),但是迭代器的next方法含有自己的arguments。所以當(dāng)返回arguments[i++]時(shí),我們?cè)L問(wèn)的是it.next的參數(shù),而不是values函數(shù)中的參數(shù)。
解決方案只需在我們感興趣的arguments對(duì)象作用域綁定一個(gè)新的局部變量,并確保嵌套函數(shù)只能引用這個(gè)顯式命名的變量。
function values() { var i = 0, n = arguments.length, a = arguments; return { hasNext: function () { return i < n; }, next: function () { if (i >= n) { throw new Error("end of iteration"); } return a[i++]; } } } var it = values(1, 4, 1, 4, 2, 1, 3, 5, 6); it.next(); // 1 it.next(); // 4 it.next(); // 1
提示:
當(dāng)引用arguments時(shí)當(dāng)心函數(shù)嵌套層級(jí)。
綁定一個(gè)明確作用域的引用到arguments變量,從而可以在嵌套的函數(shù)中引用它。
使用bind方法提取具有確定接收者的方法var buffer = { entries: [], add: function (s) { this.entries.push(s); }, concat: function () { return this.entries.join(""); } }; var source = ["867", "-", "5309"]; source.forEach(buffer.add); // error: entries is undefiend
上述例子中,對(duì)象的方法buffer.add被提取出來(lái)作為回調(diào)函數(shù)傳遞給高階函數(shù)Array.prototype.forEach。但是buffer.add的接收者并不是buffer對(duì)象。事實(shí)上,forEach方法的實(shí)現(xiàn)使用全局對(duì)象作為默認(rèn)的接收者。
所幸,forEach方法運(yùn)行調(diào)用者提供一個(gè)可選的參數(shù)作為回調(diào)函數(shù)的接收者。
var source = ["867", "-", "5309"]; source.forEach(buffer.add, buffer); buffer.join(); // 867-5309
函數(shù)對(duì)象的bind方法需要一個(gè)接收者對(duì)象,并產(chǎn)生一個(gè)以該接收者對(duì)象的方法調(diào)用的方式調(diào)用原來(lái)的函數(shù)的封裝函數(shù)。
var source = ["867", "-", "5309"]; source.forEach(buffer.add.bind(buffer)); buffer.join(); // 867-5309
記住,buffer.add.bind(buffer)創(chuàng)建了一個(gè)新函數(shù)而不是修改了buffer.add函數(shù)。
提示:
要注意,提取一個(gè)方法不會(huì)將方法的接收者綁定到該方法的對(duì)象上。
當(dāng)給高階函數(shù)傳遞對(duì)象方法時(shí),使用匿名函數(shù)在適當(dāng)?shù)慕邮照呱险{(diào)用該方法。
使用bind方法創(chuàng)建綁定到適當(dāng)接收者的函數(shù)。
使用bind方法實(shí)現(xiàn)函數(shù)柯里化TODO...
使用閉包而不是字符串來(lái)封裝代碼function f() {} function repeat(n, action) { for (var i = 0; i < n; i++) { eval(action); } } function benchmark() { var start = [], end = [], timings = []; repeat(1000, "start.push(Date.now()); f(); end.push(Date.now())"); for (var i = 0, n = start.length; i < n; i++) { timings[i] = end[i] - start[i]; } return timings; } benchamrk(); // Uncaught ReferenceError: start is not defined
上述代碼會(huì)導(dǎo)致repeat函數(shù)引用全局的start和end變量。
更健壯的API應(yīng)該接受函數(shù)而不是字符串。
function repeat(n, action) { for (var i = 0; i < n; i++) { action(); } } function benchmark() { var start = [], end = [], timings = []; repeat(1000, function (){ start.push(Date.now()); f(); end.push(Date.now()) }); for (var i = 0, n = start.length; i < n; i++) { timings[i] = end[i] - start[i]; } return timings; }
eval函數(shù)的另一個(gè)問(wèn)題是,一些高性能的引擎很難優(yōu)化字符串中的代碼,因?yàn)榫幾g器不能盡可能早地獲得源代碼來(lái)及時(shí)優(yōu)化代碼。然而函數(shù)表達(dá)式在其代碼出現(xiàn)的同時(shí)就能被編譯,這使得它更適合標(biāo)準(zhǔn)化編譯。
提示:
當(dāng)將字符串傳遞給eval函數(shù)以執(zhí)行它們的API時(shí),絕不要在字符串中包含局部變量引用。
接受函數(shù)調(diào)用的API優(yōu)于使用eval函數(shù)執(zhí)行字符串的API。
不要信賴函數(shù)對(duì)象的toSting方法JavaScript函數(shù)有一個(gè)非凡的特性,即將其源代碼重現(xiàn)為字符串的能力。
(function(x) { return x + 1; }).toString(); // function (x) { return x + 1; }
但是使用函數(shù)對(duì)象的toString方法有嚴(yán)重的局限性。
(function(x) { return x + 1; }).bind(16).toString(); // function () { [native code] }
(function(x) { return function(y) { return x + y; } })(42).toString(); // function (y) { return x + y; }
提示:
當(dāng)調(diào)用函數(shù)的toString方法時(shí),并沒(méi)有要求JavaScript引擎能夠精確地獲取到函數(shù)的源代碼。
由于在不同的引擎下調(diào)用toString方法的結(jié)果可能不同,所以絕不要信賴函數(shù)源代碼的詳細(xì)細(xì)節(jié)。
toString方法的執(zhí)行結(jié)果并不會(huì)暴露存儲(chǔ)在閉包中的局部變量值。
通常情況下,應(yīng)該避免使用函數(shù)對(duì)象的toString方法。
避免使用非標(biāo)準(zhǔn)的棧檢查屬性每個(gè)arguments對(duì)象都包含兩個(gè)額外的屬性:arguments.callee和arguments.caller。前者指向使用該arguments對(duì)象被調(diào)用的函數(shù),后者指向調(diào)用該arguments對(duì)象的函數(shù)。
arguments.callee除了允許匿名函數(shù)遞歸地引用其自身之外,無(wú)更多用途了。
var factorial = function (n) { return (n <= 1) ? 1 : (n * arguments.callee(n - 1)); };
但是這并不是很有用,因?yàn)楦苯拥姆绞绞鞘褂煤瘮?shù)名來(lái)引用函數(shù)自身。
var factorial = function (n) { return (n <= 1) ? 1 : (n * factorial(n - 1)); };
arguments.caller在大多數(shù)環(huán)境中已經(jīng)被移除了,但許多JavaScript環(huán)境也提供了一個(gè)相似的函數(shù)對(duì)象屬性——非標(biāo)準(zhǔn)但普遍適用的caller屬性,它指向函數(shù)最近的調(diào)用者。
function revealCaller() { return revealCaller.caller; } function start() { return revealCaller(); } start() === start; // true
使用函數(shù)的caller屬性來(lái)獲取棧跟蹤(stack trace)是很有誘惑力的。棧跟蹤是一個(gè)提供當(dāng)前調(diào)用棧快照的數(shù)據(jù)結(jié)構(gòu)。
function getCallStack() { var stack = []; for (var f = getCallStack.caller; f; f = f.caller) { stack.push(f); } return stack; } function f1() { return getCallStack(); } function f2() { return f1(); } var trace = f2(); trace; // [f1, f2]
但是如果某個(gè)函數(shù)在調(diào)用棧中出現(xiàn)了不止一次,那么棧檢查邏輯將會(huì)陷入循環(huán)。
function f(n) { return n === 0 ? getCallStack() : f(n - 1); } var trace = f(1); // infinite loop
在ES5的嚴(yán)格模式下,棧檢查屬性是禁止使用的。
function f() { "use strict"; return f.caller; } f(); // Uncaught TypeError: "caller", "callee", and "arguments" properties may not be accessed on strict mode functions or the arguments objects for calls to them
提示:
避免使用非標(biāo)準(zhǔn)的arguments.caller和arguments.callee屬性,因?yàn)樗鼈儾痪邆淞己玫囊浦残浴?/p>
避免使用非標(biāo)準(zhǔn)的函數(shù)對(duì)象caller屬性,因?yàn)樵诎織P畔⒎矫妫遣豢煽康摹?/p>
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/94502.html
摘要:實(shí)際上,系統(tǒng)維護(hù)了一個(gè)按事件發(fā)生順序排列的內(nèi)部事件隊(duì)列,一次調(diào)用一個(gè)已注冊(cè)的回調(diào)函數(shù)。提示異步使用回調(diào)函數(shù)來(lái)延緩處理代價(jià)高昂的操作以避免阻塞主應(yīng)用程序。這具有幾乎立刻將回調(diào)函數(shù)添加到事件隊(duì)列上的作用。 參考書籍:《Effective JavaScript》 并發(fā) 在JavaScript中,編寫響應(yīng)多個(gè)并發(fā)事件的程序的方法非常人性化,而且強(qiáng)大,因?yàn)樗褂昧艘粋€(gè)簡(jiǎn)單的執(zhí)行模型(有時(shí)稱為事件...
摘要:類是由一個(gè)構(gòu)造函數(shù)和一個(gè)關(guān)聯(lián)的原型組成的一種設(shè)計(jì)模式。該模式的一個(gè)缺點(diǎn)是,為了讓構(gòu)造函數(shù)中的變量在使用它們的方法的作用域內(nèi),這些方法必須放置于實(shí)例對(duì)象中,這會(huì)導(dǎo)致方法副本的擴(kuò)散。 參考書籍:《Effective JavaScript》 對(duì)象和原型 理解prototype、getPrototypeOf和__proto__之間的不同 原型包括三個(gè)獨(dú)立但相關(guān)的訪問(wèn)器。 C.prototy...
摘要:前言月份開(kāi)始出沒(méi)社區(qū),現(xiàn)在差不多月了,按照工作的說(shuō)法,就是差不多過(guò)了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了一般來(lái)說(shuō),差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議那么今天我就把看過(guò)的一些學(xué)習(xí)資源主要是博客,博文推薦分享給大家。 1.前言 6月份開(kāi)始出沒(méi)社區(qū),現(xiàn)在差不多9月了,按照工作的說(shuō)法,就是差不多過(guò)了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了!一般來(lái)說(shuō),差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議!那么今天我就...
摘要:現(xiàn)在,我們將會(huì)剖析的工作原理,而最重要的是它和在性能方面的比對(duì)加載時(shí)間,執(zhí)行速度,垃圾回收,內(nèi)存使用,平臺(tái)訪問(wèn),調(diào)試,多線程以及可移植性。目前,是專門圍繞和的使用場(chǎng)景設(shè)計(jì)的。目前不支持多線程。 原文請(qǐng)查閱這里,略有改動(dòng),本文采用知識(shí)共享署名 4.0 國(guó)際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工作原理的第六章...
閱讀 2780·2021-09-23 11:44
閱讀 1671·2021-09-13 10:24
閱讀 2619·2021-09-08 09:36
閱讀 1231·2019-08-30 15:54
閱讀 2248·2019-08-30 13:54
閱讀 3308·2019-08-30 10:57
閱讀 1844·2019-08-29 18:43
閱讀 3609·2019-08-29 15:10