一文搞定this、apply、call、bind

简介: 一文搞定this、apply、call、bind

一文搞定this、apply、call、bind

背景

JavaScript中最容易被误解的一个方面是this关键字。看我本篇文章,它们将不会是问题。

this的指向

ES5 中,「this 永远指向最后调用它的那个对象」

下面我们来看一个最简单的例子:

var name = "windowsName";
function a() {
    var name = "Cherry";
    console.log(this.name);          // windowsName
    console.log("inner:" + this);    // inner: Window
}
a();
console.log("outer:" + this)         // outer: Window

我们看最后调用 a 的地方 a();,前面没有调用的对象那么就是全局对象 window,这就相当于是 window.a();注意,这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined,那么就会报错 Uncaught TypeError: Cannot read property 'name' of undefined

再看下这个例子:

var name = "windowsName";
var a = {
    name: "Cherry",
    fn : function () {
        console.log(this.name);      // Cherry
    }
}
a.fn();

在这个例子中,函数 fn 是对象 a 调用的,所以打印的值就是 a 中的 name 的值。

再来看一个比较坑的例子:

var name = "windowsName";
var a = {
    name : null,
    // name: "Cherry",
    fn : function () {
        console.log(this.name);      // windowsName
    }
}
var f = a.fn;
f();

这里你可能会有疑问,为什么不是 Cherry,这是因为虽然将 a 对象的 fn 方法赋值给变量f 了,但是没有调用,this 永远指向最后调用它的那个对象」,由于刚刚的 f并没有调用,所以 fn() 最后仍然是被window 调用的。所以 this 指向的也就是 window

由以上五个例子我们可以看出,this的指向并不是在创建的时候就可以确定的,在 ES5 中,this 永远指向最后调用它的那个对象」

怎么改变 this 的指向

我们看个栗子:

var name = '夏安';
var obj = {
  name: '...夏安',
  print: function () {
    setTimeout(function() {
      console.log(this.name);
    }, 0);
  }
}
obj.print(); // undefined

实际上调用匿名函数的是Timeout对象,所以this指的是Timeout对象,因为Timeout对象没有name属性,所以输出undefined。那么如果我们想让this指向obj,我们该怎么操作呢?

箭头函数

众所周知,ES6 的箭头函数是可以避免 ES5 中使用 this 的坑的。「箭头函数的 this 始终指向函数定义时的 this,而非执行时。」,箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,thisundefined”。

var name = '夏安';
var obj = {
  name: '...夏安',
  print: function () {
    setTimeout(() => {
      console.log(this.name);
    }, 0);
  }
}
obj.print(); // ...夏安

在函数内部使用 _this = this

var name = '夏安';
var obj = {
  name: '...夏安',
  print: function () {
    var _this = this;
    setTimeout(function() {
      console.log(_this.name);
    }, 0);
  }
}
obj.print(); // ...夏安

print中,首先设置 var _this = this;,这里的 this 是调用 print 的对象 obj,我们将 this(指向变量 obj) 赋值给一个变量 _this,这样,在 「匿名函数」 中我们使用 _this 就是指向对象 obj 了。

使用 call

call是每个函数的一个方法,它允许你调用函数,指定调用函数的上下文。语法如下:

fun.call(thisArg[, arg1[, arg2[, ...]]])

call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。

var name = '夏安';
var obj = {
  name: '...夏安',
  print: function (name) {
    console.log(this.name);
    console.log(name);
  }
}
let { print } = obj;
print.call(obj, '夏安...'); 
// ...夏安
// 夏安...

使用 apply

其实 applycall 基本类似,他们的区别只是传入的参数不同。applycall 的区别是 call方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

var name = '夏安';
var obj = {
  name: '...夏安',
  print: function (name) {
    console.log(this.name);
    console.log(name);
  }
}
let { print } = obj;
print.apply(obj, ['夏安...']); 
// ...夏安
// 夏安...

使用 bind

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

所以我们可以看出,bind 是创建一个新的函数,我们必须要手动去调用:

var name = '夏安';
var obj = {
  name: '...夏安',
  print: function (name) {
    console.log(this.name);
    console.log(name);
  }
}
let { print } = obj;
print.bind(obj, '夏安...')(); 
// ...夏安
// 夏安...

手写 call

function mySymbol(obj) {
  let unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8);
  if (obj.hasOwnProperty(unique)) {
    return mySymbol(obj); //递归调用
  } else {
    return unique;
  }
}
Function.prototype.myCall = function (context) {
  // 如果没有传或传的值为空对象 context指向window
  context = context || window;
  let fn = mySymbol(context);
  context[fn] = this; //给context添加一个方法 指向this
  // 处理参数 去除第一个参数this 其它传入fn函数
  let arg = [...arguments].slice(1); //[...xxx]把类数组变成数组
  context[fn](...arg); //执行fn
  delete context[fn]; //删除方法
}
let Person = {
  name: '夏安...',
  say(age) {
    console.log(this);
    console.log(`我叫${this.name}我今年${age}`);
  }
}
Person1 = {
  name: '夏安'
}
Person.say.myCall(Person1, 18); //我叫夏安我今年18

手写 apply

function mySymbol(obj) {
  let unique = (Math.random() + new Date().getTime()).toString(32).slice(0, 8);
  if (obj.hasOwnProperty(unique)) {
    return mySymbol(obj); //递归调用
  } else {
    return unique;
  }
}
Function.prototype.myApply = function (context) {
  // 如果没有传或传的值为空对象 context指向window
  context = context || window;
  let fn = mySymbol(context);
  context[fn] = this; //给context添加一个方法 指向this
  // 处理参数 去除第一个参数this 其它传入fn函数
  let arg = [...arguments].slice(1); //[...xxx]把类数组变成数组
  context[fn](...arg[0]); //执行fn
  delete context[fn]; //删除方法
}
let Person = {
  name: '夏安...',
  say(age) {
    console.log(this);
    console.log(`我叫${this.name}我今年${age}`);
  }
}
Person1 = {
  name: '夏安'
}
Person.say.myApply(Person1, [18]); //我叫夏安我今年18

手写 bind

Function.prototype.myBind = function (context) {
  //返回一个绑定this的函数,我们需要在此保存this
  let self = this
  // 可以支持柯里化传参,保存参数
  let arg = [...arguments].slice(1)
  // 返回一个函数
  return function () {
    //同样因为支持柯里化形式传参我们需要再次获取存储参数
    let newArg = [...arguments]
    console.log(newArg)
    // 返回函数绑定this,传入两次保存的参数
    //考虑返回函数有返回值做了return
    return self.apply(context, arg.concat(newArg))
  }
}
let Person = {
  name: '夏安...',
  say(age) {
    console.log(this);
    console.log(`我叫${this.name}我今年${age}`);
  }
}
Person1 = {
  name: '夏安'
}
Person.say.myBind(Person1, 18)(); //我叫夏安我今年18
相关文章
new bind apply call instanceof 等笔记
new bind apply call instanceof 等笔记
39 0
|
1月前
|
JavaScript 前端开发 开发者
call、bind、apply区别
【10月更文挑战第26天】`call`、`bind` 和 `apply` 方法在改变函数 `this` 指向和参数传递方面各有特点,开发者可以根据具体的需求和使用场景选择合适的方法来实现更灵活和高效的JavaScript编程。
32 1
|
2月前
|
前端开发 JavaScript
比较一下apply/call/bind ?
本文首发于微信公众号“前端徐徐”,详细介绍了 JavaScript 中 `apply`、`call` 和 `bind` 方法的概念、使用场景及手动实现。主要内容包括: - **apply**:使用数组作为参数调用函数,并指定 `this`。 - **call**:直接传递参数调用函数,并指定 `this`。 - **bind**:返回一个绑定了 `this` 和部分参数的新函数。 文章还对比了这三个方法的区别,并提供了手动实现的代码示例。
26 2
|
7月前
call\apply\bind详解
call\apply\bind详解
37 0
bind、call、apply 区别
bind、call、apply 区别
82 0
call、apply、bind笔记
call、apply、bind笔记
65 0
apply、bind和call
apply、bind和call
93 0
|
前端开发