对this的理解与总结
this既不指向函数自身,也不指向函数的词法作用域!它指向谁完全取决于它在哪里被调用,被谁调用!
绑定规则
总体来说,this的绑定规则有:
- 默认绑定(严格模式/非严格模式)
- 隐式绑定
- 显式绑定
- new存在时绑定
- 箭头函数绑定
1.默认绑定:
默认绑定就是没有应用其他绑定规则时的绑定方式。
在非严格模式下,直接调用函数默认this指向全局对象,即window。这里要注意!即使是在某个函数中,如果自执行某个函数或者是使用setTimeout内部执行某个函数,它的this都指向全局变量!
var value = 1;
var obj = {
value: 2
}
function funA() {
setTimeout(function () {
console.log(this.value)
}, 100)
} funA.call(obj) // 1,这里setTimeou相当于window调用这个函数,即默认调用。
如果想输出obj内部定义的value,可以在setTimeout外层设变量var that = this; 然后console.log(that.value)即可。将this设定指向本函数的this。
在严格模式下,并不能将全局对象用于默认绑定,this会绑定到undefined
function foo() {
'use strict' // 严格模式下会输出undefined,非严格模式输出2 console.log(this.a);
}
var a = 2;
foo()
但是在严格模式下,调用函数不影响默认绑定!
function foo() {
console.log(this.a);
}
var a = 2;
(function () {
'use strict' foo() // 在严格模式下调用函数并不影响默认绑定!所以输出2
})()
2.隐式绑定:
就是前面有对象引用时,this会指向这个对象,如果有多层,则只有上一层起作用!并不会一直向上寻找!!!切记!!!
var obj = {
a: 1,
b: 2,
getA: function () {
console.log(this.a)
},
getB: {
b: 4,
subGetA: function () {
console.log(this.a)
},
subGetB: function () {
console.log(this.b)
}
}
} obj.getA() //
obj.getB.subGetA() // undefined,这里因为getB中并没有a,因此输出undefined
obj.getB.subGetB() //
有时候会有隐式丢失的情况出现,比如:
// 代码同上,加如下语句: var a = "global"
var other = obj.getA // 将obj.getA赋值给other,other执行的时候其实是相当于 other(){ console.log(this.a) } other() // "global"
此时,other函数实际上是var other = function(){ consle.log(this.a) },相当于默认绑定this,this指向window!(自我感觉这时的other其实与obj和getA摆脱了关系,刚才那一步也就是进行了赋值操作而已~)
间接引用时(最容易发生在赋值操作!!),调用函数会应用默认绑定规则:
function foo() {
console.log(this.a)
}; var a = 1; var obj = {
a: 3,
foo: foo
}; var fun = {
a: 2
}; (fun.foo = obj.foo)() // // 如果将上面一句拆成如下两句来写,结果又不同
fun.foo = obj.foo
fun.foo() //
(fun.foo = obj.foo)得到的结果是( function foo(){ console.log(this.a) } ),所以这个函数执行等同于自执行foo这个函数,this指向全局对象
两句拆开后,第一句是将obj的foo函数赋值给fun,此时,fun对象内部也有了一个foo函数,那fun再调用foo则是应用了this的隐式调用规则,this指向调用对象fun,因此这时输出2
函数中传入参数也属于隐式丢失的一种:
var obj = {
a: 1,
getA: function () {
console.log(this.a)
}
} var a = "global"; function getFn(fn) { // 这里的fn = obj.getA,相当于上面一例当中的other = obj.getA
fn()
} getFn(obj.getA) // global
此时,obj.getA当作参数传到getFn中,这时在getFn中调用它等同于默认调用,this指向全局对象或undefined。
与此情况类似的还有使用定时器调用函数,setTimeout(function(){ ... //在函数中的this也存在隐式丢失 }, time),原理同参数传递!
3.显示绑定:使用call()/apply()
call()和applay()的区别:前者接受的是若干参数的列表,而后者接受的是一个包含多个参数的数组
fn.call(obj, arg1, arg2,...)
fn.apply(obj, [arg1, arg2,...])
延伸一下:
fn.bind(obj, [arg1, [arg2, [, ...]]])
bind的特性:
- 指定this
- 返回一个函数
- 可以指定参数
- 柯里化
call() applay()和bind的区别:call和apply是直接执行了前面的fn函数,而bind()返回的是一个新函数,当再次调用它时,它的this值会绑定到obj上。
call()和apply()中的第一个参数为一个对象,使用call()或applay()会默认将this绑定到这个对象上。
function foo() {
console.log(this.a)
} var obj = {
a: 3
} var fun = function () {
foo.call(obj) // 强制把foo的this绑定到了obj上
} fun() //
setTimeout(fun, 1000) //
fun.call(window) // 3 fun函数内部已经存在硬绑定,不可能再修改它的this
显示绑定无法解决丢失绑定的问题。?????啥意思????
有时候把null或者undefined作为this的绑定对象传入call()、apply()的时候,实际应用的是默认规则!即相当于没有应用显示绑定!!
但是,有以下两种情况可以利用null:
- 使用apply()展开一个数组,因为apply的语法是:apply(obj, [para1, para2, para3...]),参数放在一个数组当中传入到函数中
- 利用bind预先设置一些参数
function foo(a, b) {
console.log("a:" + a + ",b:" + b);
} // 1. 利用null把数组“展开”成参数
foo.apply(null, [2, 3]); // a:2,b:3 // 2. 利用bind(..)进行柯里化(即预先设置一些参数)
var bar = foo.bind(null, 2);
bar(3); // a:2,b:3
但是,传入null会带来一些副作用:比如某个函数确实使用了this,那默认绑定规则会把this绑定到全局对象中
所以,可以用var empty = Object.create(null);生成一个空对象,用它代替null,此时的empty不会创建Object.prototype这个委托,比{}更空!
4.new绑定:
构造函数和普通函数的差别就在于:构造函数是被new操作符调用的函数,会生成一个实例对象。该实例对象有两个特性:1. 能访问到构造函数内部的属性;2. 能访问到原型里的属性
使用new调用函数,会自动执行如下操作:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此,this指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
function create() {
// 创建一个对象
var obj = new Object(); // 将arguments的第一个参数,即作为要传入的构造函数
var Constructor = [].shift.call(arguments); // 将obj的原型指向构造函数
obj.__proto__ = Constructor.prototype; // 改变Constructor的this指向,指到obj对象,并给obj添加新属性arguments
Constructor.apply(obj, arguments);
// 返回obj
return obj;
}
解析:
- [].shift的作用是将arguments这个伪数组对象转成数组,等价于:Array.prototype.shift;因为arguments只能获取索引值和length,没有数组的一系列方法,因此加call是通过显示绑定让arguments变相有shift这个方法
- var Constructor = [].shift.call(arguments);这句其实得到的就是create这个函数,并将其赋给Constructor
- 但是还有一个问题是:需要判断return的值是不是对象!!!
如果函数没有return返回值 or return的是String/Number/null,那么new表达式中的函数调用会自动返回这个新对象;
如果函数return了一个Object类型,则this指向return返回的对象。
所以create()得到如下优化代码:
function create() {
var obj = new Object();
var Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var result = Constructor.apply(obj, arguments);
return typeof result == 'object' ? result : obj;
}
__proto__是访问器属性,通过它可以访问到对象的内部属性[[prototyoe]],
但是在使用时并不推荐使用__proto__,原因有两点:
- __proto__在ES6时才被标准化,存在浏览器兼容问题
- 通过改变一个对象的[[prototype]]去改变和继承对象的属性会造成严重的性能问题,所以应该尽量避免去改变一个对象的[[prototype]]属性
因此,如果要创建一个对象并且继承这个对象的[[prototype]],这里推荐使用Object.create(),因此代码优化如下:
function create() {
var Constructor = [].shift.call(arguments);
var obj = Object.create(Constructor.prototype);
var result = Constructor.apply(obj, arguments); return result instanceof Object ? result : obj;
}
5.箭头函数(不按照以上四种绑定规则,而是由外层(函数或者全局)作用域来决定this)
以上四种绑定规则实际上总结为:this总是指向调用该函数的对象!
箭头函数的this总结:
- 箭头函数不绑定this
- 其this寻值行为与普通变量相同,是在作用域中逐级寻找
- 无法通过bind、call、apply来直接修改(可以间接修改)
- 改变作用域中的this指向可以改变箭头函数的this
function foo() {
return (a) => {
console.log( this.a ); // this指向的是foo(),因为箭头函数在foo函数这个作用域中
};
} var obj1 = {
a: 2
}; var obj2 = {
a: 3
} var bar = foo.call( obj1 );
bar.call( obj2 ); // 2,不是3!这里实际是foo.call(obj1).(obj2)
foo的this已经绑定到了obj1上,foo()的内部创建了一个箭头函数会捕捉foo()的this,箭头函数的绑定无法被修改!!即使使用new也不会修改!
并且我们知道通过硬绑定后不能再次修改它的绑定,所以这里是把foo里的this指向obj1而不是obj2
几种绑定方式的优先级:
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
对this的理解与总结的更多相关文章
- 理解CSS视觉格式化
前面的话 CSS视觉格式化这个词可能比较陌生,但说起盒模型可能就恍然大悟了.实际上,盒模型只是CSS视觉格式化的一部分.视觉格式化分为块级和行内两种处理方式.理解视觉格式化,可以确定得到的效果是应 ...
- 彻底理解AC多模式匹配算法
(本文尤其适合遍览网上的讲解而仍百思不得姐的同学) 一.原理 AC自动机首先将模式组记录为Trie字典树的形式,以节点表示不同状态,边上标以字母表中的字符,表示状态的转移.根节点状态记为0状态,表示起 ...
- 理解加密算法(三)——创建CA机构,签发证书并开始TLS通信
接理解加密算法(一)--加密算法分类.理解加密算法(二)--TLS/SSL 1 不安全的TCP通信 普通的TCP通信数据是明文传输的,所以存在数据泄露和被篡改的风险,我们可以写一段测试代码试验一下. ...
- node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理
一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...
- 如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念
一.前言 DDD(领域驱动设计)的一些介绍网上资料很多,这里就不继续描述了.自己使用领域驱动设计摸滚打爬也有2年多的时间,出于对知识的总结和分享,也是对自我理解的一个公开检验,介于博客园这个平 ...
- 学习AOP之透过Spring的Ioc理解Advisor
花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...
- ThreadLocal简单理解
在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...
- JS核心系列:理解 new 的运行机制
和其他高级语言一样 javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过 ...
- 深入理解JS 执行细节
javascript从定义到执行,JS引擎在实现层做了很多初始化工作,因此在学习JS引擎工作机制之前,我们需要引入几个相关的概念:执行环境栈.全局对象.执行环境.变量对象.活动对象.作用域和作用域链等 ...
- 浅谈我对DDD领域驱动设计的理解
从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...
随机推荐
- NX二次开发-UFUN初始化UF_initialize
在调用UFUN函数时必须加Uf.h头文件,代码开头和结尾加UF_initialize和UF_terminate NX9+VS2012 #include <uf.h> #include &l ...
- docker实用参数
docker images:查看docker镜像docker ls:查看运行中的docker 镜像docker run -d -p 80:80 -v /home/xxxxx/nginx-conf/ht ...
- Apache Shiro RememberMe 1.2.4 反序列化漏洞
拉取镜像 docker pull medicean/vulapps:s_shiro_1 启动环境 docker run -d -p 80:8080 medicean/vulapps:s_shiro_1 ...
- Beanutils工具类,封装数据的三种方式,单例模式
org.apache.commons.beanutils.Beanutils; Beanutils setProperty(Object obj,String name,Object value) O ...
- 3.3_springBoot2.1.x检索之RestHighLevelClient方式
1.版本依赖 注意对 transport client不了解先阅读官方文档: transport client(传送门) 这里需要版本匹配,如失败查看官网或百度. pom.xml <?xml v ...
- Android笔记之从图库选择图片
Demo链接:https://pan.baidu.com/s/1T4T2pTEswmbcYYfpN3OwDw,提取码:pzqy 参考链接:[Android Example] Pick Image fr ...
- Shiro学习笔记1 —— Hello World
1.创建一个Maven工程加载Shiro的jar包 <!-- junit --> <dependency> <groupId>junit</groupId&g ...
- 2019-5-8-WPF-绑定命令在-MVVM-的-CanExecute-和-Execute-在按钮点击都没触发可能的原因...
title author date CreateTime categories WPF 绑定命令在 MVVM 的 CanExecute 和 Execute 在按钮点击都没触发可能的原因 lindexi ...
- jpa 踩坑 SQLGrammarException
SQLGrammarException could not execute query cause by not found column id ,, id指的是,返回的结果没有Id 封装结果集出错 ...
- 配置文件一mapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-/ ...