JavaScript中的this详解
栏目: JavaScript · 发布时间: 5年前
内容简介:this是JavaScript这门语言中极其重要的一个知识点,特别是关于面向对象的相关的写法,可以说掌握了this的特性,相当于掌握了一大半JavaScript面向对象的编写能力。总的来说,JavaScript中的this大概有7种情况,理解到位了这些情况,基本上就掌握了这部分相关的内容,所有的高级写法,都是基于这些情况的演变。这7种情况分别是:我们所说的全局环境,其实指的就是window这个对象,也就是我们在浏览器中每打开一个页面,都会生成的一个window。先来看看最简单的全局调用。我们都知道,全局下使
this是JavaScript这门语言中极其重要的一个知识点,特别是关于面向对象的相关的写法,可以说掌握了this的特性,相当于掌握了一大半JavaScript面向对象的编写能力。总的来说,JavaScript中的this大概有7种情况,理解到位了这些情况,基本上就掌握了这部分相关的内容,所有的高级写法,都是基于这些情况的演变。这7种情况分别是:
- 全局环境调用下的this
- 事件处理函数中的this
- 对象方法内的this
- 构造函数中的this
- 原型链上函数中的this
- getter和setter中个this
-
箭头函数中的this
除了最后一个箭头函数中的this指向,是基于函数书写时候确定的,其它所有情况下,JavaScript中的this,都是由调用时决定的。很多时候,大家会很纳闷,这个this,到底是什么?this可以是全局对象window,可以是一个具体的元素比如<div></div>,也可以是一个对象比如{},还可以是一个实例等等。至于this到底是什么,就看函数执行的时候,到底是谁调用了它。
1.全局环境调用
我们所说的全局环境,其实指的就是window这个对象,也就是我们在浏览器中每打开一个页面,都会生成的一个window。先来看看最简单的全局调用。
function fn1() { console.log( this ); } fn1(); // window // 相当于 window.fn1(); 复制代码
我们都知道,全局下使用var声明的变量,都会隐式的被创建为window对象的属性和方法。所以,当你看到一个函数被调用而没有前缀的时候(也就是说不是通过"."符号来调用),这其实就是全局对象window在调用它。因此,此时函数内部的this是指向window对象的。再来看个变化版本。
let o = { name: 'abc', fn: function() { console.log( this.a ); } } let fn2 = o.fn; fn2(); // undefined 复制代码
是的,虽然fn2拿到的是对象o里面的一个方法,但是,万变不离其宗,在执行fn2()的时候,仍然是没有前缀的,那是谁在调用fn2的?当然是window对象。所以这里的this也指向window。
1.1 严格模式和非严格模式的区别
我们现在知道,全局对象window调用的函数,内部的this就是指向window。但是这里有个问题需要注意一下。JavaScript有严格模式和非严格模式之分(严格模式就在代码的顶部加上一句"use strict")。在这两种情况下,this的指向是有区别的。
非严格模式下this指向我们已经讨论过了,指的是window对象,而严格模式下的全局调用,this指向的是undefined。
"use strict" function fn1() { console.log( this ); } fn1(); // undefined 复制代码
2.事件处理函数中的this
JavaScript中对于事件的处理是采用异步回调的方式,对一个元素绑定一个回调函数,当事件触发的时候去执行这个函数。而对于回调函数的绑定,有下面几种情况:
- 元素标签内绑定
- 动态绑定
- 事件监听 这几种情况下,回调函数内的this分别又是什么呢?分别来看看。
2.1元素标签内绑定
<div id="div1" onclick="console.log( this )"></div> 复制代码
点击元素div1后,我们发现控制台打印的是"<div id="div1" onclick="console.log( this )">",可以知道的是,元素内联所执行的语句中的this,指向的是元素本身。但是,有一个特例,来改动一下方式。
<div id="div1" onclick="(function () {console.log( this )}()"></div> 复制代码
看明白了吗,元素内联的是一个匿名自执行函数,这个时候匿名自执行函数中的this,就不是指向元素本身了,而是window对象!虽然这种写法很无聊,但这就是内联写法我们需要注意的一个点。我们可以这样理解,匿名自执行函数有独立的作用域,相当于是window在调用它。这种情况,知道就好,无需太花力气死磕。
2.2 动态绑定
let div1 = document.getElementById("div1"); div1.onclick = function() { console.log( this ); // div1 } 复制代码
这是通过动态绑定的方式,给元素添加了事件,这种情况下,当回调函数执行的时候,是元素div1在调用它,所以此时函数内部的this,是指向元素div1的。
2.3 事件监听
let div1 = document.getElementById("div1"); div1.addEventListener("click", function() { console.log( this ); // div1 }, false); 复制代码
同样的,通过事件监听器的方式绑定的回调函数,内部的this也是指向div1。所以我们可以总结一下得知:事件处理函数中的this,指向的是触发这个事件的元素。
3.对象方法中的this
在JavaScript中,对象是可以有属性和方法的,这个方法,其实就是函数。既然是函数,那么内部肯定也会有this,作为对象方法中的this,到底是指的什么呢?看个简单的例子。
var name = 'aaa'; let obj = { name: 'jack', fn: function() { console.log( this.name ); } } let f1 = obj.fn; obj.fn(); // jack f1(); // aaa 复制代码
作为对象的方法调用的函数,它内部的this,就指向这个对象。在这个例子中,当通过obj.fn()的形式调用fn函数的时候,它内部的this指的就是obj这个对象了。至于第二种情况,先把obj.fn赋值给f1,然后通过执行f1来执行函数的情况,我们在上面已经说过,这个时候,其实是window对象在调用f1,因此它内部的this就是指向window对象,因而打印的就是'aaa'。
如果是一个对象中嵌套着比较深的方法,它内部的this又是什么呢?
let person = { name: 'jack', eat: { name: 'apple', fn1: function() { console.log( this.name ); }, obj: { name: 'grape', fn2: function() { console.log( this.name ); } } } } person.eat.fn1(); // apple person.eat.obj.fn2(); // grape 复制代码
这里遵守一个就近原则:如果是通过对象方法的方式调用函数,则函数内部的this指向离它最近一级的那个对象。在这个例子中,person.eat.fn1()这种调用,fn1中的this指的就是eat这个对象;person.eat.obj.fn2()这种调用方式,fn2中的this,指的就是obj这个对象。
4.构造函数中的this
构造函数其实就是普通的函数,只是它内部一般都书写了许多this,可以通过new的方式调用来生成实例,所以我们一般都用首字母大写的方式,来区分构造函数和一般的函数。构造函数,是JavaScript中书写面向对象的重要方式。
function Fn1(name) { this.name = name; } let n1 = new Fn1('abc'); n1.name; // abc 复制代码
这是一个非常简单的构造函数书写方式,以及对构造函数的调用。构造函数中的this,以及new调用的这种方式,其实都是为了能够创造实例服务的,否则也就没有意义了。那么,构造函数中的this也就很清楚了:它指向构造函数所创造的实例。当通过new方法调用构造函数的时候,构造函数内部的this就指向这实例,并将相应的属性和方法"生成"给这个实例。通过这个方法,生成的实例才能够获取属性和方法。
凡事总有例外嘛,构造函数中有这样一种例外,我们看看。
function Fn1(name) { this.name = name; return null; } function Fn2(name) { this.name = name; return {a: '123'}; } let f1 = new Fn1("ttt"); console.log( f1 ); // {name: "ttt"} let f2 = new Fn2("ggg"); console.log( f2 ); // {a: "123"} 复制代码
f1是通过new Fn1创建的一个实例,这没有问题。但f2为什么不是我们所想的结果呢? 当构造函数内部return的是一个对象类型的数据的时候,通过new所得到的,就是构造函数return出来的那个对象;当构造函数内部return的是基本类型数据(数字,字符串,布尔值,undefined,null),那么对于创建实例没有影响。
5.原型链函数中的this
原型链函数中个this,其实跟构造函数中的this一样,也是指向创建的那个实例。
function Fn() { this.name = '878978' } Fn.prototype.sum = function() { console.log(this) return this; } let f5 = new Fn(); let f6 = new Fn(); console.log( f5 === f5.sum() ); // true console.log( f6 === f6.sum() ); // true 复制代码
6.getter和setter中的this
我们知道,JavaScript中getter和setter是作为对对象属性读取和修改的一种劫持。可以分别在读取和设置对象相应属性的时候触发。
let obj = { n: 1, m: 2, get sum() { console.log(this.n, this.m); return '正在尝试访问sum...'; }, set sum(k) { this.m = k; return '正在设置obj属性sum...'; } } obj.sum; // 1,2 obj.sum = 5; // 正在设置obj属性sum.. 复制代码
getter和setter中的this,规则跟作为对象方法调用时候函数内部的this指向是一样的,它指的就是这个对象本身。
7.箭头函数中的this
箭头函数是ES6中新推出的一种函数简写方法,跟ES5函数最大的区别,就要数它的this规则了。在ES5的函数中,this都是在函数调用的时候,才能确定具体的this指向。而箭头函数,其实是没有this的,但是它内部的这个所谓this,在箭头函数书写的时候,就已经绑定了(绑定父级的this),并且无法改变。看个例子。
let div1 = document.getElementById("div"); div1.onclick = function() { setTimeout(() => { console.log( this ); // div1 }, 500); } 复制代码
我们知道,setTimeout中所绑定的回调函数,其实是window在调用它,所以它内部的this指向的是window。但是,当回调函数是箭头函数的写法的时候,内部的this竟然是div1!这在箭头函数书写的时候,就已经决定了它内部的this指向,就是它父级的this。而它父级函数作用域中的this,其实就是元素div1。作为对象方法的箭头函数,其实也是类似的道理。
var name = 'aaa'; let obj = { name: 'jack', fn1: () => { console.log( this.name ); } } obj.fn1(); // aaa 复制代码
没错,还是那句话,当我们写下箭头函数的时候,它内部的this就已经确定了,并且无法修改(call, apply, bind)。这个例子中,箭头函数最近的父级作用域显然是全局环境window,因此它的this就指向window。
8.call, apply, bind的用法
说到JavaScript中的this,就没法不说call, apply, bind这三个方法。在所有JavaScript函数的高级用法,或者是JavaScript框架中,都会有这三个方法的踪影。这三个方法都是Function.prototype上的方法,所以所有的函数都默认继承了这三个方法。现在具体说说这三个方法的分别用途。
8.1 call
call方法可以实现对函数的立即调用,并且显示的指定函数内部的this以及传参。
let obj = { color: 'green' } function Fn() { console.log( this.color ); } Fn(); // undefined Fn.call(obj); // green 复制代码
call可以实现对函数的立即调用,并且改变函数内部的this指向。上面的例子中,直接调用函数Fn的时候,它内部的this指向window对象,因此打印的是undefined;当通过call指定函数内部的this指向obj的时候,它就能获取到obj上的属性和方法了。call调用还能实现调用时候的传参,请看。
let obj = { color: 'blue' } function Fn(height, width) { console.log(`the tree is ${this.color}, and the tall is ${height}, width is ${width}`); } Fn.call(obj, 20, 3); // the tree is blue, and the tall is 20, width is 3 复制代码
8.2 apply
apply的作用和call是一模一样的,都是实现对函数内部this的改变,唯一的区别就是传参的方式不一样:call是通过一个一个参数的方式传递参数,而apply是通过数组的形式传递多个参数。
let obj = { color: 'orange' } function Fn(height, width) { console.log(`the tree is ${this.color}, and the tall is ${height}, width is ${width}`); } Fn.apply(obj, [16, 7]); // the tree is orange, and the tall is 16, width is 7 复制代码
8.3 bind
call和apply都是实现对函数的立即调用,并且改变函数内部this的指向,如果说我只想改变函数内部的this,而不执行函数,该怎么办?这个时候,就需要用到bind。
let person = { name: 'jack' } function Person() { console.log(this.name); } let p1 = Person.bind(person); p1(); // 'jack' 复制代码
当一个函数执行完bind方法后,会返回一个新的函数,而这个新的函数跟原函数相比,内部的this指向被显示的改变了。但是不会立即执行新的函数,而是在你需要的时候才去调用。 但是有一点需要注意,返回的新函数p1,它内部的this就无法再改变了。接着上面的例子。
let animal = { name: 'animal' } let p2 = p1.bind(); p2(); // 'jack' 复制代码
p2的this依然是指向obj,而非animal。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Flutter 完整开发实战详解(十六、详解自定义布局实战)
- 数据结构 1 线性表详解 链表、 栈 、 队列 结合JAVA 详解
- 详解Openstack环境准备
- Java泛型详解
- iOS RunLoop 详解
- Raft协议详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Pragmatic Programmer
Andrew Hunt、David Thomas / Addison-Wesley Professional / 1999-10-30 / USD 49.99
本书直击编程陈地,穿过了软件开发中日益增长的规范和技术藩篱,对核心过程进行了审视――即根据需求,创建用户乐于接受的、可工作和易维护的代码。本书包含的内容从个人责任到职业发展,直至保持代码灵活和易于改编重用的架构技术。从本书中将学到防止软件变质、消除复制知识的陷阱、编写灵活、动态和易适应的代码、避免出现相同的设计、用契约、断言和异常对代码进行防护等内容。一起来看看 《The Pragmatic Programmer》 这本书的介绍吧!