前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试题之:JavaScript中this以及apply/call/bind的用法

面试题之:JavaScript中this以及apply/call/bind的用法

作者头像
用户10106350
发布2022-10-28 18:00:28
2620
发布2022-10-28 18:00:28
举报
文章被收录于专栏:WflynnWebWflynnWeb

什么是函数的调用位置

调用位置就是函数在代码中被调用的位置(而不是声明的位置)

为什么要了解调用位置:只有了解函数的调用位置才能进一步的确定 this 的绑定对象

代码语言:javascript
复制
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 的四种绑定规则

this4 种绑定规则分别是:默认绑定、隐式绑定、显式绑定、new 绑定。优先级从低到高。

new 绑定 > 显式绑定> 隐式绑定 > 默认绑定

默认绑定

最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。

如代码(非严格模式下)和图片所示

  • this.a 被解析成了 window.afnn 中的 this 是等于 window 的。
  • 由于 fnn() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则,因此 this 指向全局对象。。
代码语言:javascript
复制
function fnn() { 
  console.log(this);
  console.log(this === window);
  console.log(this.a); 
}
var a = 2; 
fnn(); // 2

隐式绑定

调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含

如代码(非严格模式下)和图片所示

  • this.a 被解析成了 obj.afnn 中的 this 是等于 obj 的。
  • 由于调用 fnn() 函数时,有引用上下文对象 obj,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象 obj,因此 this 指向 obj 对象。。
代码语言:javascript
复制
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

代码语言:javascript
复制
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,虽然 barobj.fnn 的一个引用,但是实际上,它引用的是 fnn 函数本身, 因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

代码语言:javascript
复制
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

回调函数中的例子

代码语言:javascript
复制
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

显式绑定

使用 callapply 或者 bind 方法绑定

call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数

代码语言:javascript
复制
function fnn(arg1, arg2) { 
  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 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

代码语言:javascript
复制
function fnn(arg1, arg2) { 
  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() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

代码语言:javascript
复制
function fnn(arg1, arg2) { 
  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

::: tip bindapplycall 的区别是,bind 是创建一个函数,但不会直接调用 :::

new 绑定

使用 new 来调用 People(..) 时,我们会构造一个新对象并把它绑定到 People(..) 调用中的 this 上。

代码示例

代码语言:javascript
复制
// 声明一个构造函数
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 一个实例对象,代码执行过程如下

代码语言:javascript
复制
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 指向同一个对象)

::: danger 为什么 console.log(fnn != this) 的值为 true,关于这一点我还不是特别理解。 按照我得想法,可能是在 new 对象实例的过程中,实例对象实际并没有创建完毕,导致的不相等,如果有更好的理解,欢迎大家留言。 :::

new 绑定遇到 retrun

代码语言:javascript
复制
function fnn () {
    this.user = 'fx'
    return {}
}
var a = new fnn()
console.log(a.user) // undefined
代码语言:javascript
复制
function fnn () {
    this.user = 'fx'
    return function () {
    }
}
var a = new fnn()
console.log(a.user) // undefined
代码语言:javascript
复制
function fnn () {
    this.user = 'fx'
    return 1
}
var a = new fnn()
console.log(a.user) // fx
代码语言:javascript
复制
function fnn () {
    this.user = 'fx'
    return undefined
}
var a = new fnn()
console.log(a.user) // fx
代码语言:javascript
复制
function fn () {
    this.user = 'fx'
    return null
}
var a = new fn
console.log(a.user) // fx

如果返回值是一个对象,那么 this 指向的就是那个返回的对象,如果返回值不是一个对象那么 this 还是指向函数的实例。 还有一点就是虽然 null 也是对象,但是在这里 this 还是指向那个函数的实例。

优先级测试

显示绑定 与 隐式绑定

如下代码,可以看出 显示绑定 优先级大于 隐式绑定

代码语言:javascript
复制
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 绑定 优先级大于 隐式绑定

代码语言:javascript
复制
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 绑定 优先级大于 显示绑定

代码语言:javascript
复制
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 的绑定对象传入 callapply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则,如下代码

代码语言:javascript
复制
function fnn() { 
  console.log(this.a); 
}

var a = 2; 
fnn.call(null); // 2

间接引用的问题

如下代码,赋值表达式 p.fnn = o.fnn 的返回值是目标函数的引用,因此调用位置是 fnn() 而不是 p.fnn() 或者 o.fnn(),这里会应用默认绑定

代码语言:javascript
复制
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 绑定到 obj1bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不行!)

代码语言:javascript
复制
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

代码语言:javascript
复制
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

测试题

示例一

代码语言:javascript
复制
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

示例二

代码语言:javascript
复制
var o = {
    a: 10,
    b: {
        a: 12,
        fn: function () {
            console.log(this.a) //undefined
            console.log(this) //window
        }
    }
}
var j = o.b.fn
j()

示例三

代码语言:javascript
复制
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 = argreturn 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 = 10return this 相当于 return window
  • console.log(b.a) 相当于 window.a 等于 10
代码语言:javascript
复制
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

示例五

代码语言:javascript
复制
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
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WflynnWeb 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是函数的调用位置
  • this 是什么
  • this 的四种绑定规则
    • 默认绑定
      • 隐式绑定
        • 隐式丢失
      • 显式绑定
      • new 绑定
      • 优先级测试
      • this 丢失的情况
      • 间接引用的问题
      • 箭头函数的 this
      • 测试题
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档