前言:最近在刷面筋,发现笔者自己对 this的指向的概念有所模糊,因此特地花了些时间并记录如下,如有错误,请各位大牛指正~ (●'◡'●) 话不多说,正式进入我们的正题:
1.this
指向问题,在ES5
中 this
永远指向最后调用它的那个对象
var name = 'windowName';
function fn() {
var name = 'private';
console.log(this.name); // windowName
console.log('此时的this', this); // 指向全局的Window
}
fn(); // 这里相当于window.fn()
在ES5
中 “this 永远指向最后调用它的那个对象”,我们看最后调用 fn
的地方 fn();
,前面没有调用的对象那么就是全局对象 window,这就相当于是 window.fn()
;注意,这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined
,那么就会报错 Uncaught TypeError: Cannot read property 'name' of undefined
。
接下来,我们来看看几个变形,仔细对比,相信大家就能发现“this 永远指向最后调用它的那个对象”的理解:
// 变形1
var name = 'windowName';
var foo = {
name: 'privateName',
fn: function() {
console.log(this.name); // privateName
console.log('此时的this', this); // 指向调用该方法的foo {name: "privateName", fn: ƒ}
}
}
foo.fn(); // 这里相当于window.foo.fn(), 因为最后调用fn方法的还是foo,因为输出还是 ‘privateName’
// 变形2
var name = 'windowName';
var foo = {
// name: 'privateName',
fn: function() {
console.log(this.name); // 此时在foo内并没有声明 name 因此输出为 undefined
console.log('此时的this', this); // 指向调用该方法的foo {fn: ƒ}
}
}
window.foo.fn(); // undefined
// 变形 3
var name = 'windowName';
var foo = {
name: null,
fn() {
console.log(this.name); // windowName
console.log('此时的this', this); // 指向Window
}
}
var bar = foo.fn; // 在这里实际上发生了隐式转换,调用bar() 相当于window.bar() 因此this指向的是全局对象window
bar();
// 变形 4
var name = 'windowName';
function fn() {
var name = 'privateName';
innerFn();
function innerFn() {
console.log('此时的this', this); // 指向window
console.log(this.name); // windowName
}
}
fn(); // window.fn()
2. 如何改变this
指向
-
使用
ES6
的箭头函数
var name = 'windowName';
var a = {
name: 'privateName',
fun1: function () {
console.log(this.name);
},
fun2: function () {
setTimeout(function() { // 这里不是箭头函数,调用setTimeout函数的对象是window,此时window下并没有fun1
this.fun1();
}, 100)
}
}
a.fun2(); // this.fun1() is not a function 在window下找不到fun1()
// 变形1 --验证setTimeout中传入的是普通函数时,此时的this指向window
var name = 'windowName';
function fun1 (a, b) {
// console.log(this.name); // windowName fun1() 在setTimeout中被调用,setTimeout的调用对象指向window
return a + b;
}
var a = {
name: 'privateName',
fun1: function (a, b) {
// console.log(this.name);
return a - b;
},
fun2: function () {
setTimeout(function() {
console.log(this.fun1(4, 2)); // 这里的this.fun1() 实际上是全局的fun1()
}, 100)
}
}
a.fun2(); // 6, 由此可以证实setTimeout中的this指向window
// 变形2 -- 借助箭头函数,箭头函数的 this 始终指向函数定义时的 this, 而非执行时
var name = 'windowName';
function fun1 (a, b) {
return a + b;
}
var a = {
name: 'privateName',
fun1: function (a, b) {
return a - b;
},
fun2: function () {
setTimeout(() => {
console.log('此时的this', this); // 此时的this指向a {name: "privateName", fun1: ƒ, fun2: ƒ}
console.log(this.fun1(4, 2));
}, 100)
}
}
a.fun2(); // 2
注意:使用箭头函数需要记住:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。
-
在函数内部使用
_this = this
在fun2
函数内部调用setTimeout
前,将当前的this
赋给变量_this
_this.fun1()
调用时的this
就能被绑定到当前的对象fn
上var name = 'windowName'; var fn = { name: 'privateName', fun1() { console.log('此时的this', this); console.log(this.name); }, fun2() { var _this = this; // 此时的this指向对象fn setTimeout(function() { _this.fun1(); }, 100); } } fn.fun2(); // privateName
-
通过
call
、apply
、bind
(显式绑定)mdn
上对三者的释义:-
call
-
语法:
function.call(this.Arg,arg1, arg2..)
-
参数:
thisArg
:可选参数,在function
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装;arg1,arg2,...
: 指定的参数列表 -
返回值: 使用调用者提供的
this
值和参数调用该函数的返回值。若该方法没有返回值,则返回undefined
。var name = 'windowName'; var fn = { name: 'privateName', fun1() { console.log('此时的this', this); // {name: "privateName", fun1: ƒ, fun2: ƒ} console.log(this.name); }, fun2() { setTimeout(function() { this.fun1(); }.call(fn), 100); // function.call(thiArg, arg1, arg2,..) 这里传入的thisArg为fn } } fn.fun2(); // privateName
-
-
apply
-
语法:
function.apply(thisArg, [argArray])
-
参数:
thisArg
:必选参数。在func
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装;argArray
: 可选参数,数组或类数组对象 -
返回值: 调用有指定
**this**
值和参数的函数的结果。var name = 'windowName'; var fn = { name: 'privateName', fun1() { console.log('此时的this', this); // {name: "privateName", fun1: ƒ, fun2: ƒ} console.log(this.name) }, fun2() { setTimeout(function() { this.fun1(); }.apply(fn), 100); // function.apply(thisArg, [argArray]) 这里传入的thisArg为fn } } fn.fun2(); // privateName
-
-
call
和apply
的区别:apply
与call()
非常相似,不同之处在于提供参数的方式。apply
使用参数数组而不是一组参数列表。apply
可以使用数组字面量(array literal),如fun.apply(this, ['eat', 'bananas'])
,或数组对象, 如fun.apply(this, new Array('eat', 'bananas'))
。 -
bind
-
语法:
function.bind(thisArg[, arg1[, arg2[, ...]]])
-
参数:
thisArg
调用绑定函数时作为this
参数传递给目标函数的值。 如果使用new
运算符构造绑定函数,则忽略该值。当使用bind
在setTimeout
中创建一个函数(作为回调提供)时,作为thisArg
传递的任何原始值都将转换为object
。如果bind
函数的参数列表为空,或者thisArg
是null
或undefined
,执行作用域的this
将被视为新函数的thisArg
。arg1, arg2...
: 当目标函数被调用时,被预置绑定函数的参数列表 -
返回值: 返回一个原函数的拷贝,并拥有指定的
this
值和初始参数
var name = 'windowName'; var fn = { name: 'privateName', fun1() { console.log('此时的this', this); // {name: "privateName", fun1: ƒ, fun2: ƒ} console.log(this.name); }, fun2() { setTimeout(function() { this.fun1(); }.bind(fn)(), 100); // function.bind(thisArg)() 注意,bind返回的是一个函数,调用时机可由开发者决定 } } fn.fun2(); // privateName
-
-
-
new
实例化对象, 在构造函数中,this
始终指向实例化对象// 构造函数 function Person(name, age) { this.name = name; this.age = age; } let person = new Person('privateName', 20); console.log(person.name); // privateName
由此可延伸出另一个问题
new
的过程到底发生了些什么??// 构造函数 function Person(name, age) { this.name = name; this.age = age; } let person = new Person('privateName', 20); // 伪代码模拟 new Person({ var obj = {}; // 创建一空对象 obj.__proto__ = Person.prototype; // 空对象的隐式原型指向其构造函数的显式原型 var result = Person.call(obj, 'privateName', 20); // 利用call实现this指向空对象 return typeof result === 'obj' ? result : obj; // 返回值 })
-
创建一个空对象 obj;
-
将新创建的空对象的隐式原型指向其构造函数的显示原型。
-
使用 call 改变 this 的指向,
这里也即是this始终指向实例化对象的原因
-
如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象
-
(✿◡‿◡)目前记录就到这里,后期有学到新的再补充,欢迎各路大神指正,有错误还请轻喷(满满的求生欲 ( •̀ ω •́ )y)