详解JS中的this、apply、call、bind(经典面试题)


Posted in Javascript onSeptember 19, 2017

这又是一个面试经典问题~/(ㄒoㄒ)/~~也是 ES5中众多坑中的一个,在 ES6 中可能会极大避免 this 产生的错误,但是为了一些老代码的维护,最好还是了解一下 this 的指向和 call、apply、bind 三者的区别。

this 的指向

在 ES5 中,其实 this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象,来,跟着我朗读三遍:this 永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象。记住这句话,this 你已经了解一半了。

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

例 1:

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

这个相信大家都知道为什么 log 的是 windowsName,因为根据刚刚的那句话“this 永远指向最后调用它的那个对象”,我们看最后调用 a 的地方 a();,前面没有调用的对象那么就是全局对象 window,这就相当于是 window.a();注意,这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined,那么就会报错 Uncaught TypeError: Cannot read property 'name' of undefined。

再看下这个例子:

例 2:

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

在这个例子中,函数 fn 是对象 a 调用的,所以打印的值就是 a 中的 name 的值。是不是有一点清晰了呢~

我们做一个小小的改动:

例 3:

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

这里打印 Cherry 的原因也是因为刚刚那句话“this 永远指向最后调用它的那个对象”,最后调用它的对象仍然是对象 a。

我们再来看一下这个例子:

例 4:

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

这里为什么会打印 undefined 呢?这是因为正如刚刚所描述的那样,调用 fn 的是 a 对象,也就是说 fn 的内部的 this 是对象 a,而对象 a 中并没有对 name 进行定义,所以 log 的 this.name 的值是 undefined。

这个例子还是说明了:this 永远指向最后调用它的那个对象,因为最后调用 fn 的对象是 a,所以就算 a 中没有 name 这个属性,也不会继续向上一个对象寻找 this.name,而是直接输出 undefined。

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

例 5:

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 永远指向最后调用它的那个对象。

再来看一个例子:

例 6:

var name = "windowsName";
 function fn() {
  var name = 'Cherry';
  innerFunction();
  function innerFunction() {
   console.log(this.name);  // windowsName
  }
 }
 fn()

读到现在了应该能够理解这是为什么了吧(o゚?゚)o。

怎么改变 this 的指向

改变 this 的指向我总结有以下几种方法:

使用 ES6 的箭头函数

在函数内部使用 _this = this

使用 apply、call、bind

new 实例化一个对象

例 7:

var name = "windowsName";
 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)  
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   },100);
  }
 };
 a.func2()  // this.func1 is not a function

在不使用箭头函数的情况下,是会报错的,因为最后调用 setTimeout 的对象是 window,但是在 window 中并没有 func1 函数。

我们在改变 this 指向这一节将把这个例子作为 demo 进行改造。

箭头函数

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

例 8 :

var name = "windowsName";
 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)  
  },
  func2: function () {
   setTimeout( () => {
    this.func1()
   },100);
  }
 };
 a.func2()  // Cherry

在函数内部使用 _this = this

如果不使用 ES6,那么这种方式应该是最简单的不会出错的方式了,我们是先将调用这个函数的对象保存在变量 _this 中,然后在函数中都使用这个 _this,这样 _this 就不会改变了。

例 9:

var name = "windowsName";
 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)  
  },
  func2: function () {
   var _this = this;
   setTimeout( function() {
    _this.func1()
   },100);
  }
 };
 a.func2()  // Cherry

这个例子中,在 func2 中,首先设置 var _this = this;,这里的 this 是调用 func2 的对象 a,为了防止在 func2 中的 setTimeout 被 window 调用而导致的在 setTimeout 中的 this 为 window。我们将 this(指向变量 a) 赋值给一个变量 _this,这样,在 func2 中我们使用 _this 就是指向对象 a 了。

使用 apply、call、bind

使用 apply、call、bind 函数也是可以改变 this 的指向的,原理稍后再讲,我们先来看一下是怎么实现的:

使用 apply

例 10:

var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   }.apply(a),100);
  }
 };
 a.func2()   // Cherry

使用 call

例 11:

var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   }.call(a),100);
  }
 };
 a.func2()   // Cherry

使用 bind

例 12:

var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   }.bind(a)(),100);
  }
 };
 a.func2()   // Cherry

apply、call、bind 区别

刚刚我们已经介绍了 apply、call、bind 都是可以改变 this 的指向的,但是这三个函数稍有不同。

在 MDN 中定义 apply 如下;

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

语法:

fun.apply(thisArg, [argsArray])

thisArg:在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。

apply 和 call 的区别

其实 apply 和 call 基本类似,他们的区别只是传入的参数不同。

call 的语法为:

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

所以 apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

例 13:

var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.apply(a,[1,2])  // 3

例 14:

var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.call(a,1,2)  // 3

bind 和 apply、call 区别

我们先来将刚刚的例子使用 bind 试一下

var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.bind(a,1,2)

我们会发现并没有输出,这是为什么呢,我们来看一下 MDN 上的文档说明:

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

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

var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.bind(a,1,2)()   // 3

总结

以上所述是小编给大家介绍的JS中的this、apply、call、bind,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
Javascript 不能释放内存.
Sep 07 Javascript
用 JavaScript 迁移目录
Dec 18 Javascript
jQuery Lightbox 图片展示插件使用说明
Apr 25 Javascript
去掉gridPanel表头全选框的小例子
Jul 18 Javascript
用console.table()调试javascript
Sep 04 Javascript
jQuery tagsinput在h5邮件客户端中应用详解
Sep 26 Javascript
Vue实现PopupWindow组件详解
Apr 28 Javascript
深入理解与使用keep-alive(配合router-view缓存整个路由页面)
Sep 25 Javascript
koa2实现登录注册功能的示例代码
Dec 03 Javascript
layui字体图标 loading图标静止不旋转的解决方法
Sep 23 Javascript
React Ant Design树形表格的复杂增删改操作
Nov 02 Javascript
Vue实现省市区三级联动
Dec 27 Vue.js
JavaScript 中的 this 简单规则
Sep 19 #Javascript
在 Node.js 中使用原生 ES 模块方法解析
Sep 19 #Javascript
Webpack 服务器端代码打包的示例代码
Sep 19 #Javascript
原生js中ajax访问的实例详解
Sep 19 #Javascript
angularjs实现过滤并替换关键字小功能
Sep 19 #Javascript
详解vue-cli与webpack结合如何处理静态资源
Sep 19 #Javascript
前端常见跨域解决方案(全)
Sep 19 #Javascript
You might like
在线竞拍系统的PHP实现框架(二)
2006/10/09 PHP
php设计模式之命令模式的应用详解
2013/05/21 PHP
Laravel 5.4.36中session没有保存成功问题的解决
2018/02/19 PHP
Yii框架核心组件类实例详解
2019/08/06 PHP
TP5(thinkPHP5)框架使用ajax实现与后台数据交互的方法小结
2020/02/10 PHP
php5.3/5.4/5.5/5.6/7常见新增特性汇总整理
2020/02/27 PHP
直接生成打开窗口代码,不必下载
2008/05/14 Javascript
javascript实现淘宝幻灯片广告展示效果
2015/04/27 Javascript
JS封装cookie操作函数实例(设置、读取、删除)
2015/11/17 Javascript
BootstrapValidator超详细教程(推荐)
2016/12/07 Javascript
EditPlus中的正则表达式 实战(4)
2016/12/15 Javascript
Bootstrap面板使用方法
2017/01/16 Javascript
JS去除字符串中空格的方法
2017/02/14 Javascript
JavaScript寄生组合式继承实例详解
2018/01/06 Javascript
socket在egg中的使用实例代码详解
2019/05/30 Javascript
基于javascript实现贪吃蛇经典小游戏
2020/04/10 Javascript
JS实现“全选”和"全不选"功能代码实例
2020/02/06 Javascript
详解Node.js使用token进行认证的简单示例
2020/05/25 Javascript
Python Tkinter简单布局实例教程
2014/09/03 Python
Python Json模块中dumps、loads、dump、load函数介绍
2018/05/15 Python
朴素贝叶斯Python实例及解析
2018/11/19 Python
django xadmin action兼容自定义model权限教程
2020/03/30 Python
pyinstaller打包成无控制台程序时运行出错(与popen冲突的解决方法)
2020/04/15 Python
Python ckeditor富文本编辑器代码实例解析
2020/06/22 Python
全球知名旅游社区巴西站点:TripAdvisor巴西
2016/07/21 全球购物
意大利体育用品网上商城:Nencini Sport
2016/08/18 全球购物
Expedia印度尼西亚站:预订酒店、廉价航班和度假套餐
2018/01/31 全球购物
瑞典香水、须后水和美容产品购物网站:Parfym-Klick.se
2019/12/29 全球购物
幼儿园教师节演讲稿
2014/09/03 职场文书
收款委托书范本
2014/09/11 职场文书
建设办主任四风问题整改思路和措施
2014/09/20 职场文书
2014年保管员工作总结
2014/11/18 职场文书
幼儿教师继续教育培训心得体会
2016/01/19 职场文书
深入详解JS函数的柯里化
2021/06/09 Javascript
nginx实现多geoserver服务的负载均衡
2022/05/15 Servers
python playwright之元素定位示例详解
2022/07/23 Python