如果同一个函数被多次声明,后面的声明就会覆盖前面的声明
function print(s) {
console.log(s);
}
var print = function (s) {
console.log(s);
};
var print = function x() {
console.log(typeof x);
};
x; // ReferenceError: x is not defined
print(); // function
这种声明函数的方式非常不直观,几乎无人使用
var add = new Function("x", "y", "return x + y");
// 等同于
function add(x, y) {
return x + y;
}
() => {}
x => {}
(x, y) => {}
({x, y}) => {}
箭头函数误区
函数体内的 this 是定义时所在的对象而不是使用时所在的对象
可让 this 指向固定化,这种特性很有利于封装回调函数
不可当作构造函数,因此箭头函数不可使用 new 命令
不可使用 yield 命令,因此箭头函数不能用作 Generator 函数
不可使用 Arguments 对象,此对象在函数体内不存在(可用 rest/spread 参数代替)
返回对象时必须在对象外面加上括号
箭头函数: () => {}
没有自己的 this 对象, 内部的 this 就是定义时上层作用域中的 this, this 指向是固定的
普通函数: fun() {}
内部的 this 指向函数运行时所在的对象, this 指向是可变的
function
命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。f();
function f() {}
f
。但是实际上,由于“变量提升”,函数f
被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。name
属性返回函数的名字。length
属性返回函数预期传入的参数个数,即函数定义之中的参数个数。toString()
方法返回一个字符串,内容是函数的源码。作用域(scope)指的是变量存在的范围。在 ES5 的规范中,JavaScript 只有两种作用域:
一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;
一种是函数作用域,变量只在函数内部存在。在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。
ES6 又新增了块级作用域
函数内部定义的变量,会在该作用域内覆盖同名全局变量。
对于var
命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var
命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
很容易犯错的一点是,如果函数A
调用函数B
,却没考虑到函数B
不会引用函数A
的内部变量。
函数参数不是必需的,JavaScript 允许省略参数,但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined
。
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值
o
)的值实际是参数obj
的地址,重新对o
赋值导致o
指向另一个地址,保存在原地址上的值当然不受影响var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
f()
的时候,没有提供第二个参数,a
的取值就变成了undefined
。这时,如果要获得第一个a
的值,可以使用arguments
对象。函数尾调用 :某个函数的最后一步是调用另一个函数
function f(x){
return g(x);
}
上面代码中,函数f
的最后一步是调用函数g
,这就叫尾调用。
以下三种情况,都不属于尾调用。
// 情况一
function f(x){
let y = g(x);
return y;
}
// 情况二
function f(x){
return g(x) + 1;
}
// 情况三
function f(x){
g(x);
}
上面代码中,情况一是调用函数g
之后,还有赋值操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。
function f(x){
g(x);
return undefined;
}
arguments
对象包含了函数运行时的所有参数,arguments[0]
就是第一个参数,arguments[1]
就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。arguments
很像数组,但它是一个对象arguments.length
属性,可以判断函数调用时到底带几个参数。arguments.callee
属性,返回它所对应的原函数。function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc(); // 5
inc(); // 6
inc(); // 7
上面代码中,start
是函数createIncrementor
的内部变量。通过闭包,start
的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc
使得函数createIncrementor
的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。
为什么闭包能够返回外层函数的内部变量?原因是闭包(上例的inc
)用到了外层变量(start
),导致外层函数(createIncrementor
)不能从内存释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就始终保存着当前值,供闭包读取。
JS 中 return 一个函数与直接 return 一个函数变量的区别
函数的节流与防抖
function makeCounter() {
var count = 0;
function counter() {
count = count + 1;
return count;
}
return counter(); // 将嵌套函数返回
}
var doCount = makeCounter();
console.log(doCount, "--doCount1"); // 1 '--doCount1'
console.log(doCount, "--doCount2"); // 1 '--doCount2'
console.log(doCount, "--doCount3"); // 1 '--doCount3'
function makeCounter() {
var count = 0;
function counter() {
count = count + 1;
return count;
}
return counter; // 将嵌套函数返回,但只写函数名称
}
var doCount = makeCounter();
console.log(doCount(), "--doCount1"); // 1 '--doCount1'
console.log(doCount(), "--doCount2"); // 2 '--doCount2'
console.log(doCount(), "--doCount3"); // 3 '--doCount3'
return counter 返回的是整一个 cunter()函数。
因此执行 var doCount = makeCounter()时,doCount 将引用 counter 函数及其中的变量环境。
那么 counter 函数及其中的变量环境,就是闭包了
闭包的形成:内部函数引用了外部函数的数据(这里为 count),
因此在 doCount=makeCounter(),则会把这个 count 保存在 doCount 中,函数执行完,count 并不会被销毁。
注意: 为什么上面这段代码没有直接写的 function doCount(){…}
而是把 function
赋值给了变量 doCount
呢?
我们通常会想当然的认为每次调用 doCount()
都会重走一遍 doCount()
中的代码块, 但其实不然。
注意 makeCounter
方法中的 return
不是 1,2,3
这样的数值, 而是一个方法,并且把这个方法赋值给了 doCount
变量。
那么在这个 makeCounter
自运行一遍之后, 其实最后赋值给 doCount
的是 count = count + 1; return count;
这段代码。
所以后面每次调用 doCount()
其实都是在调用 count = count + 1; return count;
闭包会持有父方法的局部变量并且不会随父方法销毁而销毁, 所以这个 count
其实就是来自于第一次 makeCounter
执行时创建的变量
()
跟在函数名之后,表示调用该函数。比如,print()
就表示调用print
函数。(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
eval
命令接受一个字符串作为参数,并将这个字符串当作语句执行。
eval("var a = 1;");
a; // 1
使用严格模式 加一行 ‘use strict’;