98

常见函数错误引发的思考

 6 years ago
source link: http://mp.weixin.qq.com/s/IgQ4bXHDqHsxmiOOzWNB2g
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

常见函数错误引发的思考

Original 冯鹏 大转转FE 2017-12-01 00:08 Posted on

今天在写代码的时候,我犯了一个很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的语法来改写一下上面用构造函数定义的“类”。

classZhuanzhuan{constructor(name){this.name=name;}say(){console.log('hi~I am zhuanzhuan');}}letzhuanzhuan=newZhuanzhuan('zhuanzhuan');

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

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

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

——————————————————

长按二维码,关注大转转FE

                                    

Image

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK