更多课程 选择中心


Web培训

400-996-5531

Web培训

web培训中JavaScript this指的是什么?


因为工作原因,经常关注有关互联网行业的最新动态。说起到WEB的工作,如今这个市场行情,做WEB前端开发工作,没有经验不好找工作,但是基础的操作都不会,按时压根就找不到工作啊。所以,由于市场的引导,web培训机构也是层出不穷。达内web培训发展已十余年之久,服务与课程体系都已成熟。而且还是包就业哦!那么今天小编先给大家说点实际的吧!我们今天带来的是web培训课程中JavaScript this的应用。

要说 JavaScript 这门语言最容易让人困惑的知识点,this 关键词肯定算一个。JavaScript 语言面世多年,一直在进化完善,现在在服务器上还可以通过 node.js 来跑 JavaScript。显然,这门语言还会活很久。

所以说,我一直相信,如果你是一个 JavaScript 开发者或者说 web 开发者,学好 JavaScript 的运作原理以及语言特点肯定对你以后大有好处。

开始之前

在开始正文之前,我强烈推荐你先掌握好下面的知识:

变量作用域和作用域提升

JavaScript 的函数

闭包

如果没有对这些基础知识掌握踏实,直接讨论 JavaScript 的 this 关键词只会让你感到更加地困惑和挫败。

什么是 this ?

在我开始讲解前,如果你学过一门基于类的面向对象编程语言(比如 C#,Java,C++),那请将你对 this 这个关键词应该是做什么用的先入为主的概念扔到垃圾桶里。JavaScript 的 this 关键词是很不一样,因为 JavaScript 本来就不是一门基于类的面向对象编程语言。

虽说 ES6 里面 JavaScript 提供了类这个特性给我们用,但它只是一个语法糖,一个基于原型继承的语法糖。

this 就是一个指针,指向我们调用函数的对象。

我难以强调上一句话有多重要。请记住,在 Class 添加到 ES6 之前,JavaScript 中没有 Class 这种东西。Class 只不过是一个将对象串在一起表现得像类继承一样的语法糖,以一种我们已经习惯的写法。所有的魔法背后都是用原型链编织起来的。

如果上面的话不好理解,那你可以这样想,this 的上下文跟英语句子的表达很相似。比如下面的例子

Bob.callPerson(John);

就可以用英语写成 “Bob called a person named John”。由于 callPerson() 是 Bob 发起的,那 this 就指向 Bob。我们将在下面的章节深入更多的细节。到了这篇文章结束时,你会对 this 关键词有更好的理解(和信心)。

执行上下文

执行上下文 是语言规范中的一个概念,用通俗的话讲,大致等同于函数的执行“环境”。具体的有:变量作用域(和 作用域链条,闭包里面来自外部作用域的变量),函数参数,以及 this 对象的值。

引自: #

记住,现在起,我们专注于查明 this 关键词到底指向哪。因此,我们现在要思考的就一个问题:

是什么调用函数?是哪个对象调用了函数?

为了理解这个关键概念,我们来测一下下面的代码。

var person = {

name: "Jay",

greet: function() {

console.log("hello, " + this.name);

}

};

person.greet();

谁调用了 greet 函数?是 person 这个对象对吧?在 greet() 调用的左边是一个 person 对象,那么 this 关键词就指向 person,this.name 就等于 "Jay"。现在,还是用上面的例子,我加点料:

var greet = person.greet; // 将函数引用存起来;

greet(); // 调用函数

你觉得在这种情况下控制台会输出什么?“Jay”?undefined?还是别的?

正确答案是 undefined。如果你对这个结果感到惊讶,不必惭愧。你即将学习的东西将帮助你在 JavaScript 旅程中打开关键的大门。

this 的值并不是由函数定义放在哪个对象里面决定,而是函数执行时由谁来唤起决定。

对于这个意外的结果我们暂且压下,继续看下去。(感觉前后衔接得不够流畅)

带着这个困惑,我们接着测试下 this 三种不同的定义方式。

我为什么要学 this?

如果上面的简单介绍没有说服你来深入探索 this 关键词,那我用这节来讲讲为什么要学。

考虑这样一个重要问题,假设开发者,比如 Douglas Crockford (译者注:JavaScript 领域必知牛人),不再使用 new 和 this,转而使用完完全全的函数式写法来做代码复用,会怎样?

事实上,基于 JavaScript 内置的现成的原型继承功能,我们已经使用并且将继续广泛使用 new 和 this 关键词来实现代码复用。

理由一,如果只能使用自己写过的代码,你是没法工作的。现有的代码以及你读到这句话时别人正在写的代码都很有可能包含 this 关键词。那么学习怎么用好它是不是很有用呢?

因此,即使你不打算在你的代码库中使用它,深入掌握 this 的原理也能让你在接手别人的代码理解其逻辑时事半功倍。

理由二,拓展你的编码视野和技能。使用不同的设计模式会加深你对代码的理解,怎么去看、怎么去读、怎么去写、怎么去理解。我们写代码不仅是给机器去解析,还是写给我们自己看的。这不仅适用于 JavaScript,对其他编程语言亦是如此。

随着对编程理念的逐步深入理解,它会逐渐塑造你的编码风格,不管你用的是什么语言什么框架。

就像毕加索会为了获得灵感而涉足那些他并不是很赞同很感兴趣的领域,学习 this 会拓展你的知识,加深对代码的理解。

找出 this 的指向

上一节我们已经对 this 做了测试。但是这块知识实在重要,我们需要再好好琢磨一下。在此之前,我想用下面的代码给你出个题:

var name = "Jay Global";

var person = {

name: 'Jay Person',

details: {

name: 'Jay Details',

print: function() {

return this.name;

}

},

print: function() {

return this.name;

}

};

console.log(person.details.print()); // ?

console.log(person.print()); // ?

var name1 = person.print;

var name2 = person.details;

console.log(name1()); // ?

console.log(name2.print()) // ?

console.log() 将会输出什么,把你的答案写下来。如果你还想不清楚,复习下上一节。

准备好了吗?放松心情,我们来看下面的答案。

答案和解析

person.details.print()

首先,谁调用了 print 函数?在 JavaScript 中我们都是从左读到右。于是 this 指向 details 而不是 person。这是一个很重要的区别,如果你对这个感到陌生,那赶紧把它记下。

print 作为 details 对象的一个 key,指向一个返回 this.name 的函数。既然我们已经找出 this 指向 details ,那函数的输出就应该是 'Jay Details'。

person.print()

再来一次,找出 this 的指向。print() 是被 person 对象调用的,没错吧?

在这种情况,person 里的 print 函数返回 this.name。this 现在指向 person 了,那 'Jay Person' 就是返回值。

console.log(name1)

这一题就有点狡猾了。在上一行有这样一句代码:

var name1 = person.print;

如果你是通过这句来思考的,我不会怪你。很遗憾,这样去想是错的。要记住,this 关键词是在函数调用时才做绑定的。name1() 前面是什么?什么都没有。因此 this 关键词就将指向全局的 window 对象去。

因此,答案是 'Jay Global'。

name2.print()

看一下 name2 指向哪个对象,是 details 对象没错吧?

所以下面这句会打印出什么呢?如果到目前为止的所有小点你都理解了,那这里稍微思考下你就自然有答案了。

console.log(name2.print()) // ??

答案是 'Jay Details',因为 print 是 name2 调起的,而 name2 指向 details。

词法作用域

你可能会问:“什么是词法作用域?”

逗我呢,我们不是在探讨 this 关键词吗,这个又是哪里冒出来的?好吧,当我们用起 ES6 的箭头函数,这个就要考虑了。如果你已经写了不止一年的 JavaScript,那你很可能已经碰到箭头函数。随着 ES6 逐渐成为现实标准,箭头函数也变得越来越常用。

JavaScript 的词法作用域 并不好懂。如果你 理解闭包,那要理解这个概念就容易多了。来看下下面的小段代码。

// outerFn 的词法作用域

var outerFn = function() {

var n = 5;

console.log(innerItem);

// innerFn 的词法作用域

var innerFn = function() {

var innerItem = "inner"; // 错了。只能坐着电梯向上,不能向下。

console.log(n);

};

return innerFn;

};

outerFn()();

想象一下一栋楼里面有一架只能向上走的诡异电梯。

#FormatImgID_0#

建筑的顶层就是全局 windows 对象。如果你现在在一楼,你就可以看到并访问那些放在楼上的东西,比如放在二楼的 outerFn 和放在三楼的 window 对象。

这就是为什么我们执行代码 outerFn()(),它在控制台打出了 5 而不是 undefined。

然而,当我们试着在 outerFn 词法作用域下打出日志 innerItem,我们遇到了下面的报错。请记住,JavaScript 的词法作用域就好像建筑里面那个只能向上走的诡异电梯。由于 outerFn 的词法作用域在 innerFn 上面,所以它不能向下走到 innerFn 的词法作用域里面并拿到里面的值。这就是触发下面报错的原因:

test.html:304 Uncaught ReferenceError: innerItem is not defined

at outerFn (test.html:304)

at test.html:313

this 和 use strict

为了让 JavaScript 更加健壮及尽量减少人为出错,ES5 引进了严格模式。一个典型的例子就是 this 在严格模式下的表现。你如果想按照严格模式来写代码,你只需要在你正在写的代码的作用域最顶端加上这么一行 "use strict;"。

记住,传统的 JavaScript 只有函数作用域,没有块作用域。举个例子:

function strict() {

// 函数级严格模式写法

'use strict';

function nested() { return 'And so am I!'; }

return "Hi! I'm a strict mode function! " + nested();

}

function notStrict() { return "I'm not strict."; }

代码片段来自 Mozilla Developer Network。

不过呢,ES6 里面通过 let 关键词提供了块作用域的特性。

现在,来看一段简单代码,看下 this 在严格模式和非严格模式下会怎么表现。在继续之前,请将下面的代码运行一下。

(function() {

"use strict";

console.log(this);

})();

(function() {

// 不使用严格模式

console.log(this);

})();

正如你看到的,this 在严格模式下指向 undefined。相对的,非严格模式下 this 指向全局变量 window。大部分情况下,开发者使用 this ,并不希望它指向全局 window 对象。严格模式帮我们在使用 this 关键词时,尽量少做搬起石头砸自己脚的蠢事。

举个例子,如果全局的 window 对象刚好有一个 key 的名字和你希望访问到的对象的 key 相同,会怎样?上代码吧:

(function() {

// "use strict";

var item = {

document: "My document",

getDoc: function() {

return this.document;

}

}

var getDoc = item.getDoc;

console.log(getDoc());

})();

这段代码有两个问题。

this 将不会指向 item。

如果程序在非严格模式下运行,将不会有错误抛出,因为全局的 window 对象也有一个名为 document 的属性。

在这个简单示例中,因为代码较短也就不会形成大问题。

如果你是在生产环境像上面那样写,当用到 getDoc 返回的数据时,你将收获一堆难以定位的报错。如果你代码库比较大,对象间互动比较多,那问题就更严重了。

值得庆幸的是,如果我们是在严格模式下跑这段代码,由于 this 是 undefined,于是立刻就有一个报错抛给我们:

test.html:312 Uncaught TypeError: Cannot read property 'document' of undefined at getDoc (test.html:312) at test.html:316 at test.html:317

明确设置执行上下文

先前假定大家都对执行上下文不熟,于是我们聊了很多关于执行上下文和 this 的知识。

让人欢喜让人忧的是,在 JavaScript 中通过使用内置的特性开发者就可以直接操作执行上下文了。这些特性包括:

bind():不需要执行函数就可以将 this 的值准确设置到你选择的一个对象上。还可以通过逗号隔开传递多个参数,如 func.bind(this, param1, param2, ...) 。

apply():将 this 的值准确设置到你选择的一个对象上。第二个参数是一个数组,数组的每一项是你希望传递给函数的参数。最后,执行函数。

call():将 this 的值准确设置到你选择的一个对象上,然后想 bind 一样通过逗号分隔传递多个参数给函数。如:print.call(this, param1, param2, ...)。最后,执行函数。

上面提到的所有内置函数都有一个共同点,就是它们都是用来将 this 关键词指向到其他地方。这些特性可以让我们玩一些骚操作。只是呢,这个话题太广了都够写好几篇文章了,所以简洁起见,这篇文章我不打算展开它的实际应用。

重点:上面那三个函数,只有 bind() 在设置好 this 关键词后不立刻执行函数。

this 和箭头函数

在 ES6 里面,不管你喜欢与否,箭头函数被引入了进来。对于那些还没用惯箭头函数或者新学 JavaScript 的人来说,当箭头函数和 this 关键词混合使用时会发生什么,这个点可能会给你带来小小的困惑和淡淡的忧伤。那这个小节就是为你们准备的!

当涉及到 this 关键词,箭头函数 和 普通函数 主要的不同是什么?

答案:

箭头函数按词法作用域来绑定它的上下文,所以 this 实际上会引用到原来的上下文。

引自:#

我实在没法给出比这个更好的总结。

箭头函数保持它当前执行上下文的词法作用域不变,而普通函数则不会。换句话说,箭头函数从包含它的词法作用域中继承到了 this 的值。

我们不妨来测试一些代码片段,确保你真的理解了。想清楚这块知识点未来会让你少点头痛,因为你会发现 this 关键词和箭头函数太经常一起用了。

示例

仔细阅读下面的代码片段。

var object = {

data: [1,2,3],

dataDouble: [1,2,3],

double: function() {

console.log("this inside of outerFn double()");

console.log(this);

return this.data.map(function(item) {

console.log(this); // 这里的 this 是什么??

return item * 2;

});

},

doubleArrow: function() {

console.log("this inside of outerFn doubleArrow()");

console.log(this);

return this.dataDouble.map(item => {

console.log(this); // 这里的 this 是什么??

return item * 2;

});

}

};

object.double();

object.doubleArrow();

如果我们看执行上下文,那这两个函数都是被 object 调用的。所以,就此断定这两个函数里面的 this 都指向 object 不为过吧?是的,但我建议你拷贝这段代码然后自己测一下。

这里有个大问题:

arrow() 和 doubleArrow() 里面的 map 函数里面的 this 又指向哪里呢?

 

上一张图已经给了一个大大的提示。如果你还不确定,那请花5分钟将我们上一节讨论的内容再好好想想。然后,根据你的理解,在实际执行代码前把你认为的 this 应该指向哪里写下来。在下一节我们将会回答这个问题。

回顾执行上下文

这个标题已经把答案泄露出来了。在你看不到的地方,map 函数对调用它的数组进行遍历,将数组的每一项传到回调函数里面并把执行结果返回。如果你对 JavaScript 的 map 函数不太了解或有所好奇,可以读读这个了解更多。

总之,由于 map() 是被 this.data 调起的,于是 this 将指向那个存储在 data 这个 key 里面的数组,即 [1,2,3]。同样的逻辑,this.dataDouble 应该指向另一个数组,值为 [1,2,3]。

现在,如果函数是 object 调用的,我们已经确定 this 指向 object 对吧?好,那来看看下面的代码片段。

double: function() {

return this.data.map(function(item) {

console.log(this); // 这里的 this 是什么??

return item * 2;

});

}

这里有个很有迷惑性的问题:传给 map() 的那个匿名函数是谁调用的?答案是:这里没有一个对象是。为了看得更明白,这里给出一个 map 函数的基本实现。

// Array.map polyfill

if (Array.prototype.map === undefined) {

Array.prototype.map = function(fn) {

var rv = [];

for(var i=0, l=this.length; i<l; i++)

rv.push(fn(this[i]));

return rv;

};

}

fn(this[i])); 前面有什么对象吗?没。因此,this 关键词指向全局的 windows 对象。那,为什么 this.dataDouble.map 使用了箭头函数会使得 this 指向 object 呢?

我想再说一遍这句话,因为它实在很重要:

箭头函数按词法作用域将它的上下文绑定到 原来的上下文

现在,你可能会问:原来的上下文是什么?问得好!

谁是 doubleArrow() 的初始调用者?就是 object 对吧?那它就是原来的上下文

 

我们学习任何一门技术都得先从企业需求的角度来分析,到底这个市场需要怎样的前端开发人才,这样才有让我们这些开发从业者有机会去思考满足需求这个问题,进而才能正确地确立一个可行的职业发展方向,最后达到自己想要的目标。web的前景无限啊!达内web培训大门为你敞开,欢迎你的加入。

免责声明:内容和图片源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容

预约申请免费试听课

填写下面表单即可预约申请免费试听! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!

上一篇:web前端开发-JS代码实现超级简单的区块链
下一篇:web前端培训中HTML 速查列表的教程

怎样快速学会web渗透?

Web前端开发培训学习

微信小游戏怎么开发?

Web前端工程师需要掌握哪些知识?

Copyright © 2023 Tedu.cn All Rights Reserved 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有

选择城市和中心
黑龙江省

吉林省

河北省

贵州省

云南省

广西省

海南省