作者 | Jeskson
掘金 | https://juejin.im/user/5a16e1f3f265da43128096cb
2020 年 01 月 10 日
前言, 为什么要学习在掌握 JavaScript 中的 this,call,apply, 因为面试官会问啊! 所以我们 必须掌握才能答啊! 那么 this 是什么, Function.prototype.call 和
Function.prototype.apply 这两个方法又是如何使用在 JavaScript 中的呢.
学习掌握 this 是必须的, 我们常常在编写 JavaScript 中的代码时, 会常用到它.
this 的指针作用域, 在全局环境中执行 this, 表示 Global 对象, 在浏览器中表示 Windows 对象; 当通过 new 运算符来调用函数时, 函数被当做为一个构造函数, this 的指向构造函数创建出来的对象; 当在函数的执行环境中使用 this 时, 情况有些不同, 如函数没有作为一个非 Windows 对象的属性, 那么只是定义了在这个函数, 不管这个函数是不是定义在另一个函数中, 其函数中的 this 仍表示为 Windows 对象; 如果函数表示作为一个非 Windows 对象的属性, 则表示函数中的 this 就代表为 这个对象.
如截图情况下, 在全局执行环境中使用 this, 表示 Global 对象, 在浏览器中表示为 Windows 对象.
functionA(){// 在 A 函数中定义一个 B 函数 functionB(){console.log(this);//Windowconsole.log(typeofthis);//objectconsole.log(this===Windows);//true}// 在 A 函数内部调用 B 函数 B();}// 调用 A 函数 A();
在函数执行环境中使用 this 时, 如果函数没有明显的作为非 Windows 对象的属性, 而只是定义了函数, 不管这个函数是不是定义在另一个函数中, 这个函数中的 this 仍然表示 Windows 对象.
// 定义一个对象 obj, 添加属性 name, 添加方法 objFunvarobj = {name:'dada',objFun:function(){console.log(this);// Object {name: "dada"}console.log(typeofthis);//objectconsole.log(this===Windows);//falseconsole.log(this.name);//dada}};// 调用 obj 对象的方法 obj.objFun();//this 绑定到当前对象, 也就是 obj 对象
// 定义一个对象 obj, 添加属性 name, 添加方法 objFunvarobj = {name:'dada',objFun:function(){console.log(this);//windowconsole.log(typeofthis);//objectconsole.log(this===Windows);//trueconsole.log('dada 的, 名字'+this.name+'大帅哥'); }};vartest = obj.objFun;test();
可以看出函数内部中 this 值不是静态的, 是动态的, 可以改变的, 每次调用一个函数时, 它总是在重新求值. 函数内部中的 this 值, 实际上是由函数被调用的父作用域提供, 依赖实际函数的语法.
- // 第一种情况 // 调用 obj 对象的方法 obj.objFun();//this 绑定到当前对象, 也就是 obj 对象 // 第二种情况 vartest = obj.objFun;test();
- vartest = obj.objFun// 这里没有调用函数 test()// 这里调用了函数 // test 不是一个对象的引用, 所以 this 值代表全局对象.
当通过 new 运算符来调用函数时, 函数被当做一个构造函数, this 指向构造函数创建出来的对象.
new 创建出来了一个构造函数, 这个时候 this 的值, 指向构造函数创建出来的对象.
varname ='dada';functionA(){console.log(this.name);} A();// dadavarB =newA();//undefined (因为 B 并没有 name 属性)VM162:3dadaVM162:3undefinedundefined
varname ='windowdada';varobj = {name:'objdada',objB: {name:'objBdada',bFun:function(){console.log(this.name); } }};vartest = obj.objB.bFun();vartest2 = obj.objB.bFun;test2();vartest3 = obj.objB;vartest4 = test3.bFun;test4();
注意 () 的近邻的左边, 如果这个的左边是一个引用, 那么传递给调用函数的 this 值为引用所属的这个对象, 否则 this 指向为全局对象.
- vartest = obj.objB.bFun();// ()左边是 bFun 引用, 它指向 objB 这个对象, 所有打印出 objBdada
- vartest2 = obj.objB.bFun;test2();// ()的左边为 test2, 它不是某个对象的引用, 所以是全局对象 // 打印出 objBdada
- vartest4 = test3.bFun;test4();// 同理这个也是
JavaScript 中 this 的原理
varname ='windowDada';varobj = {name:'dada',foo:function(){console.log(this.name); }};varfoo = obj.foo;// 写法一 obj.foo()// 写法二 foo()VM593:5dadaVM593:5windowDada
这个时候我相信你已经看懂了. this 指向的是函数运行时所在的环境, 对于 obj.foo()来说, foo 运行在 obj 环境中, 所以这个时候的 this 指向为 obj 这个对象, 对于 foo()来说, foo 运行是全局环境, 这个 this 的指向为全局环境.(你会问为什么呢? 一个指向 obj 这个对象, 一个运行环境为全局环境, 这里可以运用 () 左边方法)
对呀为什么呢? 函数的运行环境是怎么决定在哪种情况的?
为什么 obj.foo()的环境就在 obj 这个环境中, 而作为
var foo = obj.foo,foo()的运行环境就变成了全局的执行环境呢?
this 的指向设计, 跟内存的数据结构有关.
varobj = {name:'dada'};
当一个对象赋值给一个变量 obj 的时候, JavaScript 引擎会在内存里, 先生成一个对象为 { name: 'dada' }, 然后才把这个对象的内存地址赋值给这个变量 obj.
我们说过了很多很多遍了, 都知道这个变量 obj 就是一个地址, 这个时候如果要读 obj.foo, 那么引擎就会从这个变量 obj 中拿内存地址, 然后再从这个地址 读取原始对象, 返回它的 foo 属性.
注意: 原始的对象 (开始创建的对象 { name: 'dada' }) 以字典结构保存的, 每个属性名都对应一个属性描述对象. foo 属性的值保存在属性描述对象的 value 属性里面.
this 指包含它的函数作为方法被调用时所属的对象.
this, 第一包, 含它的函数, 第二, 作为方法被调用时, 第三, 所属的对象.
functionda(){console.log(this);//Windows}da();
会调用我, 我就是谁的, 谁最后调用我, 我就是谁的.
testFunda()函数是在全局中被 Windows 对象所调用的, this 的指向为 Windows 对象, 而 nameda 变量在 testFunda 函数中, Windows 对象中没有这个变量, 所以打印不出来.
注意 () 的左边为 testFunda
而 testFunda()函数是在全局中被 Windows 对象所调用的哦!
因此 this 的指向就是 Windows 对象哦!
varnamedada ='dada'functiontestFundada(){varnamedada="hello dada!";console.log(this.namedada);}testFundada();VM717:4dada
看这个代码当然打印出的是 dada 啦, 因为从全局调用, 全局中有这个属性, 就打印这个属性.
this 被包含中一个函数中, 但是这个函数被另个函数包含的情况下, 这个 this 的指向为顶部的函数.
varobj={a:"da",b:function(){vara="dada";console.log(this.a); }};obj.b();VM726:5da
this 被包含在函数 b()中, 因为是被 obj 对象所调用的, 所以这个 this 属于这个 obj 对象, 打印出来的就是 da 这个字符串了.
谁最后调用我, 我就属于谁!
varobj = {a:1,b:{fn:function(){console.log(this.a);//undefined} }};obj.b.fn();VM730:5undefined
对象 obj 是在 Windows 上定义的, 所以如下显示:
obj.b.fn()=Windows.obj.b.fn()
谁先调用我不算, 谁最后调用我才算, Windows, 那么 this 不是指向全局的对象了吗, 但是最后的是被 fn()调用,()左边为 b 对象, 所以 this 就指向这个 b 对象了, 因为函数中没有这个变量, 所以为 undefined.
出一道考题
结果是啥? 我知道为 2, 你知道吗? 那看看执行结果吧!
varobj = {name:1,b:{name:2,fn:function(){varname =3console.log(this.name); } }};obj.b.fn();
函数情况, 属性的值为一个函数
varobj = {foo:function(){} };
在 JavaScript 引擎中会将函数单独保存在内存中, 再将函数的地址赋值给 foo 属性的 value 属性.
{foo: { [[value]]: 函数的地址 ... }}
varf =function(){};varobj = {f: f };// 单独执行 f()// obj 环境执行 obj.f()
varfda =function(){console.log('da');};varobjDada = {f: fda };// 单独执行 fda()// objDada 环境执行 objDada.fda()VM858:2da
环境的考虑, 在 JavaScript 中运行在函数体内部, 引用当前环境的其他变量. 在 JavaScript 中, 由于函数可以在不同的运行环境执行, 就要一种机制, 使能够在函数体内部获取当前的运行环境.
this 的出现, 目的在于就是指代函数当前的运行环境.
this 指代全局对象
functiontest(){this.x =1; alert(this.x);}test();// 1
this 指代上级对象
functiontest(){ alert(this.x);}varo = {};o.x =1;o.m = test;o.m();// 1
this 指代 new 出的对象
varx =3;functiontest(){this.x =1;}varo =newtest();alert(x);// 3alert(o.x);// 1
函数的不同使用场合, this 有不同的值, this 是函数运行时所在的环境对象.
call 的用法
call(thisObj,arg1,arg2,arg...)
调用一个对象的方法, 以另一个对象替换当前对象, call 方法用来代替另一个对象调用一个方法, 该方法可以将一个函数对象的上下文改变为由 this obj 指定的新对象.
call 方法的参数, 如果是不传, 或是 null,undefined 的情况下, 函数中的 this 指向就是指 Windows 对象, 如果传递的是另一个函数的函数名, 函数中的 this 指向就是这个函数的引用, 如果参数传递的是基本类型数据时, 函数中的 this 指向就是其对应的 包装对象了, 如果参数传递的是一个对象时, 函数中的 this 就指向这个对象.
一个函数的函数名, 函数名是引用, 所以指向是指这个函数的引用
一个对象, 所以 this 就指向这个对象
基本类型数据, 就指向这个包装对象
Function.prototype.call(thisArg[,arg1[,arg2, ...]])
当以 thisArg 和可选择的 arg1,arg2 等等作为参数在一个 func 对象上调用 call 方法.
varda = {name:"da",sayHello:function(age){console.log("hello, i am",this.name +""+ age +" years old"); }};varjeskson = {name:"jeskson",};da.sayHello(12);VM891:4hello, i am da12years oldundefinedda.sayHello.call(jeskson,13);VM891:4hello, i am jeskson13years oldundefined
在 JavaScript 中, call 和 apply 作用是一样的
为了改变某个函数运行时的上下文 (context) 而存在的, 就是为了改变函数体内部 this 的指向.
每个函数都包含两个非继承而来的方法:
call()和 apply()
apply 的用法
apply(thisObj,argArray)
apply 方法应用于某一个对象的一个方法
用另一个对象替换当前对象.
区别: 参数书写方式不同
call() 方法分别接受参数.
apply() 方法接受数组形式的参数.
Math.max(1,2,3);// 会返回 3Math.max.apply(null, [1,2,3]);// 也会返回 3Math.max.apply(Math, [1,2,3]);// 也会返回 3Math.max.apply(" ", [1,2,3]);// 也会返回 3Math.max.apply(0, [1,2,3]);// 也会返回 3
call(thisObj, arg1, arg2, arg3, arg4);apply(thisObj, [args]);thisObj:call 和 apply 第一个参数是一样的该参数将替代 Function 类里面的 this 对象 arg1,arg2.... 是一个个的参数 args 一个数组或类数组, 是一个参数列表
JavaScript 严格模式
如果 apply() 方法的第一个参数不是对象, 它将成为被调用函数的所有者(对象).
在 "非严格" 模式下, 它成为全局对象.
参考:
http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
最后
欢迎加我微信(xiaoda0423), 拉你进技术群, 长期交流学习...
来源: http://www.jianshu.com/p/4db0492e56c8