常见函数错误引发的思考.

今天在写代码的时候,我犯了一个很low的错误,废话不多说,直接上代码:

1 function () {
2   console.log(‘hello world‘);
3 }()

大家看到之后,第一反应肯定会认为是个语法错误,可是自己仔细想想,这是什么原因?似乎还不能解释清楚,好奇宝宝模式立即启动,经过查阅相关资料得到了答案,接下来我们一起来探讨下其中的原理。

疑惑解答

大家有没有考虑过为什么上面这种写法会报错?

原来,浏览器遇到function关键字的时候会认为这是一个函数声明,函数声明必须包括:关键字function、函数名、形参、函数体。在解析上面代码的时候,解析器发现没有出现函数名而直接出现了(),浏览器便会认为这种定义不符合规范,所以就报错了。

既然是缺少函数名,如果我们给它添加函数名,是不是会正确调用?

1 function hello () {
2     console.log(‘hello world‘);
3 }()

让我们静静等待奇迹出现!

哎,浏览器在解析的时候怎么又报错了?

其实是函数声明的缘故,也就是说通过声明的函数会被提升到其他代码的前面。提升之后应该是这样了:

1 function hello () {
2     console.log(‘hello world‘);
3 }
4 ...// do something
5 (); // 咦?这是什么东东?

解析器对此也很茫然,不知道该按照什么标准去解析,只能告诉你写的不够规范了。 大家看一下下面的这种用法,会不会感觉很熟悉?

1 var hello = function () {
2     console.log(‘hello world‘);
3 }
4 hello();

试想一下,如果用()把上面的函数名hello包裹起来,会发生什么

1 var hello = function () {
2     console.log(‘hello world‘);
3 }
4 (hello)();

Bingo,浏览器输出了“hello world”,这种写法是不是特别像数学中的结合律。

继续对上面的函数调用做一些改变,如果把函数名hello替换成匿名函数,猜想应该也可以调用成功。

1 (function () {
2   console.log(‘hello world‘);
3 })();

果然达到了预期的结果,调用成功了!这是为什么?

因为用()把匿名函数包裹起来,解析器便会认为这是一个函数表达式,函数表达式的后面添加()显然是可以正确执行的。此外,除了()之外,也可以使用!等常见的一元运算符来执行匿名函数。

1 !function() {
2   console.log(‘hello world‘);
3 }();

上面这段代码会输出“hello world”。

Tips:

说到函数定义,需要注意函数声明和函数表达式两者的区别(大神可以跳过喔~):前者有个函数声明提升的过程,在代码预解析的时候,会把函数声明提升到代码的顶部,所以在声明函数位置之前调用函数并不会出错;而后者只是进行变量提升,因此,解析器只有执行函数表达式的代码之后才可以调用该函数。



说到这里,想起了一个老生常谈的问题,函数总是在特定的作用域中执行,函数中this的指向是不是也把好多同学弄的一知半解呢?让我们来继续研究一下~~~

走进函数的this

一般情况下,哪个对象调用函数(方法),则this便指向哪个对象。

通过一个例子来说明该如何确定this的指向。

 1 var obj= {
 2     number: 1,
 3     getOwnNumber: function () {
 4         var number = 2;
 5         return this.number;
 6     },
 7     getNumber: function () {
 8         var number = 3;
 9         return function () {
10             var number = 4;
11             return this.number;
12         };
13     }
14 }
15 console.log(obj.getOwnNumber());
16 console.log(obj.getNumber()());

大家猜一下,第一个输出结果是多少?大家不要犹豫,答案是1,就这么简单!

很明显,是obj调用的getOwnNumber方法,而obj对象内部定义的number值为1,所以第一个输出结果理所应当是1。

问题来了,第二个输出结果是多少?1?2?3?4?

还是同样的方法,我们找一找this到底指向哪个对象。obj.getNumber()运行结果是一个匿名函数,然后再执行匿名函数。我们可以把这个过程拆分一下,如下:

1 var fun = obj.getNumber() // 返回一个匿名函数
2 fun(); 

经过分解之后,很明显,函数是在全局作用域中调用的,所以此处的this理应指向window对象,window对象中没有定义number,所以结果是undefined。

假想一下,当代码复杂之后,确定this的指向是不是更加麻烦?别急,ES6规范提供了箭头函数的语法,可以帮助我们解决这个问题。箭头函数是何方神圣,该怎么定义呢?箭头函数的基本写法如下:

1    const hello = () => {
2         console.log(‘hello‘);
3     }

我们尝试使用箭头函数来改造obj对象的getNumber方法:

 1 const obj= {
 2     number: 1,
 3     getOwnNumber() {
 4         const number = 2;
 5         return this.number;
 6     },
 7     getNumber() {
 8         const number = 3;
 9         return () => {
10             const number = 4;
11             return this.number;
12         };
13     }
14 }
15 console.log(obj.getOwnNumber());
16 console.log(obj.getNumber()());

运行之后会发现两个输出结果都是1。这是为什么?

因为箭头函数内部的this是指向定义函数时所在的对象,在上面的代码中,this指向了obj对象,所以输出1。有了箭头函数之后,我们不再需要通过变量保存对象的this指针或者通过bind方法改变this的指针,轻松实现我们预期的效果。

箭头函数和普通函数的主要区别:(摘抄自阮一峰大神的《ES6标准入门》):

  • 箭头函数体内的this就是定义时所在的对象,而不是使用时所在的对象。
  • 箭头函数不可以当做构造函数。也就是说,不可以使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。可以使用rest参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作Generator函数

但是箭头函数也不是可以在任何场景下都能使用,如果我们要改变this的指向该怎么办?在ES6出现之前,我们可以使用call、apply和bind方法来改变函数中this的指向,ES7提出了使用双冒号(::)的运算符,使得我们可以通过这个运算符来改变箭头函数中this的指向。



除了箭头函数之外,ES6又对之前的函数做了哪些优化?

构造函数

在其他语言中可以通过类实现面向对象的功能,但是在ES6规范公布之前,JavaScript中并没有类的概念,面向对象的语法只能通过构造函数的方式来实现。

1 function Zhuanzhuan (name) {
2     this.name = name;
3 }
4 Zhuanzhuan.prototype.say = function () {
5    console.log(‘hi~I am zhuanzhuan‘);
6 }

要想实例化这个“类”,必须使用new关键字

1 var zhuanzhuan = new Zhuanzhuan(‘zhuanzhuan‘);

在ES6之前,使用构造函数来创建对象,阅读起来并不是很清晰。ES6提供的class语法跟其他面向对象的语言语法较为接近。 使用ES6的语法来改写一下上面用构造函数定义的“类”。

1 class Zhuanzhuan {
2     constructor (name) {
3         this.name = name;
4     }
5     say () {
6         console.log(‘hi~I am zhuanzhuan‘);
7     }
8 }
9 let zhuanzhuan = new Zhuanzhuan(‘zhuanzhuan‘);

看起来是不是特别像C++语言中的面向对象风格呢,出现class之后,妈妈再也不用担心我写的类不够清晰了~~~

JavaScript语言中的函数内容相当丰富,需要我们不断去通过实践去加深理解。骐骥一跃,不能十步,驽马十驾,功在不舍。多总结,多思考。大家如果有好的想法,可以相互交流和分享,每天进步一点点,不断提高自己的专业技能。

这就是我因为一个小错误,引发的思考。

如果你喜欢我们的文章,关注我们的公众号和我们互动吧。

时间: 2024-10-13 11:16:27

常见函数错误引发的思考.的相关文章

Android中的 Multiple dex files define 编译错误引发的思考

昨天我龙哥问我一个问题,他说如果一个工程中,有一个com.x.A枚举,导入的第三方jar中也有一个com.x.A枚举,那么我在工程中用A枚举的时候,会用到那个枚举呢?我当时一想,这个不是类(枚举是个特殊类)定义冲突吗?应该在编译的时候就报错呢,而且这个问题我之前遇到过,所以我很自信的和他说,这个应该在编译的时候就报错,结果他来了一句:没有呀?运行成功了,而且导入的是工程中的那个枚举A,我擦,我一想这不是打我脸吗?我记得非常清楚,是会报错的呀,所以我就自己写了一个AndroidDemo工程: 工程

一次快速排序错误引发的思考(1)

快速排序是目前基于关键字的内部排序算法中平均性能最好的,它采用了分治策略,这既是快速排序的优点也是它的缺点.从快速排序的算法描述上我们可以发现它具有递归的结构: (1)确定一个分界,将待排序的数组分为左.右两个部分: (2)使所有小(大)于临界值的数据移到左部分,大(小)于临界值的数据移到右部分: (3)这时左.右两个部分成为了两个独立的数组,分别对它们执行(1)(2)(3)的操作,直到所有数据都是有序的状态为止. 照这样的描述我们不难写出快排的代码,我平时遇到排序的问题,只要数据量上了100,

一次快速排序错误引发的思考(2)

上一次我说到所谓的“非递归”快速排序算法,不过是用栈来消除了递归,它的运行时间肯定比递归算法长,我们不妨来实际实现一下.代码如下: 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 5 #define MAX_TOP 10000 /*一个很大的栈*/ 6 #define NUM 500L 7 8 /*有关栈的数据结构*/ 9 struct Region { 10 long left; 11

【ROC曲线】关于ROC曲线、PR曲线对于不平衡样本的不敏感性分析说引发的思考

ROC曲线 在网上有很多地方都有说ROC曲线对于正负样本比例不敏感,即正负样本比例的变化不会改变ROC曲线.但是对于PR曲线就不一样了.PR曲线会随着正负样本比例的变化而变化.但是没有一个有十分具体和严谨地对此做出过分析和论证(至少我没有找到). 此处记为结论1: 结论1:PR曲线会随着正负样本比例的变化而变化:但是ROC曲线不会. 此处我就这一问题进行了详细的分析论证,并在这个过程中引发了很多思考. 首先,如何分析这个问题呢? 看下ROC曲线是由TPR和FPR组成的 下面我们这样来分析这个问题

由DBCursor的“can&#39;t switch cursor access methods”异常引发的思考

先谈谈我是怎么用的: DBCollection dbcollection = XXXXXXXXXX(); //连接mongo DBCursor dbCursor = mergeVideoDB.find(XXXX); //根据name查出若干个 if (dbCursor.length() == 1) { return videoinfos; } while(dbcursor.hasNext()){ // 这一步产生错误,报出DBCursor的"can't switch cursor access

由一个emoji引发的思考

由一个emoji引发的思考 从毕业以来,基本就一直在做移动端,但是一直就关于移动端的开发,各种适配问题的解决,在日常搬砖中处理了就过了,也没有把东西都沉淀下来,觉得甚是寒颜.现就一个小bug,让我们来了解一下我们天天都在用的emoji,对于开发来说,是一个怎么样的存在. 背景 之前在做一个留言功能时,发现在其中一台安卓5.0的手机上,输入emoji糊掉了,成了如下这样的情况 这是skr啥玩意儿呀,怎么看上去像某白色幼虫. 与是我又试了好几个手机,ios都没有问题,甚至一台安卓机中之霸(安卓4.0

曲演杂坛--一条DELETE引发的思考

原文:曲演杂坛--一条DELETE引发的思考 场景介绍: 我们有一张表,专门用来生成自增ID供业务使用,表结构如下: CREATE TABLE TB001 ( ID INT IDENTITY(1,1) PRIMARY KEY, DT DATETIME ) 每次业务想要获取一个新ID,就执行以下SQL: INSERT INTO TB001(DT) SELECT GETDATE(); SELECT @@IDENTITY 由于这些数据只需保留最近一天的数据,因此建立一个SQL作业来定期删除数据,删除脚

一次部署HTTPS的相关事件引发的思考

前言: 上周五快要下班的时候,突然收到通知客户希望了解一下部署HTTPS的流程,这种事情谁听了都会有几分诧异的.因为这件事虽然和工作有一定的相关度,但平时不会走这个方向,实际上也较少接触.此外,客户手下应该不缺人,做运维和开发的肯定比我更懂这个,但情况却和我想的不一样. 正文: 客户有需求,就应该尽量满足!因此,尽管之前对Apache.Tomcat的一些配置不熟,也未有过自己部署HTTPS的经验[当然失败的尝试还是有的],便趁着周末了解了一下相关的东西,在本地搭建了环境.实践表明,当你对一个东西

UPDATE 时主键冲突引发的思考【转】

假设有一个表,结构如下: root@localhost : yayun 22:59:43> create table t1 ( -> id int unsigned not null auto_increment, -> id2 int unsigned not null default '0', -> primary key (id) -> )engine=myisam; Query OK, 0 rows affected (0.00 sec) root@localhost