读书笔记-你不知道的JS上-this
关于this
与静态词法作用域不用,this的指向动态绑定,在函数执行期间才能确定。感觉有点像C++的多态?
- var a = 1;
- var obj = {
- a: 2,
- fn: function() {
- console.log(this.a);
- }
- };
- obj2 = {
- a: 3,
- fn: obj.fn
- };
- //通过对象调用 this指向obj
- obj.fn(); //
- //通过函数调用 this指向window
- setTimeout(obj.fn, 0); //
- obj2.fn(); //
这个例子很好理解,谁调用的函数,this就指向谁。
当一个函数被调用时,会创建一个活动记录(上下文)。这个记录会包含函数在哪里被调用,函数的调用方法,传入的参数等信息。this就是记录的其中一个信息。
开始详解this了!
理解this绑定,首先要理解调用位置:调用位置就是函数在代码中被调用的位置。
分析位置最重要的是分析调用栈,我们关心的调用位置就在当前正在执行的函数的前一个调用中。
- //调用栈是f1 即全局作用域
- function f1() {
- console.log('f1');
- f2(); //f2调用位置
- }
- //调用栈是f1 => f2
- function f2() {
- console.log('f2');
- f3(); //f3调用位置
- }
- //调用栈是f1 => f2 => f3
- function f3() {
- console.log('f3');
- }
- f1(); //f1调用位置
通过找到调用位置,判断需要应用哪一条规则。this绑定的四条规则:
默认绑定
最常用的函数调用类型:独立函数调用。
- var a = 1;
- // 全局函数
- function fn() {
- // 'use strict' error
- console.log(this.a); //
- }
- //默认this绑定到window
- fn();
在这里,fn是直接进行调用,没有任何修饰,相当于window.fn(),只能适用默认绑定,指向了window。
隐式绑定
这种绑定需要考虑调用位置是否有上下文对象。
- function fn() {
- console.log(this.a);
- }
- var obj = {
- a: 2,
- fn: fn
- };
- obj.fn(); //
当fn被调用时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用fn时this被绑定到obj,因此this.a和obj.a是一样的。
另外,链式调用时,只有最近的调用对象会影响this。
- var a = 1;
- var obj = {
- a: 2,
- fn: function() {
- console.log(this.a);
- }
- };
- obj2 = {
- a: 3,
- fn: obj
- };
- //this.a相当于obj.a
- obj2.fn.fn(); //
隐式丢失
一个最常见的this绑定问题是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或undefined上。
- function fn() {
- console.log(this.a);
- }
- var a = 1;
- var obj = {
- a: 2,
- fn: fn
- }
- var f = obj.fn;
- obj.fn(); //
- f(); //
被作为函数调用时,会被默认绑定到window对象。
另外一种情况是传入回调函数时:
- function fn() {
- console.log(this.a);
- }
- function fn2(fn) {
- fn();
- }
- var a = 1;
- var obj = {
- a: 2,
- fn: fn
- }
- //还是作为函数调用
- fn2(obj.fn);
显式绑定
就是用apply和call方法强制绑定this。一个例子说明一切:
- var a = 1;
- var obj = {
- a: 2,
- fn: function() {
- console.log(this.a);
- }
- };
- obj2 = {
- a: 3,
- fn: obj.fn
- };
- obj.fn.call(this); //
- obj.fn.call(obj); //
- obj.fn.call(obj2); //
硬绑定
直接用vue源码来举例子吧。
- function bind(fn, ctx) {
- return function(a) {
- var len = arguments.length;
- return len ? len > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx);
- }
- }
简单暴力,都会背了。
new绑定
JS中new的机制实际上和面向类的语言完全不同。
在JS中,构造函数只是一些使用new操作符时被调用的函数,它们不属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。
Number作为构造函数时,ES5.1中这样描述:当Number在new表达式中被调用时,它是一个构造函数,会初始化新创建的对象。
因为,包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。实际上不存在所谓的构造函数,只有对于函数的构造调用。
使用new来调用函数,会自动执行下面的操作:
1、创建一个全新的对象。
2、这个新对象会被执行【原型】连接。
3、这个新对象会绑定到函数调用的this。
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
- function fn(a, b) {
- this.a = a;
- this.b = b;
- }
- var f = new fn();
- console.log(f); //{a:undefined,b:undefined}
优先级
如果某个调用位置应用多条规则,需要为这些规则设定优先级。(默认绑定、隐式绑定、显示绑定、new绑定)
首先默认绑定优先级最低。
隐式 VS 显示?
- function fn() {
- console.log(this.a);
- }
- var obj1 = {
- a: 2,
- fn: fn
- };
- var obj2 = {
- a: 3,
- fn: fn
- }
- obj1.fn(); //
- obj2.fn(); //
- obj1.fn.call(obj2); //
- obj2.fn.call(obj1); //
显式比较厉害。
再来比较一下new和显示绑定优先级:
- function fn(a) {
- this.a = a;
- }
- var obj = {};
- //this强制绑定到obj对象上
- var f1 = fn.bind(obj);
- f1(2);
- //现在obj有了一个a属性
- console.log(obj.a); //
- //new一个对象将this.a改成3
- var f2 = new f1(3);
- //这里依然没有变
- console.log(obj.a); //
- //将this指向了new出来的a
- console.log(f2.a); //
可以看出,通过new的绑定,覆盖了bind强制绑定的a,所以new绑定是优先于显示绑定的。
顺便贴一下MDN提供的bind()实现:
- if (!Function.prototype.bind) {
- Function.prototype.bind = function(oThis) {
- if (typeof this !== 'function') {
- throw new TypeError('Function.prototype.bind - what is trying ' +
- 'to be bound is not callable');
- }
- var aArgs = Array.prototype.slice.call(arguments, 1),
- fToBind = this,
- fNOP = function() {},
- fBound = function() {
- return fToBind.apply(
- this instanceof fNoP && oThis ? this : oThis,
- aArgs.concat(Array.prototype.slice.call(arguments))
- );
- };
- fNOP.prototype = this.prototype;
- fBound.prototype = new fNOP();
- return fBound;
- };
- }
总结
优先级为:
1、如果使用了new,this绑定新创建的对象。
2、通过call、apply显式绑定。
3、是否使用隐式绑定?即对象调用
4、其余情况默认绑定在window上
例外情况
1、如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用默认绑定规则(window)。
如果用apply展开数组可能用得上,比如fn.apply(null,arr),其中arr为数组,里面的值作为参数传给fn。
但是这个参数十分不安全,可能会不经意的修改全局变量,可以通过纯空对象来创建一个安全的this。
- var a = 1,
- b = 1;
- console.log(a); //
- function fn(a) {
- this.a = a;
- }
- function fn2(b) {
- this.b = b;
- }
- //原本可能只想利用apply
- fn.apply(null, [2]);
- //不小心改了全局变量!
- console.log(a);
- //创建一个纯空对象
- var obj = Object.create(null);
- console.log(b); //
- //安全的this
- fn2.call(obj, 3);
- // 现在安全了!
- console.log(b); //
但是引入了箭头函数,问题就有点奇怪了。
- var a = 1;
- var obj = {
- a: 2,
- fn: () => {
- console.log(this.a);
- }
- };
- obj2 = {
- a: 3,
- fn: obj.fn
- };
- obj.fn(); //1
- setTimeout(obj.fn, 0); //1
- obj2.fn(); //1
全是1,这是怎么回事?换arguments的例子来看:
- function fn() {
- setTimeout(function() {
- console.log(arguments);
- }, 0)
- }
- fn(1, 2); //[] 确实未传入参数
- function fn2() {
- setTimeout(() => {
- console.log(arguments);
- }, 0)
- }
- fn2(1, 2); //[1,2] 把外部的参数吸进来了!
简单来讲,箭头函数使得this查询回到了词法作用域规则,它本身不具有this、arguments等属性,根据外层作用域来决定this。
- function fn() {
- return (a) => console.log(this.a);
- }
- var obj = {
- a: 1
- };
- var obj2 = {
- a: 2
- }
- var f = fn.call(obj);
- f(); //
- f.call(obj2); //
当箭头函数外部的fn被绑定到obj时,无论怎么调用和绑定,this指向永远是上一层作用域(window),所以会输出1。
真·总结
如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面4条规则来判断this的绑定对象。
1、由new调用?绑定到新创建的对象。
2、由call、apply或bind调用?绑定到指定对象。
3、由上下文调用?绑定到那个上下文调用。
4、默认:严格模式绑定到undefined,否则绑定到全局对象。
读书笔记-你不知道的JS上-this的更多相关文章
- 读书笔记-你不知道的JS上-对象
好想要对象··· 函数的调用位置不同会造成this绑定对象不同.但是对象到底是什么,为什么要绑定他们呢?(可以可以,我也不太懂) 语法 对象声明有两个形式: 1.字面量 => var obj = ...
- 读书笔记-你不知道的JS上-函数作用域与块作用域
函数作用域 Javascript具有基于函数的作用域,每声明一个函数,都会产生一个对应的作用域. //全局作用域包含f1 function f1(a) { var b = 1; //f1作用域包含a, ...
- 读书笔记-你不知道的JS上-词法作用域
JS引擎 编译与执行 Javascript引擎会在词法分析和代码生成阶段对运行性能进行优化,包含对冗余元素进行优化(例如对语句在不影响结果的情况下进行重新组合). 对于Javascript来说,大部分 ...
- 读书笔记-你不知道的JS上-混入与原型
继承 mixin混合继承 function mixin(obj1, obj2) { for (var key in obj2) { //重复不复制 if (!(key in obj1)) { obj1 ...
- 读书笔记-你不知道的JS上-闭包与模块
闭包定义 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行. 看一段最简单的闭包代码: function foo() { var a = 2; //闭包 fun ...
- 读书笔记-你不知道的JS上-声明提升
变量声明提升 Javascript代码一般情况下是由上往下执行的,但是有些情况下不成立. a = 2; //变量声明被提升在当前作用域顶部 var a; console.log(a); console ...
- 读书笔记-你不知道的JS中-promise
之前的笔记没保存没掉了,好气,重新写! 填坑-- 现在与将来 在单个JS文件中,程序由许多块组成,这些块有的现在执行,有的将来执行,最常见的块单位是函数. 程序中'将来'执行的部分并不一定在'现在'运 ...
- 读书笔记-你不知道的JavaScript(上)
本文首发在我的个人博客:http://muyunyun.cn/ <你不知道的JavaScript>系列丛书给出了很多颠覆以往对JavaScript认知的点, 读完上卷,受益匪浅,于是对其精 ...
- 读书笔记-你不知道的JS中-函数生成器
这个坑比较深 可能写完我也看不懂(逃 ES6提供了一个新的函数特性,名字叫Generator,一开始看到,第一反应是函数指针?然而并不是,只是一个新的语法. 入门 简单来说,用法如下: functio ...
随机推荐
- JavaScript基础回顾
1, NaN 不是数字 Infinity 无穷大 alert(parseInt("dd")); //NaN alert(1/0); //Infinity 2, 逻辑或 || ,返 ...
- iOS开发-AFNetworking参数和多文件同时上传【多文件上传】
1. 前言 在项目开发中,我们经常需要上传文件,例如:上传图片,上传各种文件,而有时也需要将参数和多个文件一起上传,不知道大家的项目中遇到了没有,我在最近的项目中,就需要这样的一个功能:同时上传参数. ...
- angular $observe() 和$watch的区别
1.$observe()是属性attributes的方法,只能在DOM属性的值发生变化时用,并且只用于directive内. 当需要监听一个包含变量的属性值时attr1="Name:{{na ...
- 【京东详情页】——原生js爬坑之放大镜
一.引言 在商城的详情页中,放大镜的功能是很常见的.这里京东详情页就要做一个仿放大镜的效果,预览如下: 二.实现原理 实际上,放大镜的实现是单纯用几个div,鼠标移入其中一个小图div,触发事件显示另 ...
- JS之脚本延迟
自从开了博客,我就一下班回来匆匆吃完饭门一关等一开电脑一打开匆匆的研究东西,以至于朋友们都怀疑我是不是都得了自闭症 其实因为我有恐惧心理怕自己的技术哪天跟不上社会了,说到技术我觉得技术不求越新越好,但 ...
- TCP/IP(二)物理层详解
前言 在前面说了一下,计算机网络的大概内容,没有去深刻的去了解它,这篇文章给大家分享一下物理层! 我们知道ISO模型是七层,TCP/IP模型是五层,而tcp/ip协议只将七层概括为4层,我们将学习其中 ...
- EOutOfResources字符异常
近日,用Delphi编程时,遇到一个莫名其妙的异常:EOutOfResources,这是一个可以重复再现的异常.开始以为是程序中创建的对象太多,导致占用了过多的资源,引起了这个异常.于是在代码中将许多 ...
- SpringBoot文档翻译系列——26.日志logging
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/7613854.html 这是SpringBoot的日志内容 26 日志 Spring使用Co ...
- Twitter的分布式系统中ID生成方法——Snowflake
Twitter-Snowflake算法产生的背景相当简单,为了满足Twitter每秒上万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统 ...
- Just Finish it up UVA - 11093
Just Finish it up Time Limit: 3000MS Memory Limit: Unknown 64bit IO Format: %lld & %llu [Sub ...