函数的扩展3
写在前面的话
感觉博客写的有点太细了,导致我学习进度过于缓慢。
因此,建议学习者以阮一峰博客为主,以本博客为补充。
我这里主要写一些示例和补充,帮助理解的更为全面
0、一句话总结
- 严格模式要用全局用(稳),不然就别用(不用容易出bug,用不好也容易出bug),所以还是babel大法好
- name属性就是函数名,bind后加前置bound
- 箭头函数写起来简单粗暴又省事(就是打符号挺麻烦,没有打英文字母简单)
- 尾递归很好很强大但是默认不开启然后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); //foo
console.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; }
简单来说,箭头函数分为三部分(名字都是我自己起的=.=):
- 参数部分;
- 箭头符号(固定的=>)
- 函数体部分
6.2、参数
简单来说:
没有参数可以写()或者写_(左边是下划线),或者随便什么非关键字且不影响正常使用的变量名;
一个参数就写该参数名即可;
多个参数需要用()包裹起来使用。
总之,不能什么都不写。
6.3、函数体部分
1、被{}包裹起来的情况:
- 大于一行语句;
- 无返回值;
2.不被{}包裹起来的情况:
- 只有一行语句,且需要这行语句的值作为函数的返回值
不管是被包括还是不被包括,函数体部分可以理解为一个代码块。
6.4、箭头函数的this
箭头函数的特点是不绑定this,具体来说,箭头函数的this,和其父级作用域的this保持一致;
而非箭头函数的this,和原函数的this保持一致;
如代码:
let foo = function (callback) {
callback();
};
function test1() {
foo(function () {
console.log(this);
})
}
test1(); //Window
new test1(); //Window
function test2() {
foo(() => {
console.log(this);
})
}
test2(); //Window
new 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思密达┑( ̄Д  ̄)┍