[TOC]
# instanceof
[參考鏈接](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof)
`instanceof` 運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的`prototype`屬性
`object instanceof constructor`,`object`要檢測的對象,`constructor`某個構造函數
```javascript
// 定義構造函數
function C(){}
function D(){}
var o = new C();
o instanceof C;
// true 因為Object.getPrototypeOf(o) === C.prototype
o instanceof D;
// false 因為D.prototype不在o的原型鏈上
o instanceof Object;
// true 因為Object.prototype.isPrototypeOf(o) 返回true
C.prototype instanceof Object
// true 同上
C.prototype = {};
var o2 = new C();
o2 instanceof C;
// true
o instanceof C;
// false C.prototype指向了一個空對象,這個空對象不再o的原型鏈上
D.prototype = new C(); //繼承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true
```
# Symbol 創建私有屬性
[參考鏈接1](https://developer.mozilla.org/zh-CN/docs/Glossary/Symbol) [參考鏈接2](http://es6.ruanyifeng.com/#docs/symbol)
`Symbol`為一種數據類型,可以使用此類型的值來創建匿名的對象屬性,此類型作為一個私有對象屬性的鍵,用于類或對象類型的內部使用。
```javascript
var privateVal = Symbol();
this[privateVal] = function(){};
```
該屬性為匿名且不可枚舉,訪問全局`Symbol`的方式為`Symbol.for()`和`Symbol.keyFor()`
`Symbol`值作為屬性名時,該屬性還是公開屬性,不是私有屬性
消除魔術字符串
```javascript
const shapeType = {
triangle: 'Triangle'
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 });
// 改寫為
const shapeType = {
triangle: Symbol()
};
```
# Object.create
[參考鏈接1](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create) [參考鏈接2](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
`Object.create(proto, [propertiesObject])` 方法會使用指定的原型對象及屬性去創建一個新的對象,`proto`一個對象,新創建對象的原型,`propertiesObject`一組屬性與值,該對象的屬性名稱將是新創建對象的熟悉名稱
單繼承
```javascript
//Shape - 超類
function Shape() {
this.x = 0;
this.y = 0;
}
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info("Shape moved.");
};
// Rectangle - 子類
function Rectangle() {
Shape.call(this); //超類的this指向子類.
}
// 子類繼承超類
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle; // 將構造器函數指向子類本身
var rect = new Rectangle();
console.log('Is rect an instance of Rectangle?',
rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
rect instanceof Shape); // true
rect.move(1, 1); //Outputs, "Shape moved."
```
多對象繼承
```javascript
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 繼承其中一個超類
MyClass.prototype = Object.create(SuperClass.prototype);
// 通過合并prototype繼承另一個超類
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 將prototype的構造器指向本身
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do a thing
};
```
propertyObject參數
```javascript
var o;
// 創建一個原型為null的空對象
o = Object.create(null);
o = {};
// 以字面量方式創建的空對象就相當于:
o = Object.create(Object.prototype);
o = Object.create(Object.prototype, {
// foo會成為所創建對象的數據屬性
foo: {
writable:true,
configurable:true,
value: "hello"
},
// bar會成為所創建對象的訪問器屬性
bar: {
configurable: false,
get: function() { return 10 },
set: function(value) {
console.log("Setting `o.bar` to", value);
}
}
});
function Constructor(){}
o = new Constructor();
// 上面的一句就相當于:
o = Object.create(Constructor.prototype);
// 當然,如果在Constructor函數中有一些初始化代碼,Object.create不能執行那些代碼
// 創建一個以另一個空對象為原型,且擁有一個屬性p的對象
o = Object.create({}, { p: { value: 42 } })
// 省略了的屬性特性默認為false,所以屬性p是不可寫,不可枚舉,不可配置的:
o.p = 24
o.p
//42
o.q = 12
for (var prop in o) {
console.log(prop)
}
//"q"
delete o.p
//false
//創建一個可寫的,可枚舉的,可配置的屬性p
o2 = Object.create({}, {
p: {
value: 42,
writable: true,
enumerable: true,
configurable: true
}
});
```
兼容寫法
```javascript
if (typeof Object.create !== "function") {
Object.create = function (proto, propertiesObject) {
if (!(proto === null || typeof proto === "object" || typeof proto === "function")) {
throw TypeError('Argument must be an object, or null');
}
var temp = new Object();
temp.__proto__ = proto;
if(typeof propertiesObject ==="object")
Object.defineProperties(temp,propertiesObject);
return temp;
};
}
return Object.create(proto, propertiesObject)
```
# 繼承與原型鏈
[參考鏈接1](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)
每個對象都有一個私有屬性[[Prototype]],它持有一個連接到另一個稱其為`prototype`對象的鏈接。`null`沒有`prototype`。當試圖訪問一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。
可以用`Object.getPrototypeOf()`和`Object.setPrototypeOf()`進行訪問。
當繼承的函數被調用時,`this` 指向的是當前繼承的對象,而不是繼承的函數所在的原型對象。
要清楚代碼中原型鏈的長度,并在必要時結束原型鏈,以避免可能存在的性能問題。此外,除非為了兼容新 JavaScript 特性,否則,永遠不要擴展原生的對象原型。
# call、apply和bind
[參考鏈接1](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this)
`this`的指向和函數的調用方式有關系。
函數直接調用,`this`指向全局對象
```javascript
function f1(){
return this;
}
//在瀏覽器中:
f1() === window; //在瀏覽器中,全局對象是window
//在Node中:
f1() === global;
// 嚴格模式下則會變為undefined
function f2(){
"use strict"; // 這里是嚴格模式
return this;
}
f2() === undefined; // true
```
通過`call`和`apply`方法改變`this`的指向,從一個`context`傳到另一個
```javascript
// 一個對象可以作為call和apply的第一個參數,并且this會被綁定到這個對象。
var obj = {a: 'Custom'};
// 這個屬性是在global對象定義的。
var a = 'Global';
function whatsThis(arg) {
return this.a; // this的值取決于函數的調用方式
}
whatsThis(); // 直接調用, 返回'Global'
whatsThis.call(obj); // 通過call調用, 返回'Custom'
whatsThis.apply(obj); // 通過apply調用 ,返回'Custom'
```
```javascript
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// 第一個參數是作為‘this’使用的對象
// 后續參數作為參數傳遞給函數調用
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// 第一個參數也是作為‘this’使用的對象
// 第二個參數是一個數組,數組里的元素用作函數調用中的參數
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
```
`bind`,調用`f.bind(xx)`會創建一個與f具有相同函數體和作用域的函數,`this`將永久被綁定到了`bind`的第一個參數,無論這個函數如何被調用
```javascript
function f(){
return this.a;
}
//this被固定到了傳入的對象上
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
var h = g.bind({a:'yoo'}); //bind只生效一次!
console.log(h()); // azerty
var o = {a:37, f:f, g:g, h:h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
```
`箭頭函數`的`this`是根據當前的詞法的作用域來決定的
```javascript
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
```
無法通過`call`、`apply`或者`bind`進行改變
```javascript
// 接著上面的代碼
// 作為對象的一個方法調用
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true
// 嘗試使用call來設定this
console.log(foo.call(obj) === globalObject); // true
// 嘗試使用bind來設定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
```
`this`被當做對象的一個方法調用是,指向的是調用該函數的對象
```javascript
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // logs 37
```
不受函數定義方式或者位置的影響,只和調用方式有關
```javascript
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
```
在原型鏈中也是一致,`this`指向的是調用改方法的對象
```javascript
var o = {
f : function(){
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
```
在`getter`和`setter`中,會把`this`綁定到正在設置或獲取屬性的對象
```javascript
function sum() {
return this.a + this.b + this.c;
}
var o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
}
};
Object.defineProperty(o, 'sum', {
get: sum, enumerable: true, configurable: true});
console.log(o.average, o.sum); // logs 2, 6
```
當一個函數用作構造函數時(`new`),它的`this`被綁定到正在構造的新對象
```javascript
/*
* 構造函數這樣工作:
*
* function MyConstructor(){
* // 函數實體寫在這里
* // 根據需要在this上創建屬性,然后賦值給它們,比如:
* this.fum = "nom";
* // 等等...
*
* // 如果函數具有返回對象的return語句,則該對象將是 new 表達式的結果。
* // 否則,表達式的結果是當前綁定到 this 的對象。
* //(即通常看到的常見情況)。
* }
*/
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38}; // 手動設置了返回,若為對象則 this.a 變為`僵尸代碼`
}
o = new C2();
console.log(o.a); // logs 38
```
# 變量的作用域
沒有加上`var`關鍵字則代表全局作用域。
如果在函數內定義變量,則變量的作用域在函數體內。
如果在函數外定義變量,則可以在函數內調用。
# 變量的生存周期
函數內調用的變量,則生存周期在函數調用期間有效
# 閉包
[參考鏈接1](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures) [參考鏈接2](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Details_of_the_Object_Model)
被作用域封閉的變量,函數等執行的一個函數的作用域,可以在一定程度上延長變量的生存周期。
由兩部分構成:函數,以及創建該函數的環境。
```javascript
// 創建一個閉包函數
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5); // 第一次調用 返回一個function(y){...}的匿名函數,這時保存了變量x的值,不會因為makeAdder函數執行結束而銷毀
var add10 = makeAdder(10);
console.log(add5(2)); // 7,第二次調用
console.log(add10(2)); // 12
```
用閉包模擬私有方法
```javascript
var makeCounter = function() { // 創建一個環境
var privateCounter = 0; // 只能在該環境中訪問到的變量,外界無法訪問
function changeBy(val) {
privateCounter += val;
}
return { // 定義接口
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter(); // 再分配到不同的環境中
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
```
匿名閉包
```javascript
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
(function() { // 每次循環都創建一個新的環境
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
})(); // Immediate event listener attachment with the current value of item (preserved until iteration).
}
}
setupHelp();
```
# 高階函數
[參考鏈接1](http://www.cnblogs.com/laixiangran/p/5468567.html)
滿足其中一個條件即可:函數可以作為參數被傳遞;函數可以作為返回值輸出。
顯然`js`中的函數滿足高階函數的需求,例如`ajax`和`Array.prototype.sort`
# AOP
[參考鏈接1](http://www.cnblogs.com/laixiangran/p/5468567.html)
面向切面編程的主要作用是把一些跟核心業務邏輯模塊無關的功能抽離出來,這些跟業務邏輯無關的功能通常包括日志統計、安全控制、異常處理等。把這些功能抽離出來之后,再通過“動態織入”的方式摻入業務邏輯模塊中。
```javascript
Function.prototype.before = function(beforefn) {
var __self = this; // 保存原函數的引用
return function() { // 返回包含了原函數和新函數的"代理"函數
beforefn.apply(this, arguments); // 執行新函數,修正this
return __self.apply(this, arguments); // 執行原函數
}
};
Function.prototype.after = function(afterfn) {
var __self = this;
return function() {
var ret = __self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
}
};
var func = function() {
console.log(2);
};
func = func.before(function() {
console.log(1);
}).after(function() {
console.log(3);
});
func();
// 按順序打印出1,2,3
```
# 函數科里化
[參考鏈接1](http://www.cnblogs.com/laixiangran/p/5468567.html)
部分求值。一個currying的函數首先會接受一些參數,接受了這些參數之后,該函數并不會立即求值,而是繼續返回另外一個函數,剛才傳入的參數在函數形成的閉包中被保存起來。待到函數被真正需要求值的時候,之前傳入的所有參數都會被一次性用于求值。
```javascript
// 通用currying函數,接受一個參數,即將要被currying的函數
var currying = function(fn) {
var args = [];
return function() {
if (arguments.length === 0) {
return fn.apply(this, args);
} else {
[].push.apply(args, arguments);
return arguments.callee;
}
}
};
// 將被currying的函數
var cost = (function() {
var money = 0;
return function() {
for (var i = 0, l = arguments.length; i < l; i++) {
money += arguments[i];
}
return money;
}
})();
var cost = currying( cost ); // 轉化成currying函數
cost( 100 ); // 未真正求值
cost( 200 ); // 未真正求值
cost( 300 ); // 未真正求值
console.log (cost()); // 求值并輸出:600
```
# uncurrying
[參考鏈接1](http://www.cnblogs.com/laixiangran/p/5468567.html)
將泛化this的過程提取出來,將fn.call或者fn.apply抽象成通用的函數。
```javascript
// uncurrying實現
Function.prototype.uncurrying = function() {
var self = this;
return function() {
return Function.prototype.call.apply(self, arguments);
}
};
// 將Array.prototype.push進行uncurrying,此時push函數的作用就跟Array.prototype.push一樣了,且不僅僅局限于只能操作array對象。
var push = Array.prototype.push.uncurrying();
var obj = {
"length": 1,
"0": 1
};
push(obj, 2);
console.log(obj); // 輸出:{0: 1, 1: 2, length: 2}
```
# 函數節流
[參考鏈接1](http://www.cnblogs.com/laixiangran/p/5468567.html)
當一個函數被頻繁調用時,如果會造成很大的性能問題的時候,這個時候可以考慮函數節流,降低函數被調用的頻率。
throttle函數的原理是,將即將被執行的函數用setTimeout延遲一段時間執行。如果該次延遲執行還沒有完成,則忽略接下來調用該函數的請求。throttle函數接受2個參數,第一個參數為需要被延遲執行的函數,第二個參數為延遲執行的時間。
```javascript
var throttle = function(fn, interval) {
var __self = fn, // 保存需要被延遲執行的函數引用
timer, // 定時器
firstTime = true; // 是否是第一次調用
return function() {
var args = arguments,
__me = this;
if (firstTime) { // 如果是第一次調用,不需延遲執行
__self.apply(__me, args);
return firstTime = false;
}
if (timer) { // 如果定時器還在,說明前一次延遲執行還沒有完成
return false;
}
timer = setTimeout(function() { // 延遲一段時間執行
clearTimeout(timer);
timer = null;
__self.apply(__me, args);
}, interval || 500 );
};
};
window.onresize = throttle(function() {
console.log(1);
}, 500 );
```
# 分時函數
[參考鏈接1](http://www.cnblogs.com/laixiangran/p/5468567.html)
當一次的用戶操作會嚴重地影響頁面性能,如在短時間內往頁面中大量添加DOM節點顯然也會讓瀏覽器吃不消,我們看到的結果往往就是瀏覽器的卡頓甚至假死。
timeChunk函數接受3個參數,第1個參數是創建節點時需要用到的數據,第2個參數是封裝了創建節點邏輯的函數,第3個參數表示每一批創建的節點數量。
```javascript
var timeChunk = function(ary, fn, count) {
var t;
var start = function() {
for ( var i = 0; i < Math.min( count || 1, ary.length ); i++ ){
var obj = ary.shift();
fn( obj );
}
};
return function() {
t = setInterval(function() {
if (ary.length === 0) { // 如果全部節點都已經被創建好
return clearInterval(t);
}
start();
}, 200); // 分批執行的時間間隔,也可以用參數的形式傳入
};
};
```
# 惰性加載函數
[參考鏈接1](http://www.cnblogs.com/laixiangran/p/5468567.html)
在Web開發中,因為瀏覽器之間的實現差異,一些嗅探工作總是不可避免。比如我們需要一個在各個瀏覽器中能夠通用的事件綁定函數addEvent,常見的寫法如下:
方案一:
```javascript
var addEvent = function(elem, type, handler) {
if (window.addEventListener) {
return elem.addEventListener(type, handler, false);
}
if (window.attachEvent) {
return elem.attachEvent('on' + type, handler);
}
};
```
缺點:當它每次被調用的時候都會執行里面的if條件分支,雖然執行這些if分支的開銷不算大,但也許有一些方法可以讓程序避免這些重復的執行過程。
方案二:
```javascript
var addEvent = (function() {
if (window.addEventListener) {
return function(elem, type, handler) {
elem.addEventListener(type, handler, false);
}
}
if (window.attachEvent) {
return function(elem, type, handler) {
elem.attachEvent('on' + type, handler);
}
}
})();
```
缺點:也許我們從頭到尾都沒有使用過addEvent函數,這樣看來,一開始的瀏覽器嗅探就是完全多余的操作,而且這也會稍稍延長頁面ready的時間。
方案三:
```javascript
var addEvent = function(elem, type, handler) {
if (window.addEventListener) {
addEvent = function(elem, type, handler) {
elem.addEventListener(type, handler, false);
}
} else if (window.attachEvent) {
addEvent = function(elem, type, handler) {
elem.attachEvent('on' + type, handler);
}
}
addEvent(elem, type, handler);
};
```
此時addEvent依然被聲明為一個普通函數,在函數里依然有一些分支判斷。但是在第一次進入條件分支之后,在函數內部會重寫這個函數,重寫之后的函數就是我們期望的addEvent函數,在下一次進入addEvent函數的時候,addEvent函數里不再存在條件分支語句。