《JavaScript高级程序设计》读书笔记
箭头函数
let a = (x,y) => x*y
任何可以使用函数表达式的地方,都可以使用箭头函数。但不能使用 arguments、super 和 new.target,也不能用作构造函数,也没有prototype 属性
函数名
指向函数的指针,所以它们跟其他包含对象指针的变量具有相同的行为。
所有函数对象都会暴露一个只读的name属性,包含关于函数的信息。如果函数是一个获取函数、设置函数,或者使用bind() 实例化,那么标识符前面会加一个前缀
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
function foo() {}
console.log(foo.bind(null).name); // bound foo
let dog = {
years: 1,
get age() {
return this.years;
},
set age(newAge) {
this.years = newAge;
}
}
let propertyDescriptor = Object.getOwnPropertyDescriptor(dog, 'age');
console.log(propertyDescriptor.get.name); // get age
console.log(propertyDescriptor.set.name); // set age
理解参数
在使用非箭头函数时,可以在内部访问 arguments对象,一个类数组对象。
function sayHi(name, message) {
console.log("Hello " + name + ", " + message);
}
function sayHi() {
console.log("Hello " + arguments[0] + ", " + arguments[1]);
}
箭头函数中的参数
如果函数是使用箭头语法定义的,那么传给函数的参数将不能使用arguments关键字访问,而只能通过定义的命名参数访问。
注意:ECMAScript中所有参数都是按值传递的,不可能按引用传递参数,如果对象作为参数传递,那么传递的值就是这个对象的引用。
没有重载
如果定义了两个同名的函数,则后定义的会覆盖先定义的。
默认参数值
function makeKing(name='heihei') {
return `king ${name} VIII`;
}
console.log(makeKing());//king heihei VIII
console.log(makeKing('huang'))//king huang VIII
默认参数作用域与暂时性死区
给多个参数定义默认值实际上跟使用 let 关键字顺序声明变量一样。所以后定义默认值的参数可以引用先定义的参数。前面定义的参数不能引用后面定义的,也不能引用函数体的作用域。
参数扩展与收集
// 之前
console.log(myFun.apply(null, values));
function myFun(..values) {
return values.reduce((x, y) => x + y, 0);
}
函数声明与函数表达式
函数声明:在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义。
函数表达式:必须等到代码执行到它那一行,才会在执行上下文生成函数定义。
函数内部
arguments
类数组对象,包含调用函数时传入的所有参数。只以function 关键字定义函数时才有。该对象还有一个属性 callee ,是指向arguments 对象所在函数的指针。
function factorial(num) {
if(num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
this
在标准函数中:this 引用的是把函数当成方法调用的上下文对象。
在箭头函数中:this 引用的是定义箭头函数的上下文。
caller
引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为null。
function outer () {
inner()
}
function inner() {
console.log(inner.caller);
}
outer()
// ƒ outer () {
// inner()
// }
new.target
如果函数时正常调用的,则new.target 的值是 undefined;如果是使用new 关键字调用的,则new.target 将引用被调用的构造函数。
函数属性与方法
属性
- length 保存函数定义的命名参数的个数
- prototype 保存引用类型所有实例方法的地方
方法(设置调用函数时函数体内this 对象的值)
- apply() 第一参数是this的值,第二个参数是Array的实例或arguments 对象。
- call() 第一个参数是this的值,剩下的参数逐个传递。
- bind() 创建一个新的函数实例
递归
一个函数通过名称调用自己
尾调用优化
尾调用优化条件
- 代码在严格模式下执行(因外该状态下不能使用f.arguments 和 f.caller)
- 外部函数的返回值是对尾调用函数的调用
- 尾调用函数返回后不需要执行额外的逻辑
- 尾调用函数不是引用外部函数作用域中自由变量的闭包
尾调用优化的代码
function fib(n){
if(n < 2) {
return n;
}
return fib(n - 1) + fib(n -2);
}
"use strict"
function fib(n) {
return fibImpl(0, 1, n);
}
function fibImpl(a, b, n){
if(n === 0){
return a;
}
return fibImpl(b, a + b, n - 1);
}
fib(6); // 8
闭包
引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现。
立即调用的函数表达式
(function () {
// 块级作用域
})();
私有变量
任何定义在函数或块中的变量,都可以认为是私有的,因为这个函数或块的外部无法访问器中的变量。私有变量包含函数参数、局部变量,以及函数内部定义的其他函数。
特权方法:是能够访问函数私有变量(及私有函数)的公有方法。
构造函数实现特权方法
function MyObject() {
let private = 10;
function privateFun() {
return false;
}
// 特权方法
this.publicMethod = function() {
private++;
return privateFun();
};
};
let myobj = new MyObject();
myobj.publicMethod();
问题:必须通过构造函数来实现这种隔离,每个实例都会重新创建一遍方法。
静态私有变量实现
(function() {
let private = 10;
function privateFun() {
return false;
}
MyObj = function() {};
MyObj.prototype.publicMethod = function() {
private++;
return privateFun();
};
})();
利用原型更好地重用代码,只是每个实例没有了自己的私有变量。
模块模式
在单例对象基础上加以扩展,使其通过作用域链来关联私有变量和特权方法。
let singleton = function() {
let private = 10;
function privateFun() {
return false;
}
return {
public:true,
publicMethod() {
return privateFun();
}
}
}();