作者::Wflynn
什么是函数的调用位置
调用位置就是函数在代码中被调用的位置(而不是声明的位置)
为什么要了解调用位置:只有了解函数的调用位置才能进一步的确定 this 的绑定对象
function baz () {
// 当前调用栈是:baz, 因此,当前调用位置是全局作用域
console.log("baz");
bar(); // bar 的调用位置
}
function bar () {
// 当前调用栈是 baz -> bar,因此,当前调用位置在 baz 中
console.log("bar");
fnn(); // fnn 的调用位置
}
function fnn () {
// 当前调用栈是 baz -> bar -> fnn, 因此,当前调用位置在 bar 中
console.log("fnn");
}
baz(); // baz 的调用位置
this 是什么
this 是包含它的函数作为方法被调用时所属的对象。
- 包含它的函数。
- 作为方法被调用时。
- 所属的对象。
随着函数使用场合的不同,this 的值会发生变化。this 指向什么,完全取决于什么地方以什么方式调用,而不是创建时。
this 的四种绑定规则
this 的 4 种绑定规则分别是:默认绑定、隐式绑定、显式绑定、new 绑定。优先级从低到高。
new 绑定 > 显式绑定> 隐式绑定 > 默认绑定
默认绑定
最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。
如代码(非严格模式下)和图片所示
-
this.a 被解析成了window.a,fnn 中的this 是等于window 的。 - 由于
fnn() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则,因此this 指向全局对象。。
function fnn() {
console.log(this);
console.log(this === window);
console.log(this.a);
}
var a = 2;
fnn(); // 2

隐式绑定
调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含
如代码(非严格模式下)和图片所示
-
this.a 被解析成了obj.a,fnn 中的this 是等于obj 的。 - 由于调用
fnn() 函数时,有引用上下文对象obj,隐式绑定规则会把函数调用中的this 绑定到这个上下文对象obj,因此this 指向obj 对象。。
function fnn() {
console.log(this);
console.log(this === obj);
console.log(this.a);
}
var obj = {
a: 2,
fnn: fnn
};
obj.fnn(); // 2

对象属性引用链中只有最顶层或者说最后一层会影响调用位置
如代码所示,最后输出的 this.a 等于 888,因为最后调用 fnn 的上下文对象是 obj1,所以 this 绑定在 obj1 上
function fnn() {
console.log(this);
console.log(this === obj1); // true
console.log(this.a); // 888
}
var obj1 = {
a: 888,
fnn: fnn
};
var obj = {
a: 2,
obj1: obj1
};
obj.obj1.fnn(); // this.a 输出 888
隐式丢失
一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。
如代码所示,最后输出的 this.a 等于 888,虽然 bar 是 obj.fnn 的一个引用,但是实际上,它引用的是 fnn 函数本身,
因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
function fnn() {
console.log(this); // window 对象
console.log(this == window); // true
console.log(this.a); // 888
}
var obj = {
a: 2,
fnn: fnn
};
var bar = obj.fnn;
var a = "888"; // a 是全局对象的属性
bar(); // 888
回调函数中的例子
function fnn() {
console.log(this); // window 对象
console.log(this == window); // true
console.log(this.a); // 888
}
function doFnn(fn) {
// fn 其实引用的是 fnn
fn(); // <-- 调用位置!
}
var obj = {
a: 2,
fnn: fnn
};
var a = "888"; // a 是全局对象的属性
doFnn(obj.fnn); // 888
显式绑定
使用 call,apply 或者 bind 方法绑定
call
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
function fnn(arg1,) {
console.log(this);
console.log(this === obj); // true
console.log(this.a); // 888
console.log(arg1, arg2); // 参数一 参数二
}
var obj = {
a: 888,
fnn: fnn
};
var a = 2
fnn.call(obj, '参数一', '参数二'); // 888
apply
apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
function fnn(arg1,) {
console.log(this);
console.log(this === obj); // true
console.log(this.a); // 888
console.log(arg1, arg2); // 参数一 参数二
}
var obj = {
a: 888,
fnn: fnn
};
var a = 2
fnn.apply(obj, ['参数一', '参数二']); // 888
bind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
function fnn(arg1,) {
console.log(this);
console.log(this === obj); // true
console.log(this.a); // 888
console.log(arg1, arg2); // 参数一 参数二
}
var obj = {
a: 888,
fnn: fnn
};
var a = 2
const fnnBind = fnn.bind(obj, '参数一', '参数二');
fnnBind() // 888
bind 与 apply 和 call 的区别是,bind 是创建一个函数,但不会直接调用
new 绑定
使用 new 来调用 People(..) 时,我们会构造一个新对象并把它绑定到 People(..) 调用中的 this 上。
代码示例
// 声明一个构造函数
function People(name){
console.log(fnn != this); // true
this.name = name;
}
// 使用 new 创建实例对象 fnn
var fnn = new People("FX");
console.log(fnn);
console.log(fnn.name) // FX;
如代码和图片所示,我们 new 一个实例对象,代码执行过程如下
var fnn = {} // 创建一个空对象; 或者 var fnn = new Object()
fnn.__proto__ = People.prototype // 将该对象 fnn 的隐式原型指向构造函数显式原型
People.call(fnn, "FX") // 将构造函数中 this 指向创建的对象 fnn,并传入参数 "FX"
return fnn // 返回对象 fnn,person 指向创建的对象 fnn(对象类型赋值为按引用传递,fnn 与 person 指向同一个对象)
为什么 console.log(fnn != this) 的值为 true,关于这一点我还不是特别理解。
按照我得想法,可能是在 new 对象实例的过程中,实例对象实际并没有创建完毕,导致的不相等,如果有更好的理解,欢迎大家留言。
new 绑定遇到 retrun
function fnn () {
this.user = 'fx'
return {}
}
var a = new fnn()
console.log(a.user) // undefined
function fnn () {
this.user = 'fx'
return function () {
}
}
var a = new fnn()
console.log(a.user) // undefined
function fnn () {
this.user = 'fx'
return 1
}
var a = new fnn()
console.log(a.user) // fx
function fnn () {
this.user = 'fx'
return undefined
}
var a = new fnn()
console.log(a.user) // fx
function fn () {
this.user = 'fx'
return null
}
var a = new fn
console.log(a.user) // fx
如果返回值是一个对象,那么 this 指向的就是那个返回的对象,如果返回值不是一个对象那么 this 还是指向函数的实例。
还有一点就是虽然 null 也是对象,但是在这里 this 还是指向那个函数的实例。
优先级测试
显示绑定 与 隐式绑定
如下代码,可以看出 显示绑定 优先级大于 隐式绑定
function fnn () {
console.log(this.a)
}
var obj1 = {
a: 2,
fnn: fnn
}
var obj2 = {
a: 3,
fnn: fnn
}
obj1.fnn() // 2
obj2.fnn() // 3
obj1.fnn.call(obj2) // 3
obj2.fnn.call(obj1) // 2
new 绑定 与 隐式绑定
如下代码,可以看出 new 绑定 优先级大于 隐式绑定
function fnn (num) {
this.a = num
}
var obj1 = {
a: 2,
fnn: fnn
}
var bar = new obj1.fnn(4);
console.log(obj1.a); // 2
console.log(bar.a); // 4
new 绑定 与 显示绑定
如下代码,可以看出 new 绑定 优先级大于 显示绑定
function fnn (num) {
this.a = num
}
var obj1 = {
a: 2,
}
var bar = fnn.bind(obj1);
bar(888)
console.log(obj1.a); // 888
var baz = new bar(666);
console.log(obj1.a); // 888
console.log(baz.a); // 666
this 丢失的情况
如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则,如下代码
function fnn() {
console.log(this.a);
}
var a = 2;
fnn.call(null); // 2
间接引用的问题
如下代码,赋值表达式 p.fnn = o.fnn 的返回值是目标函数的引用,因此调用位置是 fnn() 而不是 p.fnn() 或者 o.fnn(),这里会应用默认绑定
function fnn () {
console.log(this.a)
}
var a = 2
var o = {
a: 3,
fnn: fnn
}
var p = {
a: 4
}
o.fnn(); // 3
(p.fnn = o.fnn)() // 2
箭头函数的 this
如下代码所示,fnn() 内部创建的箭头函数会捕获调用时 fnn() 的 this。由于 fnn() 的 this 绑定到 obj1,
bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不行!)
function fnn () {
// 返回一个箭头函数
return () => {
// this 继承自 fnn()
console.log(this.a)
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = fnn.call(obj1)
bar.call(obj2) // 2
非箭头函数,输出 3
function fnn () {
// 返回一个箭头函数
return function () {
// this 继承自 fnn()
console.log(this.a)
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = fnn.call(obj1)
bar.call(obj2) // 3
测试题
示例一
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); // 12
}
}
}
o.b.fn(); // 12
var o = {
a:10,
b:{
// a:12,
fn:function(){
console.log(this.a); //undefined
}
}
}
o.b.fn(); // undefined
示例二
var o = {
a: 10,
b: {
a: 12,
fn: function () {
console.log(this.a) //undefined
console.log(this) //window
}
}
}
var j = o.b.fn
j()
示例三
var x = 10
var obj = {
x: 20,
f: function () {
console.log(this.x) // 20
function fnn () {
console.log(this.x)
}
fnn() // 10 默认绑定,这里 this 绑定的是 window
}
}
obj.f()
示例四
-
fnn(1) 使用默认绑定,this.a = arg 相当于window.a = arg,return this 相当于return window -
var a = fnn(1) 相当于window.a = window,所以a.a 等于window,依次类推a.a.a.a 仍旧是 window -
fnn(10) 使用默认绑定,this.a = 10 相当于window.a = 10,return this 相当于return window -
console.log(b.a) 相当于window.a 等于 10
function fnn (arg) {
this.a = arg
return this
}
var a = fnn(1)
console.log(a.a) // window
var b = fnn(10)
console.log(b.a) // 10
示例五
var x = 10
var obj = {
x: 20,
f: function () {
console.log(this.x)
}
}
var bar = obj.f
var obj2 = {
x: 30,
f: obj.f
}
obj.f() // 20
bar() // 10
obj2.f() // 30










