this是什么
this是一个指针,并且永远指向一个对象,对于非箭头函数来说,具体指向哪个对象是在运行时基于函数的执行环境动态绑定的。对于箭头函数来说,具体指向哪个对象是在箭头函数申明时的环境确定的。
this的使用场景--非箭头函数
this的常用使用场景无非就是下面几种
- 作为对象的方法调用
- 作为普通函数调用
- 作为构造函数调用
- 作为事件的回调函数调用
- Function.prototype.call和Function.prototype.apply调用
1 作为对象的方法调用
例子
<script type = "text/javascript">// 作为对象的方法调用var userName = "wenyaoyao"var obj = {userName:"longhanghang",getName:function(){return this.userName; } }console.log(obj.getName());//输出 longhanghang</script>复制代码
以上代码运行之后输出 longhanghang
结论
当函数作为对象方法被调用时,this指向该对象。
衍生
如果把对象里面的函数赋值给一个用var定义的全局变量,然后再在全局下面执行会产生什么样的火花呢?
<script type = "text/javascript">// 作为对象的方法调用var userName = "wenyaoyao"var obj = {userName:"longhanghang",getName:function(){return this.userName; } }var getNameFun = obj.getName;console.log(getNameFun());//输出 wenyaoyao</script>复制代码
以上代码运行之后输出 wenyaoyao
刨析:因为此时getNameFun是在全局作用域下面执行的,所以this指向window。
2 作为普通函数调用
例子
<script type="text/javascript"> // 作为普通函数调用 var userName = 'wenyaoyao'; function getName() {console.log(this.userName); } getName(); //输出 wenyaoyao</script>复制代码
以上代码运行之后输出 wenyaoyao
结论
当函数直接被调用时this指向全局对象,游览器里面是window,nodejs里面是global。
衍生
1 全局环境下使用var关键字定义的变量会直接挂靠到window上,如果是使用let和const呢?
<script type="text/javascript"> // 作为普通函数调用 let userName = 'wenyaoyao'; function getName() {console.log(this.userName); } getName(); //输出 undefined</script>复制代码
以上代码运行之后输出 undefined
刨析: 可以发现let和const定义的变量并不会挂靠在window上,然后此时this指向的是window,所以打印的时候输出undefined。
3 作为构造函数调用
例子
<script type="text/javascript"> // 作为构造函数调用 function Person() {this.userName = 'longhanghang';//return this; } let person1 = new Person(); console.log(person1.userName); //输出 longhanghang</script>复制代码
以上代码运行后输出longhanghang
结论
当使用new运算符调用函数时总是会返回一个对象,this指向这个对象。这也就是大名鼎鼎的构造函数。
衍生
1 函数内部怎么区分一个函数的调用类型呢?
<script type="text/javascript"> // 函数内判断调用类型 let obj = {}; function gudgeType() {if (this instanceof gudgeType) { console.log('构造函数调用'); } else { console.log('其他方式调用'); } } gudgeType(); new gudgeType(); // 输出其他方式调用 // 构造函数调用</script>复制代码
以上代码运行后依次输出 输出其他方式调用,构造函数调用
刨析: 我们可以巧妙的在函数内部使用instanceof对函数的调用方式进行判断。
4 作为事件的回调函数使用
例子
<!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>作为事件的回调函数使用</title> </head> <body> <div id = "test">点击我</div> </body> <script type="text/javascript">//作为事件的回调函数使用var testDom = document.querySelector("#test"); test.onclick = function(e){console.log(this) } </script></html>// 输出 <div id = "test">点击我</div>复制代码
以上代码运行后输出 <div id = "test">点击我</div>
结论
作为事件的回调函数调用时,this指向绑定事件的dom对象。
衍生
如果在事件的回调函数里面咱们再定义一个函数,函数内部的this又会指向哪里呢?
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>作为事件的回调函数使用</title> </head> <body> <div id = "test">点击我</div> </body> <script type="text/javascript"> //作为事件的回调函数使用 var testDom = document.querySelector("#test"); test.onclick = function(e){ var text = function(){ console.log(this); } text(); } </script></html>// 输出结果为window复制代码
以上代码运行后输出 window
刨析:此时的text是作为普通函数被调用的,所以指向windw。
5. Function.prototype.call和Function.prototype.apply调用
1 Function.prototype.call
<script type="text/javascript"> //Function.prototype.call() function getName(name, age) { console.log(this.name, name, age); } var obj = { name: 'longhanghang' }; getName.call(obj,'wenyaoyao',11); //输出为 longhanghang wenyaoyao 11</script>复制代码
1 Function.prototype.apply
<script type="text/javascript">//Function.prototype.apply()function getName(name, age) { console.log(this.name, name, age); }var obj = { name: 'longhanghang' }; getName.apply(obj,['wenyaoyao',11]);//输出为 longhanghang wenyaoyao 11</script>复制代码
以上代码最终输出都为longhanghang wenyaoyao 11
结论
执行一个函数我们通常都是采用的后面加()的方式,例如有名为 getName 这样的一个函数,我们通常会 使用getName()进行调用,这样调用的方式this的指向会遵循我们前面说的四种情况,但是如果我们不这样调用,而使用call和apply方法来调用时,那么this的指向取决于我们传的第一个参数,正如上面的示例,第一个参数我们传递的obj,那么函数内部的this指向的就是这个obj,后面的参数是函数执行时需要的参数,apply方法跟call方法作用差不多,只是后面的函数参数形式不同。
衍生
如果我们将函数作为对象的方法调用,然后再使用call或者apply时,会擦出怎么样的火花呢?
<script type="text/javascript"> //Function.prototype.apply() var obj1 = {name: 'longhanghang',getName: function () { console.log(this.name); }, }; var obj2 = { name: 'wenyaoyao' }; obj1.getName.call(obj2); //输出为 wenyaoyao</script>复制代码
以上代码输出为wenyaoyao
刨析:当我们使用了call或者apply执行函数时,优先级会高于作为对象的方法调用
this的使用场景--箭头函数
1 作为对象的方法调用
例子
<script type="text/javascript"> // 作为对象的方法调用 var userName = 'wenyaoyao'; var obj = {userName: 'longhanghang',getName: () => { return this.userName; }, }; console.log(obj.getName()); //输出 wenyaoyao</script>复制代码
以上代码运行之后输出 wenyaoyao。
结论
this指向被申明时的环境,因为getName被申明时实在全局环境下的,所以这里的this指向window。
衍生1
如果把对象里面的函数赋值给一个用var定义的全局变量,然后再在全局下面执行会产生什么样的火花呢?
<script type="text/javascript"> // 作为对象的方法调用 var userName = 'wenyaoyao'; var obj = {userName: 'longhanghang',getName: () => { return this.userName; }, }; var getNameFun = obj.getName; console.log(getNameFun()); //输出 wenyaoyao</script>复制代码
以上代码运行之后输出 wenyaoyao
刨析:当箭头函数被申明的时候,this指向的是window。
衍生2
如果箭头函数里面嵌套一个箭头函数呢?
<script type="text/javascript"> // 作为对象的方法调用 var userName = 'wenyaoyao'; var obj = {userName: 'longhanghang',getName: () => { var getFirstName = () => { return this.userName } return getFirstName; }, }; var getNameFun = obj.getName(); console.log(getNameFun()); //输出 wenyaoyao</script>复制代码
以上代码输出结果为wenyaoyao
刨析:当getFirstName申明的时候是在getName的这个作用域里面,this指向的就是getName指向的this,而getName在申明的时候this指向的是window,所以结论就是getName种的this也是指向window。
2 作为普通方法调用
例子
<script type = "text/javascript">let getName = () => {console.log(this) } getName(); </script>//输出是window复制代码
以上代码输出的结果是window
结论
this指向被申明时的环境,因为getName被申明时实在全局环境下的,所以这里的this指向window。
3 作为构造函数调用
例子
<script type = "text/javascript">var getName = () => {console.log(this) }var aa = new getName();//报错 Uncaught TypeError: getName is not a constructor</script>复制代码
以上代码会报错 Uncaught TypeError: getName is not a constructor
结论
箭头函数不能作为构造函数调用。
4 作为事件的回调函数调用
例子
<!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>作为事件的回调函数使用</title> </head> <body> <div id = "test">点击我</div> </body> <script type="text/javascript">//作为事件的回调函数使用var testDom = document.querySelector("#test"); test.onclick = () => {console.log(this) } </script> //输出为window</html>复制代码
以上代码输出为window
结论
this指向被申明时的环境,因为回调函数在申明时的环境是window,所以this指向window。
5. Function.prototype.call和Function.prototype.apply调用
例子
<script type = "text/javascript">var getName = () => {console.log(this) } getName.call(Object) getName.apply(Object)// 输出为window</script>复制代码
以上代码输出为 window
结论
Function.prototype.call和Function.prototype.apply无法改变箭头函数this的指向,this的指向还是在申明时就决定了。因为申明时的环境时window,所以this指向window而并不是指向构造函数Object。
箭头函数和非箭头函数的区别
- 箭头函数长得帅,非箭头函数长得漂亮
- 箭头函数都是匿名函数,非箭头函数可以是匿名函数也可以是具名函数
- 箭头函数不能使用new关键字,非箭头函数可以
- 箭头函数没有prototype属性,非箭头函数有
- 箭头函数不绑定arguments,非箭头函数要绑定
- 箭头函数无法使用call和apply修改的this的指向,非箭头函数可以
- 箭头函数的this在申明时决定,非箭头函数的this在执行时确定
自己实现call,apply,bind
前面用了这么多call和apply,那么咱们来点干货,自己手写一下这几个函数
1 call
<script type="text/javascript"> Function.prototype.selfCall = function () {var args = arguments; //获取到实际参数//类型判断if (Object.prototype.toString.call(this) !== '[object Function]') { throw new Error('必须使用函数调用该方法'); }var realThis = Array.prototype.shift.call(args) || window; //截取出第一个参数也就是自定义this指向的这个对象var funName = Symbol('funName'); //定义一个Symbol类型的属性名realThis[funName] = this; //将方法赋值给对象的funName属性var res = realThis[funName](...args); // 将此方法作为传递进来的对象的方法调用,此时this已经指向我们传递进来的这个对象delete realThis[funName]; //删除这个对象上的临时属性return res; }; function getName(aa) {console.log(this); } let obj = {}; getName.selfCall(obj, 11); //输出obj这个对象</script>复制代码
最终输出的是obj对象和11
2 apply
<script type="text/javascript"> Function.prototype.selfCall = function () {//类型判断if (Object.prototype.toString.call(this) !== '[object Function]') { throw new Error('必须使用函数调用该方法'); }var realThis = arguments[0] || window; //截取出第一个参数也就是自定义this指向的这个对象var args = arguments[1]; //取出剩下的参数作为函数运行的参数var funName = Symbol('funName'); //定义一个Symbol类型的属性名realThis[funName] = this; //将方法赋值给对象的funName属性var res = realThis[funName](...args); // 将此方法作为传递进来的对象的方法调用,此时this已经指向我们传递进来的这个对象delete realThis[funName]; //删除这个对象上的临时属性return res; }; function getName(aa, bb) {console.log(this, aa, bb); } let obj = {}; getName.selfCall(obj, [11, 22]); //输出obj这个对象和11</script>复制代码
bind
bind和call以及apply有一点区别就是函数调用bind后会返回一个新的this指向的函数,而使用call和apply是改成this指向并执行
<script type="text/javascript"> Function.prototype.selfCall = function () {//类型判断if (Object.prototype.toString.call(this) !== '[object Function]') { throw new Error('必须使用函数调用该方法'); }var args = arguments; //获取到参数var realThis = [].shift.call(args) || window; //截取出第一个参数也就是自定义this指向的这个对象var funName = Symbol('funName');//定义一个Symbol类型的属性名realThis[funName] = this;//将方法赋值给对象的funName属性//返回一个匿名函数,匿名函数中去执行我们要改变this指向的函数return function () { var diaoyongArgs = arguments;//获取到内层函数的参数 let allArgs = [...args, ...diaoyongArgs];//合并参数 let ret = realThis[funName](...allArgs);//将此方法作为传递进来的对象的方法调用,此时this已经指向我们传递进来的这个对象 delete realThis[funName];//方法执行完后,删除这个对象上的临时属性 return ret; }; }; function getName() {console.log(this, ...arguments); } let obj = {}; getName.bind(obj, 11, 22)(33); //输出 {} 11 22 33</script>复制代码
以上代码最终输出 {} 11 22 33