一、默认绑定的情况(函数直接调用)
1、非严格模式下执行:
function fn() {
console.log(this) // window对象
}
fn()
//面试时可能迷惑一下
var a = 1
function fn() {
var a = 2
console.log(this.a) // 1
//因为this指向的是window
}
fn()
//如果将上面的变量a赋值换成let,结果会是什么呢?
let a = 1
function fn() {
var a = 2
console.log(this.a) // undefined
//因为使用var声明会将变量绑定到全局,所以可以在window上找到a,而let声明,并不会将变量绑定到全局,所以window.a 是 undefined
}
fn()
//再多套几层函数还会吗?
var b = 1
function outer () {
var b = 2
function inner () {
console.log(this.b) // 1
}
inner()
//因为inner函数是直接调用,所以函数内的this会指向window,和嵌套在几层函数内无关
}
outer()
//函数在对象内
const obj = {
a: 1,
fn: function() {
console.log(this.a)
}
}
obj.fn() // 1
//对象的方法里调用时,this指向调用该方法的对象.
const f = obj.fn
f() // undefined
//属于函数直接调用的情况
2、严格模式下执行:
function fn() {
'use strict'
console.log(this) // undefined
}
fn()
二、隐式绑定(属性访问调用)
function fn () {
console.log(this.a)
}
const obj = {
a: 1
}
obj.fn = fn
obj.fn() // 1
//隐式绑定的 `this` 指的是调用堆栈的上一级(`.`前面第一个对象)
下边这个例子更直接表现
function fn () {
console.log(this.a)
}
const obj1 = {
a: 1,
fn
}
const obj2 = {
a: 2,
obj1
}
obj2.obj1.fn() //1
在一些个别情况下,隐式绑定的 this指的是调用堆栈的上一级(.前面第一个对象)的说法并不完全正确,需要对应的区分
1、赋值的情况
const obj1 = {
a: 1,
fn: function() {
console.log(this.a)
}
}
const fn1 = obj1.fn // 函数引用给了fn1,等同于写了 function fn1() { console.log(this.a) }
fn1() // 所以这里其实已经变成了默认绑定规则了,该函数 `fn1` 执行的环境就是全局环境
2、setTimeout的情况
const obj1 = {
a: 1,
fn: function() {
console.log(this.a)
}
}
setTimeout(obj1.fn, 1000) //this指向的是全局
3、 函数作为参数传递
function run(fn) {
fn()
}
run(obj1.fn) // 这里传进去的是一个引用,相当于直接执行函数,所以指向的还是全局this
4、匿名函数
var name = 'The Window';
var obj = {
name: 'My obj',
getName: function() {
return function() { // 这是一个匿名函数
console.log(this.name) //The Window
};
}
}
obj.getName()()
三、显式绑定(call、 bind、 apply)
通过一些方法去强行的绑定 this 上下文,例如:
function fn () {
console.log(this.a)
}
const obj = {
a: 100
}
fn.call(obj) // 100
这种根本还是取决于第一个参数,第一个为 `null` 的时候还是绑到全局的
call、 bind、 apply三个单独的知识点
1、bind
在MDN中定义如下
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。 举一个简答的例子
var a = {
b: function() {
var action = function() {
console.log(this.c);
}
action();
},
c: 'bind'
}
a.b(); // undefined 这里的this指向的是全局作用域
console.log(a.c); // bind
var a = {
b: function() {
var _this = this; // 通过赋值的方式将this赋值给that
var action = function() {
console.log(_this.c);
}
action();
},
c: 'bind'
}
a.b(); // bind
console.log(a.c); // bind
// 使用bind方法
var a = {
b: function() {
var action = function() {
console.log(this.c);
}
action.bind(this)();
},
c: 'bind'
}
a.b(); // bind
console.log(a.c); // bind
分析几个例子
// 分析:这里的bind方法会把它的第一个实参绑定给f函数体内的this,所以里的this即指向{x:1}对象;
// 从第二个参数起,会依次传递给原始函数,这里的第二个参数2即是f函数的y参数;
// 最后调用m(3)的时候,这里的3便是最后一个参数z了,所以执行结果为1+2+3=6
// 分步处理参数的过程其实是一个典型的函数柯里化的过程(Curry)
function f(y,z){
return this.x+y+z;
}
var m = f.bind({x:1},2);
console.log(m(3)); // 6
// 分析:直接调用a的话,this指向的是global或window对象,所以会报错;
// 通过bind或者call方式绑定this至document对象即可正常调用
var a = document.write;
a('hello'); // error
a.bind(document)('hello'); // hello
a.call(document,'hello'); // hello
// 实现预定义参数
// 分析:Array.prototype.slice.call(arguments)是用来将参数由类数组转换为真正的数组;
function list() {
return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3); // [1,2,3]
// 第一个参数undefined表示this的指向,第二个参数10即表示list中真正的第一个参数,依次类推
var a = list.bind(undefined, 10);
var list2 = a(); // [10]
var list3 = a(1, 2, 3); // [10,1,2,3]
参考链接:bind