函数
let sum = new Function("num1", "num2", "return num1 + num2");
把函数理解为对象,函数名理解为指针
1. 箭头函数
let triple = x => { return 3 * x; };
// 参数只有一个时可以省略括号
let triple = (x) => 3 * x;
// 如果只有一行代码,可以省略大括号(会隐式返回)
// 无效的写法
let multiple = (a, b) => return a * b;
箭头函数中不能使用 arguments、super、new.target
2. 函数名
由于函数名可以理解为指针,所有函数可以有多个名称
function sum(){}
sum(); //合法
let anthorSum = sum;
anthorSum(); //合法
所有函数对象都有一个name属性:
function foo() {}
let bar = function() {};
let baz = () => {};
console.log(foo.name); // foo
console.log(bar.name); // bar
console.log(baz.name); // baz
console.log((() => {}).name); //(空字符串)
console.log((new Function()).name); // anonymous
get、set或这bind()实例化的函数会有前缀 :get、set、bound
3. 参数
ECMAScript函数不关心传入的参数个数,因为ECMAScript函数的参数在内部表现为一个数组 arguments。
实现类似重载:
function doAdd() {
if (arguments.length === 1) {
console.log(arguments[0] + 10);
} else if (arguments.length === 2) {
console.log(arguments[0] + arguments[1]);
}
}
doAdd(10); // 20
doAdd(30, 20); // 50
4. 重载
重写一个函数只会覆盖,没有重载。
5. 默认参数值
// ES5写法
function makeKing(name) {
name = (typeof name !== ‘undefined‘) ? name : ‘Henry‘;
return `King ${name} VIII`;
}
// ES6
function makeKing(name = ‘Henry‘) {
return `King ${name} VIII`;
}
// 注意这不会影响arguments的值,因为它只会与实际传入有关
6. 扩展与收集参数
6.1 扩展参数
在传参时使用
let values = [1, 2, 3, 4];
function getSum() {
let sum = 0;
for (let i = 0; i < arguments.length; ++i) {
sum += arguments[i];
}
return sum;
}
getSum(...values);
//等价
getSum.apply(null, values);
6.2 收集参数
在定义函数时使用
function getSum(...values) {
// 顺序累加values中的所有值
// 初始值的总和为0
return values.reduce((x, y) => x + y, 0);
}
console.log(getSum(1,2,3)); // 6
因为收集参数的结果可变,所以只能把它作 为最后一个参数(收集剩余参数为一个数组)
7. 函数声明和表达式
函数声明会提升,表达式不会
8. 函数作为变量
因为函数名在ECMAScript中就是变量,所以函数可以用在任何可以使用变量的地方。这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数。
function callSomeFunction(someFunction, someArgument) {
return someFunction(someArgument);
}
function add10(num) {
return num + 10;
}
let result1 = callSomeFunction(add10, 10);
console.log(result1); // 20
//返回函数
function createComparisonFunction(propertyName) {
return function(object1, object2) {
let value1 = object1[propertyName];
let value2 = object2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
let data = [
{name: "Zachary", age: 28},
{name: "Nicholas", age: 29}
];
data.sort(createComparisonFunction("name"));
9. 函数内部
9.1 arguments
arguments 是一个类数组对象,包含所有调用函数时传入的参数。
arguments对象还有一个callee属性,arguments.callee是指向arguments所在函数的指针
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
//递归时与函数名解耦
9.2 this
this,它在标准函数和箭头函数中有不同的行为。
在标准函数中,this引用的是把函数当成方法调用的上下文对象,这时候通常称其为this值(在网页的全局上下文中调用函数时,this指向windows)。
window.color = ‘red‘;
let o = {
color: ‘blue‘
};
function sayColor() {
console.log(this.color);
}
sayColor(); // ‘red‘
o.sayColor = sayColor;
o.sayColor(); // ‘blue‘
在箭头函数中,this引用的是定义箭头函数的上下文。下面的例子演示了这一点。在对sayColor()的两次调用中,this引用的都是window对象,因为这个箭头函数是在window上下文中定义的:
window.color = ‘red‘;
let o = {
color: ‘blue‘
};
let sayColor = () => console.log(this.color);
sayColor(); // ‘red‘
o.sayColor = sayColor;
o.sayColor(); // ‘red‘
在事件回调或定时回调中调用某个函数时,this值指向的并非想要的对象。此时将回调函数写成箭头函数就可以解决问 题。这是因为箭头函数中的this会保留定义该函数时的上下文:
function King() {
this.royaltyName = ‘Henry‘;
// this引用King的实例
setTimeout(() => console.log(this.royaltyName), 1000);
}
function Queen() {
this.royaltyName = ‘Elizabeth‘;
// this引用window对象
setTimeout(function() {
console.log(this.royaltyName); }, 1000);
}
new King(); // Henry
new Queen(); // undefined
注:函数名只是保存指针的变量。因此全局定义的sayColor()函数和o.sayColor()是同一个函数,只不过执行的上下文不同。
9.3 caller
这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为null。
function outer() {
inner();
}
function inner(){
console.log(inner.caller);
//console.log(arguments.callee.caller);
}
outer(); // outer函数源代码
9.4 new.target
如果函数是正常调用的,则new.target的值是undefined;如果是使用new关键字调用的,则new.target将引用被调用的构造函数。(如果有继承则值为继承的函数)
10. 其他属性和方法
10.1 属性
- length:定义函数时给定的参数个数
- prototype:原型
10.2 方法
apply()和call(),这两个方法都会以指定的this值来调用函数,即会设置调用函数时函数体内this对象的值。
window.color = ‘red‘;
let o = {
color: ‘blue‘
};
function sayColor() {
console.log(this.color);
}
sayColor(); // red
sayColor.call(this); // red
sayColor.call(window); // red
sayColor.call(o); // blue
apply():参数以数组形式传
call():参数一个一个传
bind():返回一个函数需要调用,其他与call() 相同
11. 递归
由于严格模式下禁止使用arguments.callee,我们可以使用命名函数表达式代替:
let factorial = (function f(num){
if(num <= 1){
return 1;
}else{
return num * f(num-1);
}
});
12. 闭包
闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
(也就是说不是传参,而是在函数体直接引用)
function createComparisonFunction(propertyName) {
return function(object1, object2) {
let value1 = object1[propertyName];
let value2 = object2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
发现在内部函数被返回后,它仍然引用着变量propertyName。
闭包原理:
全局上下文中的叫变量对象,它会在代码执行期间始终存在。而函数局部上下文中的叫活动对象,只在函数执行期间存在。
在定义函数时,就会为它创建作用域链,预装载全局变量对象,并保存在内部的[[Scope]]中。
在调用这个函数时,会创建相应的执行上下文,然后通过复制函数的[[Scope]]来创建其作用域链。接着会创建函数的活动对象(用作变量对象)并将其推入作用域链的前端。执行结束后局部活动对象销毁,其他不变。
但是,在一个函数内部定义的函数会把其包含函数的活动对象添加到自己的作用域链中。因此,在createComparisonFunction()函数中,匿名函数的作用域链中实际上包含 createComparisonFunction()的活动对象。
在createComparisonFunction()返回匿名函数后,它的作用域链被初始化为包含createComparisonFunction()的活动对象和全局变量对象。这样,匿名函数就可以访问到 createComparisonFunction()可以访问的所有变量。另一个有意思的副作用就是,createComparisonFunction()的活动对象并不能在它执行完毕后销毁,因为匿名函数的作用域链中仍然有对它的引用。在createComparisonFunction()执行完毕后,其执行上下文的作用域链会销毁,但它的活动对象仍然会保留在内存中,直到匿名函数被销毁后才会被销毁。
// 创建比较函数
let compareNames = createComparisonFunction(‘name‘);
// 调用函数
let result = compareNames({ name: ‘Nicholas‘ }, { name: ‘Matt‘ });
// 解除对函数的引用,这样就可以释放内存了
compareNames = null;
注:设置为null后返回的函数成为垃圾,被回收后作用域链也不存在了,也就释放了其他作用域(除了全局)的内存占用。
12.1 this对象
在闭包中使用this会让代码变复杂。
window.identity = ‘The Window‘;
let object = {
identity: ‘My Object‘,
getIdentityFunc() {
return function() {
return this.identity;
};
}
};
console.log(object.getIdentityFunc()()); // ‘The Window‘
注:内部函数不可能直接访问到外部的this,因为自己内部的this会覆盖掉
window.identity = ‘The Window‘;
let object = {
identity: ‘My Object‘,
getIdentity () {
return this.identity;
}
};
object.getIdentity(); // ‘My Object‘
(object.getIdentity)(); // ‘My Object‘
(object.getIdentity = object.getIdentity)(); // ‘The Window‘
12.2 内存泄漏
老版本IE中的引用计数dom垃圾清理
function assignHandler() {
let element =
document.getElementById(‘someElement‘);
let id = element.id;
element.onclick = () => console.log(id);
element = null;
}
13. 立即执行函数表达式
立即调用的匿名函数又被称作立即调用的函数表达式(IIFE)。它类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式。
ES5 可以用它模拟块作用域:
// IIFE
(function () {
for (var i = 0; i < 5; i++) {
console.log(i);
}
})();
console.log(i); // 抛出错误
ES6之后:
// 内嵌块级作用域
{
let i;
for (i = 0; i < count; i++) {
console.log(i);
}
}
console.log(i); // 抛出错误
// 循环的块级作用域
for (let i = 0; i < count; i++) {
console.log(i);
}
console.log(i); // 抛出错误
13.1 锁定值
let divs = document.querySelectorAll(‘div‘);
// 达不到目的!
for (var i = 0; i < divs.length; ++i) {
divs[i].addEventListener(‘click‘, function() {
console.log(i);
});
}
// i为全局作用域所以只会打印一个数字
// 使用立即执行表达式后,解决
let divs = document.querySelectorAll(‘div‘);
for (var i = 0; i < divs.length; ++i) {
(function(frozenCount) {
divs[i].addEventListener(‘click‘, function () {
console.log(frozenCount);
});
})(i);
}
// 这次变量 i 通过传参被拷贝了
有了ES6后:
let divs = document.querySelectorAll(‘div‘);
for (let i = 0; i < divs.length; ++i) {
divs[i].addEventListener(‘click‘, function() {
console.log(i);
});
}
这是因为在 ECMAScript 6中,如果对for循环使用块级作用域变量关键字,在这里就是let,那么循环就会为每个循环创建独立的变量,从而让每个单击处理程序都能引用特定的索引。
13.2 模块模式
看看就好,与闭包类似
let singleton = function () {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 特权/公有方法和属性
return {
publicProperty: privateVariable,
publicMethod() {
privateVariable++;
return privateFunction();
}
};
}();