JavaScript 中 this 老是指向一個對象編程
this 指向該對象,如:設計模式
var obj = {
a: 1,
getA: function() {
console.log(this === obj); // true
console.log(this.a); // 1
}
};
obj.getA();
複製代碼
當函數不做爲對象的屬性被調用時,普通函數的 this 老是指向全局對象,瀏覽器裏就是 window 對象。數組
var name = "globalName";
var obj = {
name: "tyang",
getName: function() {
return this.name;
}
};
var getName = obj.getName;
console.log(obj.getName()); // tyang
console.log(getName()); // globalName
複製代碼
obj.getName()
做爲 obj 對象的屬性被調用,this 指向 obj 對象;瀏覽器
getName()
使用變量 getName 引用 obj.getName,此時是函數調用方式,this 指向全局 window;閉包
在嚴格模式,狀況有所不一樣:this 不會指向全局對象,而是 undefined:app
function func() {
"use strict";
console.log(this); // undefined
}
func();
複製代碼
當函數做爲某個對象的方法調用時,this 等於那個對象。不過,匿名函數的執行環境具備全局性,所以其 this 對象一般指向 window。函數式編程
var gName = "The window";
var gObject = {
gName: "My object",
getName: function() {
return function() {
// 返回一個匿名函數
return this.gName;
};
}
};
console.log(gObject.getName()()); // 'The window'
var getNameFunc = gObject.getName();
console.log(getNameFunc()); // 'The window'
複製代碼
建立了一個全局對象 gName
,這個對象包含一個方法 getName()
, 這個方法返回一個匿名函數,這個匿名函數返回 this.name
。所以調用 gObject.getName()()
會當即執行匿名函數,並返回一個字符串 'The window'
。函數
爲何匿名函數沒有取得包含做用域的 this 對象呢?學習
每一個函數再被調用的時候,會自動取得兩個特殊變量:this 和 arguments,內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這兩個變量。ui
因此,能夠在外部做用域中設置一個變量來保存 this 對象:
var gName = "The window";
var gObject = {
gName: "My object",
getName: function() {
var that = this; // 將 this 對象賦值給 that 變量
return function() {
return that.gName; // that 引用着 gObject
};
}
};
console.log(gObject.getName()()); // 'My object'
複製代碼
固然,arguments 對象也能夠如此使用:對該對象的引用保存到另外一個閉包可以訪問的變量中。
當使用 new 運算符調用函數時,該函數會返回一個對象,通常狀況下,構造器裏的 this 指向返回的這個對象,如:
var MyClass = function() {
this.name = "Lisi";
};
var nameObj = new MyClass();
console.log(nameObj.name); // Lisi
複製代碼
可是,當顯式返回一個 object 類型的對象時,那最終會返回這個對象,並非以前的 this:
var MyClass = function() {
this.name = "Lisi";
return {
// 若是這裏不會煩 object 類型的數據,如:return 'wangwu',就不會返回顯式對象
name: "wangwu"
};
};
var nameObj = new MyClass();
console.log(nameObj.name); // wangwu
複製代碼
call 和 apply 能夠動態地改變傳入函數的 this:
var personObj = {
name: "ytao",
age: "22"
};
function person() {
return this.name + this.age;
}
console.log(person.call(personObj)); // ytao22
複製代碼
咱們通常會重寫這個獲取 id 的方法:
var getId = function(id) {
return document.getElementById(id);
};
getId("divBox");
複製代碼
那可不能夠這樣呢:
getId2 = document.getElementById;
getId2("divBox"); // Uncaught TypeError: Illegal invocation
複製代碼
結果直接報錯,當 getElementById
方法做爲 document 對象的屬性被調用時, 方法內部的 this 是指向 document 的。若是 getId2('divBox')
,至關因而普通函數調用,函數內部的 this 指向的是 window。
因此,按照這個思路,咱們能夠這樣模擬一下它的實現:
document.getElementById = (function(func) {
return function() {
return func.apply(document, arguments);
};
})(document.getElementById);
getId3 = document.getElementById;
getId3("divBox");
複製代碼
fun.apply(thisArg, [argsArray])
fun.call(thisArg, arg1, arg2, ...)
在函數式編程中,call 和 apply 方法尤其有用,二者用法一致,只是傳參的形式上有所區別而已。
apply() 接受兩個參數,第一個參數指定了函數體內 this 對象,第二個是數組或者類數組,apply() 方法將這個集合中的元素做爲參數傳遞給被調用的函數。
call() 方法的做用和 apply() 方法相似,區別就是 call()方法接受的是參數列表,而 apply()方法接受的是一個參數數組。
第一個參數爲 null,函數體內的 this 會指向默認的宿主對象,可是在嚴格模式下,依然是 null。
var applyFunc = function(a, b, c) {
console.log(this === window);
};
applyFunc.apply(null, [1, 2, 3]); // true
var applyFunc = function(a, b, c) {
"use strict";
console.log(this === null);
};
applyFunc.apply(null, [1, 2, 3]); // true
複製代碼
假如在一個點擊事件函數中有一個內部函數 func,當點擊事件被觸發時,就會出現以下狀況:
document.getElementById("divBox").onclick = function() {
console.log(this.id); // divBox
var func = function() {
console.log(this.id); // undefined,這裏的 this 指向了 window
};
func();
};
複製代碼
這時,咱們用 call() 來改變一下:
document.getElementById("divBox").onclick = function() {
console.log(this.id); // divBox
var func = function() {
console.log(this.id); // divBox
};
func.call(this);
};
複製代碼
function.bind(thisArg[, arg1[, arg2[, ...]]])
bind()方法建立一個新的函數,在調用時設置 this 關鍵字爲提供的值。並在調用新函數時,將給定參數列表做爲原函數的參數序列的前若干項。
Function.prototype.bind = function(context) {
var self = this; // 保存原函數
return function() {
// 返回新函數
return self.apply(context, arguments); // 將傳入的 context 當作新函數體內的 this
};
};
var bindObj = {
name: "tyang"
};
var bindFunc = function() {
console.log(this.name); // tyang
}.bind(bindObj);
bindFunc();
複製代碼
這是一個簡化版的 Function.prototype.bind
實現,self.apply(context, arguments)
纔是執行原來的 bindFunc 函數,而且指定 context 對象爲 bindFunc 函數體內的 this。
咱們再繼續修改下,使之能夠預先添加一些參數:
Function.prototype.bind = function() {
var self = this,
context = [].shift.call(arguments), // 獲取參數中第一個爲綁定的this上下文
args = [].slice.call(arguments); // 將剩餘的參數轉化爲數組
// 返回新函數
return function() {
return self.apply(context, [].concat.call(args, [].slice.call(arguments))); //arguments 爲新函數的參數,即傳入的 3,4
};
};
var bindObj = {
name: "lisisi"
};
var bindFunc = function(a, b, c, d) {
console.log(this.name); // lisisi
console.log([a, b, c, d]); // [1, 2, 3, 4]
}.bind(bindObj, 1, 2);
bindFunc(3, 4);
複製代碼
self.apply(context, [].concat.call(args, [].slice.call(arguments)));
,執行新函數的時候,會把以前傳入的 context 做爲 this,[].slice.call(arguments)
將新函數傳入的參數轉化爲數組,並做爲[].concat.call(args)
的給定參數,組合兩次,做爲新函數最終的參數。
第一種,」借用構造函數「實現一些相似繼承的效果:
var A = function(name) {
this.name = name;
};
var B = function() {
A.apply(this, arguments);
};
B.prototype.getName = function() {
return this.name;
};
var bbb = new B("Yangtao");
console.log(bbb.getName()); //Yangtao
複製代碼
第二種,給類數組對象使用數組方法,好比:
(function() {
Array.prototype.push.call(arguments, 3);
console.log(arguments); // [1, 2, 3]
})(1, 2);
複製代碼
再好比以前用到的,把 arguments 轉成真正的數組的時候能夠借用 Array.prototype.slice.call(arguments)
,想截去頭一個元素時,借用Array.prototype.shift.call(arguments)
雖然咱們能夠把」任意「對象傳入 Array.prototype.push
:
var aObj = {};
Array.prototype.push.call(aObj, "first");
console.log(aObj.length); // 1
console.log(aObj[0]); // first
複製代碼
可是,這個對象也得知足如下兩個條件:
若是是其餘類型,好比 number,沒法存取;好比函數,length 屬性不可寫,使用 call 或 apply 就會報錯:
var num = 1;
Array.prototype.push.call(num, "2");
console.log(num.length); // undefined
console.log(num[0]); // undefined
var funcObj = function() {};
Array.prototype.push.call(funcObj, "3");
console.log(funcObj.length); // Uncaught TypeError: Cannot assign to read only property 'length' of function 'function () {}'
複製代碼
學習資料: