在javascript中,this的指向一般是一個使人頭痛的問題,js中的this的指向與真正的有類的概念的語言(java,c++,c#)不一樣,js中的this並非表示指向自身,而是會隨着調用上下文和做用域而改變。javascript
一般this的綁定有四種狀況java
1.隱式綁定node
即做爲對象的函數進行調用,此時this指向上下文的對象c++
function sayName() {
console.log(this.name);
}
let person = {
name: "Jack",
sayName: sayName
}
person.sayName() // Jack
複製代碼
2顯示綁定c#
即經過apply,call和bind進行綁定數組
// apply, call軟綁定, 綁定後能夠更改this的指向
function sayName() {
console.log(this.name);
}
let person = {
name: 'Jack'
}
let child = {
name: 'Tim',
say: say
}
sayName.apply(person); // Jack
sayName.apply(child); // Tim
sayName.call(person); // Jack
sayName.call(child); // Tim
//bind 硬綁定,一旦綁定,便沒法更改this的指向
let say = sayName.bind(person);
say(); // Jack
child.say(); // Jack
let log = say.bind(child);
log(); // Jack, this並無更改指向
let says = sayName.bind(child);
says(); // Jack, this也沒有更改指向
複製代碼
3.new綁定瀏覽器
當用new建立一個對象時,this指向這個對象app
function Person(name) {
this.name = name;
}
let person = new Person('Jack');
console.log(person.name); // Jack
複製代碼
4.默認綁定函數
當不符合上面三種狀況時,則使用默認綁定規則。嚴格模式下綁定到undefined,普通模式下綁定到window(nodejs中綁定到global)ui
var name = 'Jack';
function sayName() {
console.log(this.name);
}
let person = {
name: 'Tim',
sayName: function() {
sayName();
}
};
person.sayName();
複製代碼
上面的代碼在nodejs和瀏覽器環境下的結果是不一樣的。雖然nodejs中默認綁定會綁定到global對象,可是上面的代碼結果是輸出undefined;在瀏覽器環境下會輸出Jack
若是將var換成let聲明變量方式,瀏覽器環境下回輸出空字符串,而nodejs下回輸出undefined
綁定的優先級:new > 顯示 > 隱式 > 默認
胖箭頭函數的this綁定
胖箭頭函數沒有本身的this,胖箭頭函數的this繼承自外層代碼塊所在做用域的this,若是外層代碼塊不存在this,則繼續向上查找。
let person = {
fun1: function() {
console.log(this);
return () => {
console.log(this);
}
},
fun2: function() {
console.log(this);
return function() {
console.log(this);
}
},
fun3: () => {
console.log(this);
}
}
let fun1 = person.fun1(); // person
fun1(); // person
let fun2 = person.fun2(); // person
fun2(); // window
person.fun3(); // window
/* 第一次函數調用,採用隱式綁定,this指向上下文對象,也就是person 第二次函數調用,調用了胖箭頭函數,胖箭頭函數的this繼承自上層代碼塊中的this,上層 代碼塊中的this指向person,因此胖箭頭函數的this也指向person 第三次函數調用,採用隱式綁定,this指向上下文對象 第四次函數調用,既沒有隱式綁定條件,沒有顯示綁定條件,也沒有new綁定條件,因此採用默認綁定規則,this指向window 第五次函數調用,調用了胖箭頭函數,而上層person中沒有this,因此向上查找,person的上層爲window,因此這次胖箭頭函數的this指向window */
複製代碼
apply
apply接受兩個參數,第一個參數指定函數體內this對象的指向,第二個參數是一個數組或類數組,並將此數組或類數組做爲參數傳遞給所調用的函數
let person = {
0: "Jack",
1: "Tim",
length: 2
}
let persons = [
"Jack",
"Tim"
]
function log(a, b) {
console.log(a);
console.log(b);
}
log.apply(null, person); // Jack Tim
log.apply(null, persons); // Jack Tim
複製代碼
call
call能夠接受多個參數,第一個參數與apply相同,指定函數體內的this對象的指向,其他的參數傳給被調用的函數,做爲被調用函數的參數
function log(a, b) {
console.log(a);
console.log(b);
}
log.apply(null, "Jim", "Jack"); // Jim Jack
複製代碼
call和apply的用途
apply和call除了能夠更改this指向外,還能夠用來實現相似於繼承的效果
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name);
}
}
function Student() {
Person.apply(this, arguments);
}
let student = new Student("Jack");
student.sayName();
複製代碼
借用其餘對象的方法
// 數組沒有取得最大值的方法,能夠經過調用Math.max()函數來取得數組的最大值
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.apply(null, arr)); // 9
複製代碼
bind
bind可接受多個參數,並返回一個新函數。第一個參數指定新函數體內的this對象的指向,其他的參數會在傳遞實參以前傳給新函數,做爲新函數的參數
bind只用一個用途,改變this的指向。而且一旦經過bind綁定了this的指向,沒法再次經過bind,apply或call更改this的指向
function sayName() {
console.log(this.name);
}
let person = {
name: 'Jack'
};
let child = {
name: 'Tim',
};
let say = sayName.bind(person);
say(); // Jack
let log = say.bind(child);
log(); // Jack, this並無更改指向
say.apply(child); // Jack this並無更改指向
say.call(child); // Jack this並無更改指向
複製代碼
實現apply函數
Function.prototype.mApply = function(context, arr) {
// 若是context爲null,則將context設置爲window(瀏覽器環境)或global(nodejs環境)
if (!context) {
context = typeof window === 'undefined' ? global : window; // 判斷是在什麼環境下運行,根據環境來給context設置默認值
}
context.fn = this; // 給context添加函數,使添加的函數指向當前被調用函數
let res; // 返回值
if (!arr) {
// 若參數爲null,能夠直接調用
res = context.fn(arr); // 調用添加函數
} else if (typeof arr === 'object') {
// 若參數爲數組或類數組,則經過...運算符將其傳入
res = context.fn(...arr); // 調用添加的函數
}
delete context.fn; // 刪除添加的函數
return res; // 傳出返回值
}
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.mApply(null, arr)); // 9
複製代碼
實現call函數
Function.prototype.mCall = function(context) {
// 若是context爲null,則將context設置爲window(瀏覽器環境)或global(nodejs環境)
if (!context) {
context = typeof window === 'undefined' ? global : window; // 判斷是在什麼環境下運行,根據環境來給context設置默認值
}
context.fn = this; // 給context添加函數,使添加的函數指向被調用的函數
let args = [...arguments].slice(1); // 取得除指定上下文參數以外的參數
let res = context.fn(...args); // 調用添加的函數,並存儲返回值
delete context.fn; // 刪除添加的函數
return res; // 傳出返回值
}
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.mCall(null, ...arr)); // 9
複製代碼
實現bind
Function.prototype.mBind = function(context) {
// 若不是函數
if (typeof this !== 'function') {
throw new TypeError("is not a funtion")
}
let args = [...arguments].slice(1);
function Fn() {}
fun.prototype = this.prototype;
let self = this;
let bound = function() {
let res = [...args, ...arguments]; // 將bind傳遞的參數與調用時傳遞的參數拼接
context = this instanceof Fn ? this : context || this;
return self.apply(context, res); // 返回值
}
bound.prototype = new Fn();
return bound;
}
function sayName() {
console.log(this.name);
}
let person = {
name: 'Jack'
};
let child = {
name: 'Tim',
};
let say = sayName.mBind(person);
say(); // Jack
let says = say.mBind(child);
says(); // Jack
複製代碼