JavaScript 中的 this 是什么
- 关于 this 还是得先从执行上下文说起:
- 从图中可以看出,this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this;
this 的设计缺陷
嵌套函数中的 this 不会从外层函数中继承
-
结合下面代码分析:bar 函数中的 this 是什么?
var myObj = { name : "张三", showThis: function(){ console.log(this) function bar(){console.log(this)} // 输出什么??? bar() } } myObj.showThis()
-
函数 bar 中的 this 指向的是全局 window 对象,而函数 showThis 中的 this 指向的是 myObj 对象;解决这个问题有两种思路:
- 第一种是把 this 保存为一个 self 变量,再利用变量的作用域机制传递给嵌套函数;
- 第二种是继续使用 this,但是要把嵌套函数改为箭头函数,因为箭头函数没有自己的执行上下文,所以它会继承调用函数中的 this;
-
this 没有作用域的限制,这点和变量不一样,所以嵌套函数不会从调用它的函数中继承 this,这样会造成很多不符合直觉的代码;
普通函数中的 this 默认指向全局对象 window
-
在默认情况下调用一个函数,其执行上下文中的 this 是默认指向全局对象 window 的;
-
这个设计也是一种缺陷,因为在实际工作中,可能并不希望函数执行上下文中的 this 默认指向全局对象,因为这样会打破数据的边界,造成一些误操作;解决这个问题可以:
- 可以通过设置 JavaScript 的 “严格模式” 来解决 (在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined);
- 可以通过 call/apply/bind 手动指定 this;
this 的五种情况
第一种:事件绑定
-
给元素的某个事件行为绑定方法,事件触发,方法执行,此时方法中的 this 一般都是当前元素本身;
-
DOM0 级事件
// DOM0 btn.onclick = function anonymous() { console.log(this); // 元素 };
-
DOM2 级事件
// => DOM2:不兼容 IE6 7 8 btn.addEventListener('click', function anonymous() { console.log(this); // 元素 }, false); btn.attachEvent('onclick', function anonymous() { // IE8 浏览器中的 DOM2 事件绑定 console.log(this); // window });
第二种:上下文对象调用
-
普通函数执行,它里面的 this 取决于方法执行前面是否有 “点”,有 “点” 前面是谁 this 就是谁,没有则指向 window (严格模式下是 undefinde);
-
示例代码:
function fn() { console.log(this); } let obj = { name: 'OBJ', fn: fn }; fn(); // window obj.fn(); // { name: 'OBJ', fn: [Function: fn] }
第三种:构造函数执行
-
构造函数执行
new xxx
,函数中的 this 是当前类的实例 -
示例代码:
function Fn() { console.log(this); // this.xxx = xxx 是给当前实例设置私有属性 } let f = new Fn;
第四种:箭头函数
-
箭头函数中没有自身的 this ,箭头函数的 this 指向始终为外层的作用域;
-
箭头函数没有的东西很多:
- 没有 prototype (也就是没有构造器),所以不能被 new 执行;
- 没有 arguments 实参集合 (可以基于 …args 剩余运算符获取);
-
示例代码:
JavaScriptJavaScriptJavaScriptlet obj = { name: 'OBJ', fn: function () { // console.log(this); //=>obj let _this = this; return function () { // console.log(this); //=>window _this.name = "旺旺"; }; } }; let ff = obj.fn(); ff(); console.log(obj.name); // 旺旺
let obj = { name: 'OBJ', fn: function () { // console.log(this); //=>obj return () => { // console.log(this); //=>obj this.name = "旺旺"; }; } }; let ff = obj.fn(); ff(); console.log(obj.name); // 旺旺
let obj = { name: 'OBJ', fn: function () { setTimeout(_ => { // console.log(this); //=>obj this.name = "旺旺"; }, 10); } }; obj.fn(); console.log(obj.name); // OBJ
第五种:API 改变 this 指向
-
基于 call/apply/bind 可以改变函数中 this 的指向(强行改变),call、apply、bind 是 Function.prototype 上的方法;
-
func.call(context, 10, 20):context 为改变的 this 指向(非严格模式下,传递 null/undefined 指向的也是 window);
-
示例代码:
JavaScriptJavaScriptJavaScript// call Function.prototype.call = function call(context = window, ...args) { context === null ? context = window : null; //=> 此处 this 为 apply 前的函数,也就是调用 apply 的函数 //=> 把函数赋值给需要指定 this 的对象 context context.$fn = this; let result = context.$fn(...args); // 这时函数执行 this 就变成了 context delete context.$fn; return result; }
// apply Function.prototype.apply = function apply(context = window, args) { context === null ? context = window : null; //=> 此处 this 为 apply 前的函数,也就是调用 apply 的函数 //=> 把函数赋值给需要指定 this 的对象 context context.$fn = this; let result = context.$fn(...args); // 这时函数执行 this 就变成了 context delete context.$fn; return result; }
// bind // es5 版本 Function.prototype.bind = function bind(context) { context = context || window; // 获取传递的实参集合 var args = [].slice.call(arguments, 1); var _this = this; // 需要最终执行的函数 return function anonymous() { // 此函数的参数 var amArg = [].slice.call(arguments, 0); _this.apply(context, args.concat(amArg)); }; } // es6 版本 Function.prototype.bind = function bind(context = window, ...args) { // 经过测试:apply 的性能不如 call(3 个参数以上) return (...amArg) => this.call(context, ...args.concat(...amArg)); }
面试题
阿里原题
function fn1() {
console.log(1);
}
function fn2() {
console.log(2);
}
// fn2.$fn = fn1;
// fn2.$fn() => fn2.fn1();this 指向 fn2,执行 fn1,输出 1
fn1.call(fn2); // 1
// 把 fn1.call 看成 AF0,AF0.call(fn2)
// fn2.$fn() => fn2.AF0(); fn1.call 和 fn1.call.call 都是同一个 call 函数
// 则 AF0 可以看成 call 函数,即 fn2.call 执行,this 为 window,输出 2
fn1.call.call(fn2); // 2
// 同上
Function.prototype.call.call(fn2); // 2
剑指 Offer 53 - II.0~n-1中缺失的数字
上一篇