javascript中令人迷惑的this

JS中的this很多时候会让人捉摸不透,不知道这个this到底指向的是什么。现在根据自己的理解写下这篇文章做一个总结。

我们知道this指向谁一般情况下是在运行时决定的,并且运行时决定this指向的因素又有很多,例如是不是被bind了,或者调用的时候使用了apply和call这类方法,还有是不是通过new来调用这个函数,如果没有以上显示绑定,那么是obj.fn()这样调用的吗?或者直接fn()?如果直接fn()调用,那么fn的函数体是严格模式吗?最后这个函数是ES6中的箭头函数吗?

默认绑定和隐式绑定

首先看最常见的调用方式,通过对象调用这个函数或者叫方法(this隐式绑定到了调用函数的对象上)。

var a = 2
var obj = {a: 1,fn: function () {console.log(this.a)}
}obj.fn()

我们通过运行知道打印了1,也就是说这时fn中的this指向了调用他的obj,但是是否表示任何情况下fn中的this都是指向fn定义时的对象obj呢?显然不是的。在某些情况下这种隐式绑定的this会丢失,如下:

var fn1 = obj.fn
fn1()

上面打印了2,是的出乎意料并没有打印1,所以关于this的指向和函数的定义没有什么关系,看似函数fn属于对象obj,其实并不是。这时fn中this默认指向的是window对象。上面通过赋值就弄丢了原本的隐式绑定,没有了隐式绑定,只能使用默认绑定。

现在我们切换到严格模式:

'use strict'
var a = 2
var obj = {a: 1,fn: function () {console.log(this.a)}
}var fn1 = obj.fn
fn1()

我们发现执行fn1的时候报了一个错误Uncaught TypeError: Cannot read property 'a' of undefined,这是因为在严格模式下fn1中的this指向的undefined,获取undefined的属性当然会报错,因为undefined不是一个对象也不能隐式转换成一个对象。

注:上面通过将对象的方法赋值给一个变量导致函数的方法中默认绑定this丢失,这种情况会出现在很多其他意想不到的地方,例如函数的传参(这也是一种隐式的赋值)。

显示调用call和apply的绑定

上面无论是通过对象还是直接通过函数名调用函数,其中的this指向谁好像编译器心里有数是一种默契。那么我们能不能不要这个默契,我们自己来指定函数调用的时候this指向谁。我们通过call方法和apply方法就可以轻易做到。

var a = 2
var obj = {a: 1,fn: function () {console.log(this.a)}
}
var fn1 = obj.fn
fn1.call(obj)

我们可以看到又打印出了1,不负所望。调用fn1的时候我们通过结果可以知道函数体内的this被绑定到了obj上。apply做的事情和call是一样的,区别就在于传入函数中参数的形式,call必须要和调用函数一样一个一个传入参数,但是apply允许我们通过一个数组将需要的参数一起传入函数中。这个神奇的功能就像是ES6中的 … 操作符。

bind的绑定

bind的也可以绑定函数中的this,但是和上面的call和apply有明显的不同,call和apply是直接就执行了函数,但是bind不是,bind会返回一个函数,这个特性就让这个bind不仅仅可以绑定this,还可以进行函数柯里化。

var a = 2
var obj = {a: 1,fn: function () {console.log(this.a)}
}
var fn1 = obj.fn
var fn2 = fn1.bind(obj)
fn2()

不出意料的也打印了1。

bind的简单Polyfill

if (!Function.prototype.bind) {Function.prototype.bind = function (obj) {// 这一句检测this是不是函数我本以为是多余,但是bind是可以被call的,这时候this就很有可能不指向functionif (typeof this !== 'function') {throw new TypeError('不是函数的数据尝试调用bind方法!')}// obj 就是函数要绑定的this,而函数就是现在函数体中的this,因为bind函数是在Function.prototype上的// 这是在获取在bind的时候就传过来的参数var args = [].slice.call(arguments, 1)// 存一下需要bind的函数var fn = this    // 处理fn函数的prototype属性var _fn = function () {}// 这个函数将被返回var bindFn = function () {// 处理一下被bind的函数使用new调用的时候return fn.apply(this instanceof bindFn ? this : obj, args.concat([].slice.call(arguments)))}        // 处理fn.prototypeif (fn.prototype) {_fn.prototype = fn.prototype}bindFn.prototype = new _fn()return bindFn}
}

上面的兼容是类似mozilla的写法,不仅仅绑定了this还考虑到了参数的拼接,还有函数的prototype属性的处理,还包括被bind的函数作为构造函数调用的时候其中this的指向。

new的this绑定

可以绑定this的不仅仅是上面的call,apply和bind,new也可以的。我们知道通过new调用一个函数的时候会有下面几个步骤:

  1. 新建一个对象
  2. 将对象的原型关联到函数的原型属性
  3. 将this指向这个对象
  4. 执行函数
  5. 如果函数没有返回值则返回这个对象

看上面第二步就将函数中的this关联到了新建的对象上了。那么对于一个bind的函数我们使用new来调用函数中的this到底是指向了new新建出来的对象还是bind时候的对象呢?

其实上面bind方法的Polyfill已经给出了答案,是会指向new新建出来的对象。fn.apply(this instanceof bindFn ? this : obj, args.concat([].slice.call(arguments))),这里通过this instanceof bindFn判断是不是通过new调用的该方法,如果是那么就指向当前的this也就是新建的对象,如果不是才指向传进来的obj。

##绑定的优先级

通过上面bind的Polyfill我们知道new绑定的this优先级要大于显示绑定的bind,并且bind的绑定优先级要高于call和apply方法。

隐藏是绑定优先级要高于默认绑定并且低于显示绑定的call和apply方法。

所以整理出来的优先级如下:

new > bind > (apply == call) > 隐式绑定 > 默认绑定

关于箭头函数

ES6的箭头函数和上面说的情况都不一样,箭头函数中的this指向并不是在调用的时候确定的,而是在定义的时候,和定义的时候的词法作用域有关,并且后期并不能通过上面显示绑定的方法修改this的指向。也就是说箭头函数定义的时候拿到当前上下文的this,然后就不会再改变了。

var a = 2
var obj = {a: 1,fn: function () {console.log(this.a)var b = () => {console.log(this)}b()}
}
obj.fn()

上面打印出了1 和 obj 对象

var a = 2
var obj = {a: 1,fn: function () {console.log(this.a)var b = () => {console.log(this)}b.call(window)}
}
obj.fn()

虽然使用了call指定this绑定,但是还是打印了1和obj对象,而不是window。call方法并没能修改箭头函数的this指向。

var a = 2
var obj = {a: 1,fn: function () {console.log(this.a)var b = () => {console.log(this)}var c = b.bind(window)c()}
}
obj.fn()

结果和call的绑定一致没有改变箭头函数中的this。

那么能使用new呢?箭头函数不能使用new调用,会报错的。

参考

你不知道的javascript上卷

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362139.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

容易忽视但是功能灰常强大的Java API(五. 二分查找)

五. 二分查找 二分查找是一个高效的查找算法,在java的集合对象中也提供了二分查找的算法,如下面的java api接口: java.util.Arrays.binarySearch(java.lang.Object,java.lang.Object,java.util.Comparator) java.util.Arrays.binarySear…

打印 PRINT

打印 PRINT 字符串和数值类型 可以直接输出。 print(1) #out:1 print(a) #out:a 变量 无论什么类型,数值,字符串,列表,字典...都可以直接输出 n 1 s a list_a [1,3,4] dict_c {a:3,b:4} print(n) #out&…

css3帮你轻松实现圆角效果,不一样的前端页面。

在Web前端页面实现圆角效果,CSS3帮你轻松实现,一个人人皆知的属性 圆角边框的绘制是Web页面和Web应用程序中经常用来美化页面效果的手法之一。今天,小编为大家介绍CSS3提供的可以将矩形变为圆角矩形的一个属性 技术等级:中级 | 适…

使用Vysper,TomEE和PrimeFaces将XMPP服务器嵌入JSF Web应用程序内部

我有一个需要在完成某些工作时通知用户的应用程序。 它使用JSF和Primefaces,因此可以使用大气 (也称为Push)来实现这种通知。 但是另一个有趣的方法是使用嵌入在Java Web应用程序中的XMPP服务器。 好的,好的,您不必嵌…

appium和selenium不同与相同之处

原文来自: https://www.cnblogs.com/zhengshuheng/p/6370398.html selenium是web端的自动化,appium是app端的自动化,它继承了webdriver(也就是selenium 2) 转载于:https://www.cnblogs.com/lv-lxz/p/11118862.html

Mockito 101

Mockito是一个模拟框架,可让您使用简洁的API编写漂亮的测试。 它偏向于最低规格,使不同的行为看起来有所不同,并显示清晰的错误消息。 创造嘲弄 要使用Mockito创建模拟,只需使用Mock注释模拟,然后调用MockitoAnnotati…

前端开发常用代码片段(下篇)

二十二、正则表达式 //验证邮箱/^\w ([0-9a-zA-Z] [.]) [a-z]{2,4}$///验证手机号/^1[3|5|8|7]\d{9}$///验证URL/^http:\/\/. \.///验证身份证号码/(^\d{15}$)|(^\d{17}([0-9]|X|x)$)///匹配字母、数字、中文字符/^([A-Za-z0-9]|[\u4e00-\u9fa5])*$///匹配中文字符/[\u4e00-\u9…

使用 Visual Studio 编译 wget 为库文件

添加代码与预编译指令与上一篇使用 Visual Studio 编译 wget 为可执行文件一致,区别在于这回建的是静态库工程(编译为动态库过程类似:)) 从wget的main函数开始读下来,发现问题不少,程序可能基于效率或者编码方便的因素…

动态规划(0-1背包)--- 改变一组数的正负号使得它们的和为一给定数

改变一组数的正负号使得它们的和为一给定数 494. Target Sum (Medium) Input: nums is [1, 1, 1, 1, 1], S is 3. Output: 5 Explanation:-11111 3 1-1111 3 11-111 3 111-11 3 1111-1 3There are 5 ways to assign symbols to make the sum of nums be target 3. 题目描述…

关于设计模式的胡思乱想

设计模式是一个指导,并不强制。有很多地方并不需要设计模式介入,因为设计模式是分离变化,很多代码是一次性的,不会变。如果我们一开始写程序的时候就加入设计模式,这样就显得过度设计,既耗时又费力。 并且…

JSR 310新日期/时间API的自定义JSR 303 Bean验证约束

借助JSR 310,Java 8终于为我们带来了不错的日期和时间API。 对于仍在使用Java 7的那些人(例如我目前在我的当前项目中),有很好的反向移植,请访问www.threeten.org了解更多详细信息。 但是,由于有关该主题的…

H5之audio标签放音兼容所有浏览器方法

前端交流群&#xff0c;群文件提供大量文档、书籍和资料。期待你的加入&#xff01;群号&#xff1a;127768464 由于项目需要&#xff0c;最近刚做了一个网页放音的功能&#xff0c;使用到了H5新标签<audio></audio>&#xff0c;但是audio只能支持IE8以上的浏…

VisualStudio2010 SP1 SP1 SDK SQLServer 2008 SP3 下载地址

SP1:http://go.microsoft.com/fwlink/?LinkId210710 SP1 SDK:http://www.microsoft.com/download/en/details.aspx?id21835 SQLServer2008 sp3:http://www.microsoft.com/downloads/zh-cn/details.aspx?familyid757bca07-7b52-46fb-ab92-476be9339442&displaylangzh-cn …

python高级特性:迭代器与生成器

前言 有一个列表 l [a,b,c,d,e] 我想取列表中的内容&#xff0c;有几种方式&#xff1f; # 1.最简单的方法用for循环。 for i in l:print(i,end" ") # 2.用列表内置的方法。 while 1:if l:print(l.pop(),end" ") 这是用常见的方式取出列表中的数据。 …

webpack中实现按需加载

当页面中一个文件过大并且还不一定用到的时候&#xff0c;我们希望在使用到的时候才开始加载这个文件俗称按需加载。这样可以减少页面的响应时间&#xff0c;提高访问速度。 使用webpack打包的出来的文件要实现以上的要求有两种方式&#xff0c;一个是webpack特有的require.en…

H5各种头部meta标签的功能

<!DOCTYPE html> H5标准声明&#xff0c;使用 HTML5 doctype&#xff0c;不区分大小写 <head lang”en”> 标准的 lang 属性写法 <meta charset’utf-8′> 声明文档使用的字符编码 <meta http-equiv”X-UA-Compatible” content”IEedge,chrome1″…

如何使用单例EJB和MBean构建和清除参考数据缓存

在我的一个项目中&#xff0c;我需要使用EclipseLink作为ORM框架从Java EE 6 WebLogic环境中的多个源中加载参考数据。 由于我在Java EE世界中找不到与Spring YET的Cacheable相当的注释&#xff0c;因此我不得不编写自己的缓存解决方案。 尽管参考数据几乎不会随时间变化&#…

让 UV4 支持STC 单片机

Keil 本身并没有自带 STC 8051 单片机的数据&#xff0c;这样用起来就非常令人不爽&#xff0c;好在国内有个青年已经整理了一个补丁。 这个补丁包含三个修正&#xff1a; 汉字bug修正&#xff0c;这个是老生常谈了&#xff0c;很奇怪Keil十年来都没有修正这个&#xff0c;说明…

【总结】计算机网络常见问题

1、TCP/IP协议与OSI协议 相互通信的两个计算机系统必须高度协调工作才行&#xff0c;而这种“协调”是相当复杂的。 “分层”可将庞大而复杂的问题&#xff0c;转化为若干较小的局部问题&#xff0c;而这些较小的局部问题就比较易于研究和处理。 计算机网络采用了分层的体系结构…

webpack3的CommonsChunkPlugin插件详解

webpack打出来的包在不做处理的情况下是非常大的&#xff0c;所有依赖都被塞进一个文件中&#xff0c;文件中有业务代码&#xff0c;有业务代码依赖的第三方库代码&#xff0c;还有webpack生成的运行时代码等。这样的一个文件不方便静态资源缓存&#xff0c;并且初始化页面的时…