this 指向問題ES5
ES5中this的指針
按照this
指針的優先級,列出下面常會遇到的四種情況,從上到下依次是優先級從高到低(後面會詳細比較優先級)。
- 函數是和
new
一起被調用的嗎(new綁定)?如果是,this
就是新構建的對象。var bar = new foo()
- 函數是用
call
或apply
被調用(明確綁定),甚至是隱藏在bind
硬綁定 之中嗎?如果是,this
就是明確指定的對象。var bar = foo.call( obj2 )
- 函數是用環境對象(也稱為擁有者或容器對象)被調用的嗎(隱含綁定)?如果是,
this
就是那個環境對象。var bar = obj1.foo()
- 否則,使用默認的
this
(默認綁定)。如果在strict mode
下,就是undefined
,否則是global
對象。var bar = foo()
以上,就是理解對於普通的函數調用來說的this
綁定規則所需的全部。是的···幾乎是全部。
apply、call、bind
因為apply、call存在於Function.prototype中,所以每個方法都有這兩個屬性。
- call
函數名.call(對象,arg1....argn)
//功能:
//1.調用函數
//2.將函數內部的this指向第一個參數的對象
//3.將第二個及以後所有的參數,作為實參傳遞給函數
- apply
主要用途是直接用數組傳參
函數名.apply(對象, 數組/偽數組);
//功能:
//1.調用函數
//2.將函數內部的this指向第一個參數的對象
//3.將第二個參數中的數組(偽數組)中的元素,拆解開依次的傳遞給函數作為實參
//案例求數組中最大值
var a=Math.max.apply( null, [ 1, 2, 5, 3, 4 ] );
console.log(a);// 輸出:5
- bind
- 是創建一個新的函數,我們必須要手動去調用:
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)() // 3
-
註意事項
- call和apply功能幾乎一致,唯一的區別就是傳參的方式!!
2.call和apply的第一個參數如果為null或者undefined,那麽this指向window
3.call和apply的第一個參數如果為值類型的數據,那麽會將這個值類型的數據,轉換成其對應的引用類型的數據,然後再將this指向這個引用類型的數據
4.call和apply立即執行這個函數,bind方法可以讓對應的函數想什麽時候調就什麽時候調用,並且可以將參數在執行的時候添加,這是它們的區別,根據自己的實際情況來選擇使用。
5.當參數的個數確定的情況下可以使用call;當參數的個數不確定的情況下可以使用apply
- call和apply功能幾乎一致,唯一的區別就是傳參的方式!!
apply應用
JavaScript
var array1 = [12 , "foo" , {name "Joe"} , -2458];
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
/* array1 值為 [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */
call應用(將偽數組轉為數組)
var arrayLike = {0: ‘name‘, 1: ‘age‘, 2: ‘sex‘, length: 3 }
Array.prototype.join.call(arrayLike, ‘&‘); // name&age&sex
Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"]
// slice可以做到類數組轉數組
Array.prototype.map.call(arrayLike, function(item){
return item.toUpperCase();
});
// ["NAME", "AGE", "SEX"]
call應用(判斷復雜數據類型)
console.log(
Object.prototype.toString.call(num),
Object.prototype.toString.call(str),
Object.prototype.toString.call(bool),
Object.prototype.toString.call(arr),
Object.prototype.toString.call(json),
Object.prototype.toString.call(func),
Object.prototype.toString.call(und),
Object.prototype.toString.call(nul),
Object.prototype.toString.call(date),
Object.prototype.toString.call(reg),
Object.prototype.toString.call(error)
);
// ‘[object Number]‘ ‘[object String]‘ ‘[object Boolean]‘ ‘[object Array]‘ ‘[object Object]‘
// ‘[object Function]‘ ‘[object Undefined]‘ ‘[object Null]‘ ‘[object Date]‘ ‘[object RegExp]‘ ‘[object Error]‘
bind在react中應用
下面的例子是來自react官網的案例
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this);
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? ‘ON‘ : ‘OFF‘}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById(‘root‘)
);
If you forget to bind
this.handleClick
and pass it toonClick
,this
will beundefined
when the function is actually called.
ES6中this的指向
箭頭函數this指向註意事項
箭頭函數體內的this
對象,如果包裹在函數中就是函數調用時所在的對象,如果放在全局中就是指全局對象window。並且固定不會更改。換句話說內部的this
就是外層代碼塊的this
下面是對比分析普通函數和箭頭函數中this區別
// 普通函數
function foo() {
setTimeout(function() {
console.log(‘id:‘, this.id);
});
}
var id = 21;
foo.call({ id: 42 }); //21
// 箭頭函數
function foo() {
setTimeout(() => {
console.log(‘id:‘, this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 }); //42
// 上面的匿名函數定義時所在的執行環境就是foo函數,所以匿名函數內部的this執向始終會和foo函數的this執向保持一致,不會更改,如同下面的這個案例
function foo() {
setTimeout(() => {
console.log(‘id:‘, this.id);
}, 100);
}
var id = 21;
foo(); //21(沒有用call)
// ES5普通函數模擬上面es6函數的執行過程
function foo() {
var _this = this;
setTimeout(function () {
console.log(‘id:‘, _this.id);
}, 100);
}
call的作用就是將foo函數的執行環境從window改成對象
{id: 42}
定時器的作用就是延遲執行當前函數的外部執行環境,無論有沒有設置延遲時間
普通函數解釋:定義時this指向函數foo
作用域,但是在定時器100毫秒之後執行函數時,此時this指向window對象
箭頭函數解釋:this始終指向定義時所在對象,也就是始終指向foo
作用域
進一步分析this
var handler = {
id: ‘123456‘,
init: function() {
document.addEventListener(‘click‘,
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log(‘Handling ‘ + type + ‘ for ‘ + this.id);
}
};
handler.init()// Handlingclickfor123456
箭頭函數的this始終指向handler
,如果是普通函數,this指向document
this
指向的固定化,並不是因為箭頭函數內部有綁定this
的機制,實際原因是箭頭函數根本沒有自己的this
,導致內部的this
就是外層代碼塊的this
。正是因為它沒有this
,所以也就不能用作構造函數。
註意事項(綁定事件)
在IE678裏面不支持addEventListener和removeEventListener,而是支持attchEvent和detachEvent兩個方法。
語法:target.attachEvent(“on”+type, listener);
attachEvent和addEventListener區別:
- 在attchEvent裏面的this指向的不是事件的調用者,而是window(奇葩),而addEventListener指向的事件調用者。
- attachEvent的type一定要加上on,不然沒效果
面試題
下面的面試題一、二、四都設計到引用問題,如果不是很好理解還可以理解成在 es5 中,永遠是this 永遠指向最後調用它的那個對象。摘錄鏈接
下面涉及指針應用還有綁定之類的概念來自You Dont Konw JS鏈接
面試題一
this.x = 9; // this refers to global "window" object here in the browser
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX();
// returns 9 - The function gets invoked at the global scope
// Create a new function with ‘this‘ bound to module
// New programmers might confuse the
// global var x with module‘s property x
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
retrieveX
只是getX
函數的引用,也就是只是getX
的一個指針(getX
的另一個指針是module.getX
),所以retrieveX
還是指向getX
函數本身的
和上面類似的案例,下面的func只是函數引用,所以即使在函數內部,還是執行的函數本身,不受詞法作用域限制(箭頭函數則受限制)
document.getElementById( ‘div1‘ ).onclick = function(){
console.log( this.id );// 輸出: div1
var func = function(){
console.log ( this.id );// 輸出: undefined
}
func();
};
//修正後
document.getElementById( ‘div1‘ ).onclick = function(){
var func = function(){
console.log ( this.id );// 輸出: div1
}
func.call(this);
};
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2
面試題二
var A = function( name ){
this.name = name;
};
var B = function(){
A.apply(this,arguments);
};
B.prototype.getName = function(){
return this.name;
};
var b=new B(‘sven‘);
console.log( b.getName() ); // 輸出: ‘sven‘
面試題三
確實,許多包中的函數,和許多在JavaScript語言以及宿主環境中的內建函數,都提供一個可選參數,通常稱為“環境(context)”,這種設計作為一種替代方案來確保你的回調函數使用特定的this而不必非得使用bind(..)。
舉例來說:
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// 使用`obj`作為`this`來調用`foo(..)`
[1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
面試題四
明確綁定 的優先權要高於 隱含綁定
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
new綁定的優先級高於隱含綁定(new和call/apply不能同時使用,所以new foo.call(obj1)是不允許的,也就是不能直接對比測試 new綁定 和 明確綁定)
this 指向問題ES5