this指向复习
this指向
this
的指向,是在调用函数时根据执行上下文所动态确定的。
-
在函数体中,简单调用该函数时(非显式/隐式绑定下),严格模式下
this
绑定到undefined
,否则绑定到全局对象window
/global
; -
一般构造函数
new
调用,绑定到新创建的对象上; -
一般由
call
/apply
/bind
方法显式调用,绑定到指定参数的对象上; -
一般由上下文对象调用,绑定在该对象上;
-
箭头函数中,根据外层上下文绑定的
this
决定this
指向。
例1:
function f1() {
console.log(this);
}
function f2() {
"use strict";
console.log(this);
}
f1(); //window
f2(); //undefined
函数在浏览器全局环境中被简单调用,非严格模式下 this
指向 window
;在 use strict
指明严格模式的情况下就是 undefined
。
例2:
const foo = {
bar: 10,
fn: function () {
console.log(this);
console.log(this.bar);
},
};
var fn1 = foo.fn;
fn1(); // window undefined
这里 this
仍然指向的是 window
。虽然 fn
函数在 foo
对象中作为方法被引用,但是在赋值给 fn1
之后,fn1
的执行仍然是在 window
的全局环境中。
例3:
const foo = {
bar: 10,
fn: function () {
console.log(this);
console.log(this.bar);
},
};
foo.fn();
//{bar: 10, fn: ƒ}
//10
在 foo.fn()
语句中 this
指向 foo
对象。
在执行函数时,如果函数中的 this
是被上一级的对象所调用,那么 this
指向的就是上一级的对象;否则指向全局环境。
例4:
const student = {
name: "xxx",
fn: function () {
return this;
},
};
console.log(student.fn() === student); // true
例5:
const person = {
name: "person",
student: {
name: "student",
fn: function () {
return this.name;
},
},
};
console.log(person.student.fn()); // student
在这种嵌套的关系中,this
指向最后调用它的对象,因此输出将会是:student
。
例6:
const o1 = {
text: "o1",
fn: function () {
return this.text;
},
};
const o2 = {
text: "o2",
fn: function () {
return o1.fn();
},
};
const o3 = {
text: "o3",
fn: function () {
var fn = o1.fn;
return fn();
},
};
console.log(o1.fn()); // o1
console.log(o2.fn()); // o1
console.log(o3.fn()); // undefined
-
第二个
console
的o2.fn()
,最终还是调用o1.fn()
,因此答案仍然是o1
。 -
最后一个,在进行
var fn = o1.fn
赋值之后,是“裸奔”调用,因此这里的this
指向window
,答案当然是undefined
。
例7:
如何让o2.fn()
输出结果是o2
①使用call/apply/bind
来对 this
的指向进行干预
const o1 = {
text: "o1",
fn: function () {
return this.text;
},
};
const o2 = {
text: "o2",
fn: function () {
return o1.fn.call(o2);
},
};
console.log(o2.fn());
②this
指向最后调用它的对象,在 fn
执行时,挂到 o2
对象上即可
const o1 = {
text: "o1",
fn: function () {
return this.text;
},
};
const o2 = {
text: "o2",
fn: o1.fn,
};
console.log(o2.fn());
例8:
const foo = {
name: "andy",
logName: function () {
console.log(this.name);
},
};
const bar = {
name: "mike",
};
foo.logName.call(bar); // mike
例9:
function Foo() {
this.bar = "Andy";
}
const instance = new Foo();
console.log(instance.bar); // Andy
new
操作符调用构造函数,具体做了什么?
-
创建一个新的对象;
-
将构造函数的
this
指向这个新对象; -
为这个对象添加属性、方法等;
-
最终返回新对象。
function Foo() {
this.bar = "Lucas";
}
let obj = {};
obj.__proto__ = Foo.prototype;
Foo.call(obj);
console.log(obj.bar); //Lucas
例10:
如果在构造函数中出现了显式 return
的情况,那么需要注意分为两种场景:
function Foo() {
this.user = "foo";
const o = {};
return o;
}
const instance = new Foo();
console.log(instance); // {}
console.log(instance.user); // undefined
此时 instance
是返回的空对象 o
。
function Foo() {
this.user = "foo";
return 1;
}
const instance = new Foo();
console.log(instance); // Foo{ user:'foo' }
console.log(instance.user); // foo
如果构造函数中显式返回一个值,且返回的是一个对象,那么 this
就指向这个返回的对象;如果返回的不是一个对象,那么 this
仍然指向实例。
例11:
箭头函数体内的this
对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
const foo = {
fn: function () {
setTimeout(function () {
console.log(this);
});
},
};
foo.fn();
this
出现在 setTimeout()
中的匿名函数里,因此 this
指向 window
对象。
const foo = {
fn: function () {
setTimeout(() => {
console.log(this);
});
},
};
foo.fn(); // { fn:f }
该函数所在的作用域:箭头函数所在的作用域是fn,因为fn是一个函数
作用域指向的对象:foo.fn指向的对象是foo
所以箭头函数体内的this
指向的是foo。
箭头函数注意事项:
-
不可以当作构造函数,也就是说,不可以使用
new
命令,否则会抛出一个错误。var Foo = () => { value: 1; }; var foo = new Foo(); // TypeError: Foo is not a constructor
-
不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。var A = function (a) { console.log(arguments); }; var B = (b) => { console.log(arguments); }; var C = (...c) => { console.log(c); }; A(1, 2, 3); // [Arguments]{ '0': 1, '1': 2, '2': 3 } B(2, 2, 3); // ReferenceError: arguments is not defined C(3, 4); // [ 3, 4 ]
-
不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数。
箭头函数中的this:
1.箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值,自己本身并没有this值;
2.箭头函数的this永远指向其上下文的this,任何方法都改变不了其指向,如call(), bind(), apply()。
例12:
var name = "window";
var A = {
name: "A",
sayHello: function () {
var s = () => console.log(this.name);
return s;
},
};
var sayHello = A.sayHello();
sayHello(); // A
var B = {
name: "B",
};
A.sayHello.call(B)(); //B
sayHello.call(B); // A
sayHello.call(); //A
注意:一旦函数通过bind传递了有效的this对象,则该函数在运行期的this将指向这个对象,即使通过call或apply来试图改变this的指向也是徒劳的。
例13:
call
、apply
、bind
、new
对 this
绑定的情况称为显式绑定;根据调用关系确定的 this
指向称为隐式绑定。
new
绑定的优先级比显式 bind
绑定更高。
var a = 123
const foo = () => a => {
console.log(this.a)
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
var bar = foo.call(obj1)
console.log(bar.call(obj2)) // 123
const a = 123
const foo = () => a => {
console.log(this.a)
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
var bar = foo.call(obj1)
console.log(bar.call(obj2)) //undefined
因为使用 const
声明的变量不会挂载到 window
全局对象当中。因此 this
指向 window
时,自然也找不到 a
变量了。
例14:
原生bind实现
// Funtion.prototype.bind = Funtion.prototype.bind || function(context){}
Function.prototype.myBind = function (context) {
if (typeof this !== "function") {
throw new TypeError(`${this} is not a function`);
}
//去掉首位
//outerArgs是传入的参数
let outerArgs = Array.prototype.slice.call(arguments, 1);
console.log("----outerArgs----", outerArgs);
// 这里也可以简写为:
// let outerArgs = [].slice.call(arguments,1)
let self = this;
let F = function () {};
let Bound = function () {
let innerArgs = [].slice.call(arguments);
console.log("----innerArgs----", innerArgs);
// args是调用myBind返回的新函数时传入的参数
let args = outerArgs.concat(innerArgs);
console.log(args);
// 当新函数是构造函数时,this指向实例对象Bound,否则指向传入的对象
return self.apply(this instanceof Bound ? this : context, args);
};
if (self.prototype) {
F.prototype = self.prototype;
}
// 将Bound的原型指向self的原型
Bound.prototype = new F();
return Bound;
};
function func(...arg) {
console.log(this);
console.log(...arg);
}
func.prototype.con = function () {
console.log("con");
};
var newFunc = func.myBind({ a: 1 }, 1, 2, 3, 4);
newFunc();
newFunc.prototype.con(); // con
----outerArgs---- [ 1, 2, 3, 4 ]
----innerArgs---- []
[ 1, 2, 3, 4 ]
{ a: 1 }
1 2 3 4
con
原生call
Function.prototype.myCall = function (context, ...args) {
//如果context为null / undefined,则指向window
context = typeof context === "object" ? context : window;
//防止覆盖掉原有属性
const key = Symbol();
//这里的this为需要执行的方法
context[key] = this;
//方法执行
const result = context[key](...args);
delete context[key];
return result;
};
原生apply
Function.prototype.myApply = function (context, args) {
context = typeof context === "object" ? context : window;
//防止覆盖掉原有属性
const key = Symbol();
//这里的this为需要执行的方法
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};
let a = {
name: "123",
fn: function (a, b) {
console.log(this.name);
console.log(a + b);
},
};
let b = {
name: "456",
};
a.fn.myApply(b, [99, 1]); // 456 100
a.fn.myCall(b,99,1); // 456 100