ECMAScript 6(14)函数的扩展(3)——严格模式、name、箭头函数、尾递归

来源:互联网 发布:highcharts.js 下载 编辑:程序博客网 时间:2024/04/28 23:48

函数的扩展3

写在前面的话

感觉博客写的有点太细了,导致我学习进度过于缓慢。

因此,建议学习者以阮一峰博客为主,以本博客为补充。

我这里主要写一些示例和补充,帮助理解的更为全面

0、一句话总结

  1. 严格模式要用全局用(稳),不然就别用(不用容易出bug,用不好也容易出bug),所以还是babel大法好
  2. name属性就是函数名,bind后加前置bound
  3. 箭头函数写起来简单粗暴又省事(就是打符号挺麻烦,没有打英文字母简单)
  4. 尾递归很好很强大但是默认不开启然后GG

4、严格模式

4.1、什么时候用

在函数参数使用解构赋值,默认值和三个点运算符时,不能在函数内部声明严格模式。

总而言之,如果要使用严格模式,就在该js文件的顶端添加,不要就别用,免得报错。

如果函数外面使用严格模式会报错,说明代码写的太烂了,去重构吧。

虽然也可以将该函数包括在另外一个函数的局部作用域,并对该函数使用严格模式,但这种不优雅,所以老老实实全局吧。

5、name属性

5.1、特点

原文里提到,函数的name属性是函数名;如果匿名函数赋值给变量,那么name属性就是变量名(es6)(es5为空值);

那么问题来了,假如声明一个匿名函数,赋值给foo,然后再将foo赋值给bar会怎么样?

var foo = function(){}var bar = foo;console.log(foo.name);  //fooconsole.log(bar.name);  //foo

说明name属性的值,取决于匿名函被赋予的第一个变量的名字。(即使bar = foo = function(){}同理,为foo)

另外,name属性是 只读 的,即赋值后修改无效。

5.2、anonymous

原文提到:

(new Function).name;    //"anonymous",意思是匿名的

但是只会Funtion这样的关键字生效,如果是以下代码,则不同

function foo() {}(new foo).name; //undefined//另外注意不能写为 var foo = function(){},会报错

推测是因为new Function的时候,是调用了以下函数作为构造函数:

function anonymous() {}

从以下过程得知

var foo = new Function;foo;    //function anonymous() {}

5.3、bind

在es5里,添加了bind方法。这个方法的作用是返回一个改变了this指向目标的原函数,如以下代码:

function foo() {    console.log(this);}var bar = foo.bind("this");bar();  //"this",不完全准确,但可以这么理解foo();  //Window

注意,foo函数中,this指向的目标并没有改变。

而在bind绑定后,name属性将发生变化,准确的来说,是加上前缀”bound “,如代码:

function foo() {    console.log(this);}var bar = foo.bind("this");bar.name;  //"bound foo"

“bound”是”bind”的过去式,显而易见,是指已经被绑定过的。

6、箭头函数

6.1、写法

(param1, param2, …, paramN) => { statements; }
(param1, param2, …, paramN) => expression;
// 等价于
(param1, param2, …, paramN) => { return statements; }
(param1, param2, …, paramN) => { return expression; }


// 如果只有一个参数,圆括号是可选的:
(singleParam) => { statements; }
// 等价于
singleParam => { statements; }


// 如果箭头函数 无参数 或者 有多参数, 必须使用 ()圆括号或者 _下划线:
() => { statements; }

_ => { statements; }

简单来说,箭头函数分为三部分(名字都是我自己起的=.=):

  1. 参数部分;
  2. 箭头符号(固定的=>)
  3. 函数体部分

6.2、参数

简单来说:

没有参数可以写()或者写_(左边是下划线),或者随便什么非关键字且不影响正常使用的变量名;

一个参数就写该参数名即可;

多个参数需要用()包裹起来使用。

总之,不能什么都不写。

6.3、函数体部分

1、被{}包裹起来的情况:

  1. 大于一行语句;
  2. 无返回值;


2.不被{}包裹起来的情况:

  1. 只有一行语句,且需要这行语句的值作为函数的返回值

不管是被包括还是不被包括,函数体部分可以理解为一个代码块。

6.4、箭头函数的this

箭头函数的特点是不绑定this,具体来说,箭头函数的this,和其父级作用域的this保持一致;

而非箭头函数的this,和原函数的this保持一致;

如代码:

let foo = function (callback) {    callback();};function test1() {    foo(function () {        console.log(this);    })}test1();    //Windownew test1();   //Windowfunction test2() {    foo(() => {        console.log(this);    })}test2();       //Windownew test2();   //test2 {a: "test"}

之所以最后一个指向test2这个对象,是因为通过new来创建一个函数实例时,是调用了构造器,改变了this指向的目标,而默认情况下,函数声明时,函数内部的this指向的目标是Window。

另外,因为箭头函数本身没有this,因此bind、call、apply是无效的(但不会报错),如代码:

 var bar = () => {    console.log(this);}var foo = bar.bind("a");foo();  //Window,绑定失败但不报错bar.apply("apply"); //Window,绑定失败但不报错bar.call("apply");  //Window,绑定失败但不报错

作为对比:

var bar = function () {    console.log(this);}var foo = bar.bind("bind");foo();  //"bind"bar.apply("apply"); //"apply"bar.call("call");  //"call"

6.5、一些属性和特点

1、箭头函数不能通过new来创建函数实例,如代码:

let foo = () => {    console.log(1)}new foo();  //Uncaught TypeError: foo is not a constructor

2、箭头函数没有arguments关键词。如果使用,要么取父级作用域的arguments,如果没有则返回undefined

var foo = function (a) {    console.log(arguments);    (() => {        console.log(arguments);    })()}var bar = () => {    console.log(arguments);}foo(1);//[1]//[1]bar(1); //Uncaught ReferenceError: arguments is not defined

6.5、严格模式

严格模式下,this的表现略有差别(主要体现在非箭头函数),如代码:

箭头函数是一样的,都指向Window

var bar = () => {    console.log(this);}bar();    //Window
'use strict'var bar = () => {    console.log(this);}bar();    //Window

普通匿名函数则不同,严格模式下this的值是undefined

'use strict'var bar = function () {    console.log(this);}bar();  //undefined
var bar = function () {    console.log(this);}bar();  //Window

6.6、嵌套

嵌套没啥的啦,简单暴力的说,就是把里面的箭头函数放在外面的箭头函数的函数体之中。

多层嵌套道理一样,所以没啥可说的。

如果要返回值,要么要么在函数体里return,要么就让函数体变为一个表达式并不要用大括号包裹他(可以用圆括号())

反正啊,只要你会写普通函数的嵌套,那么写箭头函数的嵌套也不在话下。

7、尾调用

7.1、什么是尾调用?

简单暴力的总结,就是在函数执行结束的时候,return 返回另外一个执行的函数。如代码:

function foo(){    return bar();   //这里bar是一个函数}

如果没有return,或者return的不是一个单纯的函数,都不叫尾调用。

也就是说,最后一行代码要严格符合上面给的示例才算。

另外,尾调用的函数中(不包括传给他的参数),里面不能包括原函数中的变量。

7.2、尾调用的原理

简单来说,其实我没理解他调用栈到底是怎么运作的,怎么压栈的,毕竟我是野路子。

但还是可以帮你理解为什么能优化

(郑重提示:以下这个只是帮忙理解,实际是调用栈,更像是一个数组)

假如函数调用如以下方式嵌套:

//这个只是帮你理解而写的不准确的例子,实际是不同的!var funA = {    name: "函数A",    variable: "变量X",    funB: {        name: "函数B",        variable: "变量Y",        funC: {            name: "函数C",            variable: "变量Z",        }    }}

funA,funB,funC分别表示函数A、B、C,然后variable表示其调用的变量

假如funB里不包含funA的变量,那么在进行尾调用的时候,由于funA的其他东西对funB没用了,于是在执行funB的时候就可以扔掉,整个结构变为以下这样:

var funB = {    name: "函数B",    variable: "变量Y",    funC: {        name: "函数C",        variable: "变量Z",    }}

如果funB里还需要使用funA的变量,那么显然funA对funB还是有意义的,于是还需要继续保留。

如果funA要保留,那么就要占用更多内存,如果调用次数比较多(典型的就是递归),那么需要占用的内存空间就可以很大,于是尾调用在这方面可以节省内存的意义就体现出来了。

7.3、尾调用的问题(比如默认不开启)

根据 饿了么这篇博客所说,V8引擎虽然实现了尾调用,但是默认是不开启的?

我实践证明(Chrome版本57.0.2987.110),的确没开启,

首先,根据调用栈最多100000次(吧?),如代码:

function sum(x, y) {  if (y > 0) {    return sum(x + 1, y - 1);  } else {    return x;  }}sum(1, 100000);    //Uncaught RangeError: Maximum call stack size exceeded

以下是尾调用代码:

function add(n, total) {    if (n === total) return n;    return add(n + 1, total);}add(0, 100000);    //Uncaught RangeError: Maximum call stack size exceeded

所以,尾调用没有被开启!GG思密达┑( ̄Д  ̄)┍

原创粉丝点击