this是JavaScript这门语言中极其重要的一个知识点,特别是关于面向对象的相关的写法,能够说掌握了this的特性,至关于掌握了一大半JavaScript面向对象的编写能力。总的来讲,JavaScript中的this大概有7种状况,理解到位了这些状况,基本上就掌握了这部分相关的内容,全部的高级写法,都是基于这些状况的演变。这7种状况分别是:
数组
咱们所说的全局环境,其实指的就是window这个对象,也就是咱们在浏览器中每打开一个页面,都会生成的一个window。先来看看最简单的全局调用。浏览器
function fn1() {
console.log( this );
}
fn1(); // window
// 至关于
window.fn1();
复制代码
咱们都知道,全局下使用var声明的变量,都会隐式的被建立为window对象的属性和方法。因此,当你看到一个函数被调用而没有前缀的时候(也就是说不是经过"."符号来调用),这其实就是全局对象window在调用它。所以,此时函数内部的this是指向window对象的。再来看个变化版本。bash
let o = {
name: 'abc',
fn: function() {
console.log( this.a );
}
}
let fn2 = o.fn;
fn2(); // undefined
复制代码
是的,虽然fn2拿到的是对象o里面的一个方法,可是,万变不离其宗,在执行fn2()的时候,仍然是没有前缀的,那是谁在调用fn2的?固然是window对象。因此这里的this也指向window。app
咱们如今知道,全局对象window调用的函数,内部的this就是指向window。可是这里有个问题须要注意一下。JavaScript有严格模式和非严格模式之分(严格模式就在代码的顶部加上一句"use strict")。在这两种状况下,this的指向是有区别的。
非严格模式下this指向咱们已经讨论过了,指的是window对象,而严格模式下的全局调用,this指向的是undefined。
框架
"use strict"
function fn1() {
console.log( this );
}
fn1(); // undefined
复制代码
JavaScript中对于事件的处理是采用异步回调的方式,对一个元素绑定一个回调函数,当事件触发的时候去执行这个函数。而对于回调函数的绑定,有下面几种状况:异步
<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在调用它。这种状况,知道就好,无需太花力气死磕。ui
let div1 = document.getElementById("div1");
div1.onclick = function() {
console.log( this ); // div1
}
复制代码
这是经过动态绑定的方式,给元素添加了事件,这种状况下,当回调函数执行的时候,是元素div1在调用它,因此此时函数内部的this,是指向元素div1的。this
let div1 = document.getElementById("div1");
div1.addEventListener("click", function() {
console.log( this ); // div1
}, false);
复制代码
一样的,经过事件监听器的方式绑定的回调函数,内部的this也是指向div1。因此咱们能够总结一下得知:事件处理函数中的this,指向的是触发这个事件的元素。spa
在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这个对象。
构造函数其实就是普通的函数,只是它内部通常都书写了许多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),那么对于建立实例没有影响。
原型链函数中个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
复制代码
咱们知道,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指向是同样的,它指的就是这个对象自己。
箭头函数是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。
说到JavaScript中的this,就无法不说call, apply, bind这三个方法。在全部JavaScript函数的高级用法,或者是JavaScript框架中,都会有这三个方法的踪迹。这三个方法都是Function.prototype上的方法,因此全部的函数都默认继承了这三个方法。如今具体说说这三个方法的分别用途。
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
复制代码
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
复制代码
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。