this问题几乎是老生常谈的问题了 ,其实真正问起来自己也是很模糊,今天趁着女神节的日子彻底弄懂这个问题,永绝后患。
在一个函数对象被调用的时候,会创建一个活动对象,首先将该函数的每个形参和实参,都添加为该活动对象的属性和值;将该函数体内显示声明的变量和函数,也添加为该活动的的属性(在刚进入该函数执行环境时,未赋值,所以值为undefined,这个是JS的提前声明机制)。
-
this四种场景逐一击破
打仗之前我先来两个锦囊
- 函数被调用时(运行时)才会确定该函数内的this指向。因为在函数中this与arguments是两个特殊的变量,只有在函数被调用时才能获取到它们,而且搜索这两个变量时之后会在活动对象范围里面去搜
- 要确定函数中this的指向,必须先找到该函数被调用的位置。
-
第一种:作为对象的方法调用时, this 指向该对象(javascript:void(0);)
var obj ={ a:1, getA:function(){ console.log(this===obj) // true console.log(this.a) // 1 } }; obj.getA(); 复制代码
var a =1 function test() { console.log(this.a) } var obj ={ a:2, test } obj.test() //2 复制代码
var a = 1 function test() { console.log(this.a) } var obj = { a: 2, test } var obj0 = { a: 3, obj } obj0.obj.test() // 2 复制代码
即使是这种串串烧的形式,结果也是一样的,
test()
中的this只对直属上司(直接调用者obj)负责。再来看一个综合点的例子:var a = 1 function test() { console.log(this.a) } var obj = { a: 2, test } var testCopy = obj.test testCopy() // 1 复制代码
你可能觉得换了名字其实还是obj.test(),但是却不是这样的 ,this永远指向的是最后调用它的对象
-
第二种:作为普通函数调用,this 总是指向全局对象 window
console.log(this) // window window.name = "globalName" var getName =function(){ return this.name } console.log(getName()); // globalName 复制代码
-
第三种:构造器调用,当用new运算符调用函数是,该函数总是会返回一个对象,通常情况下,构造函数里的this就指向返回的这个对象
函数被作为构造器调用时有以下特点:
一个新的对象被创建出来;这个新的对象被传递给这个构造器作为this参数,也就是说这个新的对象是构造器函数的上下文;如果没有显性的return语句,这个新的对象会被隐式的return(就是悄咪咪的被return了),并成为这个构造器的值
-
var a = 1 function test(a) { this.a =a } var b = new test(2) console.log(b.a) 复制代码
其中的new运算,先产生一个空对象,然后生成一个this指针,这个指针指向这个对象 ;运行构造函数时,这个对象传递导函数中作为this存在 ,就相当于“{}.a=a” 为这个对象添加了属性 ,然后再付给b
-
var MyClass = function () { this.name = "class"; return { name: "other" } } var obj = new MyClass(); console.log(obj.name); // other 复制代码
上边咱们说如果没有显性的return语句,这个新的对象会被隐式的return,但是如果 构造器显式地返回了一个 object 类型的对象,那么此次运算结果最终会返回这个对象,而不是我么之前期待的 this
-
-
第四种:Function.prototype.call 或 Function.prototype.apply和Function.prototype.bind调用, 可以动态地改变出入函数的 this
看到上边几种形式的,你可能回想我很讨厌这些乱七八遭的东西 这个调用那个调用的,我想自己指定,我的人生我做主 ,我的代码我做主 。当然可以没问题!!!
-
call和apply的作用一样,区别仅在于传入的参数形式不同而已。
- apply接收两个参数,第一个参数指定了函数体内this对象的指向,第二个参数是一个带下标的集合,这个集合可以是数组,也可以是类数组,apply方法把这个集合中的元素作为参数传入被调用的函数。
- call传入的参数不固定,跟apply相同的是,第一个参数也代表函数体内的this指向,从第二个参数开始往后,每个参数依次传入函数
var test =function(a,b,c){ console.log([a,b,c]) } //传入的第一个参数为 null ,函数体内的 this 会指向默认的宿主对象,在浏览器中则是 window test.apply(null,[1,2,3]); // [1,2,3] test.call(null, 1, 2, 3) // [1,2,3] 复制代码
-
call和apply改变this指向
var obj1 = {name:"obj1"}; var obj2 = {name:"obj2"}; window.name = "window" var getName = function () { console.log(this.name) } getName(); // window getName.call(obj1); //obj1 getName.call(obj2) // obj2 复制代码
-
apply 和call的常用方法
-
验证是否是数组(前提是toString()没有被重写)
function isArray(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; } 复制代码
-
让类数组具有数组的方法 ,比如arguments对象,获取到的文档节点等,并没有数组的那些方法
[].slice.apply(arguments); //或者 Array.prototype.slice.apply(argument); 复制代码
-
-
bind方法是事先把fn的this改变为我们要想要的结果,并且把对应的参数值准备好,以后要用到了,直接的执行即可,也就是说bind同样可以改变this的指向,但和apply、call不同就是不会马上的执行。
-
foo.bind({a:1})
却并不如此,执行该条语句仅仅得到了一个新的函数,新函数的this被绑定到了后面的第一个参数,亦即新的函数并没有执行。function foo(){ return this; } var f1=foo.call({a:1}); var f2=foo.apply({a:2}); var f3=foo.bind({a:1}); console.log(f1); //{a:1} console.log(f2); //{a:2} console.log(f3); //function foo(){ // return this; //} console.log(foo()); //window对象 console.log(f3()); //{a: 1} 复制代码
-
bind与apply和call最大的区别就是bind不会立即调用,其他 两个会立即调用
var tempFn = fn.bind(obj, 1, 2); tempFn(); 复制代码
第一行代码只是改变了fn中的this为obj,并且给fn传递了两个参数值1、2,但是此时并没有把fn这个函数给执行,执行bind会有一个返回值,这个返回值tempFn就是把fn的this改变后的那个结果。
-
-
One more
还有一种大家别忘了Es6箭头函数,猜猜下边的 结果是几
var a =1 var test = () => { console.log(this.a) } var obj = { a: 2, test } obj.test() // 1 复制代码
来,往上翻一下我们的第一个锦囊,“函数被调用时(即运行时)才会确定该函数内this的指向。”现在函数这两个字要加个词修饰一下,变成普通函数(非箭头函数)才能区别于箭头函数。箭头函数中的this在函数定义的时候就已经确定,它this指向的是它的外层作用域this的指向。