作者::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