因爲全部的函數都是對象,因此使用函數的指針很是簡單javascript
場景:假設一個頁面有多個 iframejava
array 是 window 的屬性數組
/* value是一個數組的狀況下,且必須與Array在同一個全局做用域下才會返回true 若是value是在另外一個ifream下定義的數組,那麼返回false */
var isArray = value instanceof Array;
複製代碼
任何值上面調用 object 的 toString()方法都會返回一個[object NativeConstructorName]格式的字符串,每一個類在內部都有一個[[class]]
屬性,指定了上述字符串中構造函數名瀏覽器
alert(Object.prototype.toString.call(value)); // "[object Array]"
//原生數組的構造函數與全局做用域無關,可使用toString()保證返回一致的值
function isArray(value) {
return Object.prototype.toString.call(value) == "[object Array]";
}
function isFunction(value) {
return Object.prototype.toString.call(value) == "[object Function]";
}
function isRegExp(value) {
return Object.prototype.toString.call(value) == "[object RegExp]";
}
複製代碼
這一技巧也被被普遍的應用於檢測原生 JSON 對象,Object 的 toString 方法不能檢測非原生構造函數的構造函數名,所以開發人員定義的任何的構造函數都將返回 [obje0ct object]安全
var isNativeJSON=window.JSON && Object.prototype.toString.call(JSON)==「[Onject JSON]」
複製代碼
當沒有使用 new 操做符來建立的時候,this 對象是在運行時候綁定的,直接調用的話,this 會映射到 window 全局對象上構造函數當作普通函數去調用,這個問題是 this 晚綁定產生的,這裏的 this 解析成了 window 對象bash
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
var p = Person("cc", 18, "coder");
console.log(window.name);
console.log(window.age);
console.log(window.job);
複製代碼
如下方式不管 Person 是否使用 new 操做符調用,都會返回一個新的實例對象,這就避免了在全局對象上意外的設置屬性閉包
function Person(name, age, job) {
if (this instanceof Person) {
this.name = name;
this.age = age;
this.job = job;
} else {
return new Person(name, age, job);
}
}
複製代碼
使用這個模式能夠鎖定構造函數的做用域。若是使用了構造函數竊取模式的繼承且不使用原型鏈,那麼這個繼承有可能會被破壞app
//多邊形類 ,此構造函數的做用域是安全的
function Polygon(sides) {
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function() {
return 0;
};
} else {
return new Polygon(sides);
}
}
//矩形類
function Rectangle(width, height) {
Polygon.call(this, 2); //構造函數不是做用域安全的,this並不是Polygon的實例
this.width = width;
this.height = height;
this.getArea = function() {
return this.width * this.height;
};
}
var rect = new Rectangle(5, 10);
console.log(rect.sides); //undefined
console.log(rect.getArea()); //50
console.log(rect); //Rectangle {width: 5, height: 10, getArea: ƒ}
複製代碼
解決辦法是
結合使用原型鏈或者寄生組合
//多邊形類
function Polygon(sides) {
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function() {
return 0;
};
} else {
return new Polygon(sides);
}
}
//矩形類
function Rectangle(width, height) {
Polygon.call(this, 2);
//原型鏈,rect是Rectangle實例,也是Polygon的實例,call執行,sides被添加
this.width = width;
this.height = height;
this.getArea = function() {
return this.width * this.height;
};
}
Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
console.log(rect.sides); //undefined
console.log(rect.getArea()); //50
console.log(rect);
複製代碼
產生背景:大多數瀏覽器之間的行爲差別,致使多數 js 代碼包含了大量的 if 語句,將執行引導到正確的代碼中,因此若是 if 沒必要每次執行,那麼代碼能夠運行的更快一些。ide
function createXHR() {
if (typeof XMLHttpRequest != "undefined") {
//...
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined") {
//...
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("error message");
}
}
複製代碼
解決方案就稱爲惰性載入的技巧: 表示函數執行的分支只會發生一次。函數
這兩種方式都能避免執行沒必要要的代碼,惰性載入函數的優勢只執行一次 if 分支,避免了函數每次執行時候都要執行 if 分支和沒必要要的代碼,所以提高了代碼性能,至於那種方式更合適,就要看您的需求而定了。
function createXHR() {
if (typeof XMLHttpRequest != "undefined") {
createXHR = function() {
//...
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined") {
createXHR = function() {
//...
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
createXHR = function() {
throw new Error("error message");
};
}
return createXHR();
}
複製代碼
var createXHR = (function() {
if (typeof XMLHttpRequest != "undefined") {
return function() {
//...
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined") {
return function() {
//...
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
throw new Error("error message");
}
})();
複製代碼
該技巧經常和回調函數和事件處理程序一塊兒使用,以便將函數做爲變量傳遞的同時保留代碼執行環境
//事件兼容封裝
var EventUtil = {
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
//獲取事件對象
getEvent: function(event) {
return event ? event : window.event;
},
//獲取目標元素
getTarget: function(event) {
return event.target || event.srcElement;
},
//阻止事件默認行爲
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
//解除監聽
removeHandler: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
//阻止冒泡
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation;
} else {
event.cancelBubble = true;
}
}
};
複製代碼
如下時間處理程序,在點擊 button 後會彈出 undefined,這個問題在於沒有保存 handler.handleClcik()的環境,因此 this 指向了 DOM 按鈕而非 handler,咱們能夠採用閉包來修正這個問題
var handler = {
message: "event handler",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("btn");
EventUtil.addHandler(btn, "click", handler.handleClick);
//閉包修正
EventUtil.addHandler(btn, "click", function(event) {
handler.handleClick();
});
複製代碼
可是大多數使用可能致使代碼的難以調試和理解。因此下面咱們使用 bind 解決,大多數的 js 庫實現了一個能夠將函數綁定到指定環境的函數,通常叫作 bind()
// 自定義bind函數接受一個函數和一個環境
function bind(fn, context) {
return function() {
return fn.apply(context, arguments);
};
}
var handler = {
message: "event handler",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));
複製代碼
ES5 爲全部函數提供了一個原生 bind 方法進一步簡化了操做,可是被綁定函數比普通函數相比有更多的開銷,須要更多的內存
var handler = {
message: "event handler",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
複製代碼
用於建立已設置好了一個或多個參數的函數,函數柯里化的基本方法和函數綁定是同樣的:使用一個閉包返回一個函數
如下 curriedAdd 函數雖然並非柯里化的函數,可是很好的展示了其概念
function add(x, y) {
return x + y;
}
function curriedAdd(y) {
return add(5, y);
}
console.log(add(1, 2)); //3
console.log(curriedAdd(5)); //10
複製代碼
柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。
咱們改造下,實際上就是把 add 函數的 x,y 兩個參數變成了先用一個函數接收 x 而後返回一個函數去處理 y 參數。如今思路應該就比較清晰了,就是只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。
// 普通的add函數
function add(x, y) {
return x + y;
}
// Currying後
function curriedAdd(x) {
return function(y) {
return x + y;
};
}
add(1, 2); // 3
curriedAdd(1)(2); // 3
複製代碼
柯里化函數一般的動態建立方式
/* arguments是一個關鍵字,表明當前參數,在javascript中雖然arguments表面上以數組形式來表示,但實際上沒有原生數組slice的功能,這裏使用call方法算是對arguments對象不完整數組功能的修正。 slice返回一個數組,該方法只有一個參數的狀況下表示除去數組內的第一個元素。就本上下文而言,原數組的第一個參數是「事件名稱」,具體像「click」,"render"般的字符串,其後的元素纔是處理函數所接納的參數列表。 */
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
};
}
function add(x, y) {
return x + y;
}
var curryAdd = curry(add, 5);
console.log(curryAdd(1)); //6
var curryAdd2 = curry(add, 1, 5); //兩個參數都提供了,就無需在傳遞了
console.log(curryAdd2()); //6
// ------支持多參數傳遞---------
function progressCurrying(fn, args) {
var _this = this;
var len = fn.length;
var args = args || [];
return function() {
var _args = Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, _args);
// 若是參數個數小於最初的fn.length,則遞歸調用,繼續收集參數
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 參數收集完畢,則執行fn
return fn.apply(this, _args);
};
}
複製代碼
用於函數綁定的一部分構造更復雜的 bind 函數
function bind(fn, context) {
var args = Array.prototype.slice.call(arguments, 2);
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(context, finalArgs);
};
}
複製代碼
柯里化好處:
//將第一個參數reg進行復用,這樣別的地方就可以直接調用hasNumber,hasLetter等函數,讓參數可以複用,調用起來也更方便
// 正常正則驗證字符串 reg.test(txt)
// 函數封裝後
function check(reg, txt) {
return reg.test(txt);
}
check(/\d+/g, "test"); //false
check(/[a-z]+/g, "test"); //true
// Currying後
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt);
};
}
var hasNumber = curryingCheck(/\d+/g);
var hasLetter = curryingCheck(/[a-z]+/g);
hasNumber("test1"); // true
hasNumber("testtest"); // false
hasLetter("21212"); // false
複製代碼
缺點:
存取arguments對象一般要比存取命名參數要慢一點
一些老版本的瀏覽器在arguments.length的實現上是至關慢的
使用fn.apply( … ) 和 fn.call( … )一般比直接調用fn( … ) 稍微慢點
建立大量嵌套做用域和閉包函數會帶來花銷,不管是在內存仍是速度上
複製代碼
開發人員可能意外修改別人的代碼,因此 ES5 提供了防篡改對象定義, 一旦把對象定義爲防篡改。就沒法撤銷了
var person = { name: "cc" };
object.preventExtensions(person);
person.name = "bb";
console.log(person.name);
複製代碼
未完持續中......