学习笔记 2020-10-28
JavaScript 高级程序设计(第4版) 阅读记录
函数
立即调用的函数表达式
立即调用的匿名函数又被称作立即调用的函数表达式 ( IIFE
, Immediately Invoked Function Expression
)。它类似于函数声明,但由于被包含在括号中,会被解释为函数表达式。紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式。
使用 IIFE
可以模拟块级作用域。即在一个函数表达式内部声明变量,然后立即调用这个函数。
使用立即调用的函数表达式可以解决 var
带来的问题。
let divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; ++i) {
divs[i].addEventListener(
'click',
(function (frozenCounter) {
return function () {
console.log(frozenCounter);
};
})(i)
);
}
在 ES6
,有了块级作用域的概念,就可以规避这个问题。
私有变量
JS
没有私有成员的概念,所有对象属性都是公有的。有私有变量的概念。任何定义在函数或块中的变量,都可以认为是私有的。
特权方法是能够访问函数私有变量及私有函数的公有方法。在对象上有两种方式创建特权方法。第一种是在构造函数中实现。
function MyObject() {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 特权方法
this.publicMethod = function () {
privateVariable++;
return privateFunction();
};
}
这个模式是把所有私有变量和私有函数都定义在构造函数中。然后,再创建一个能够访问这些私有成员的特权方法。因为形成了闭包,所以可行。
静态私有变量
特权方法可以通过使用私有作用域定义私有变量和函数来实现。
(function () {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 构造函数
MyObject = function () {};
// 公有和特权方法
MyObject.prototype.publicMethod = function () {
privateVariable++;
return privateFunction();
};
})();
使用这种方法,避免了所有方法都在对象实例上重复定义。特权方法作为一个闭包,始终引用着包含它的作用域。但该种方法的每个实例没有了自己的私有变量。
模块模式
模块模式在一个单例对象上实现了相同的隔离和封装。
单例对象就是只有一个实例的对象。
let singleton = (function () {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 特权/公有方法和属性
return {
publicProperty: true,
publicMethod() {
privateVariable++;
return privateFunction();
}
};
})();
模块模式是在单例对象基础上加以拓展,使其通过作用域链来关联私有变量和特权方法。
模块模式使用了匿名函数返回一个对象。在匿名函数内部,首先定义私有变量和私有函数。之后,创建一个要通过匿名函数返回的对象字面量。这个对象字面量中只包含可以公开访问的属性和方法。本质上,对象字面量定义了单例对象的公共接口。如果单例对象需要进行某种初始化,并且需要访问私有变量时,就可以采用这个模式。
let application = (function () {
// 私有变量和私有函数
let components = new Array();
// 初始化
components.push(new BaseComponent());
// 公共接口
return {
getComponentCount() {
return components.length;
},
registerComponent(component) {
if (typeof component == 'object') {
components.push(component);
}
}
};
})();
模块增强模式
在返回对象之前先对其进行增强,适合单例对象需要是某个特定类型的实例,但又必须给它添加额外属性和方法的场景。
let singleton = (function () {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 创建对象
let object = new CustomType();
// 添加特权/公有属性和方法
object.publicProperty = true;
object.publicMethod = function () {
privateVariable++;
return privateFunction();
};
// 返回对象
return object;
})();
期约与异步函数
ES6
新增了正式的 Promise
引用类型,支持优雅地定义和组织异步逻辑。
异步编程
同步行为和异步行为的对立统一是计算机科学的一个基本概念。特别是在 JavaScript
这种单线程事件循环模型中,同步操作与异步操作更是代码所要依赖的核心机制。异步行为是为了优化因计算量大而时间长的操作。但不一定必须如此,不想为等待某个操作而阻塞线程执行,就可以使用异步操作。
同步与异步
同步行为对应内存中顺序执行的处理器指令。每条指令都会严格按照它们出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地的信息。这样的执行流程容易分析程序在执行到代码任意位置时的状态。
在程序执行的每一步,都可以推断出程序的状态。
异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。异步操作经常是必要的,因为强制进程等待一个长时间的操作通常是不可行的。如果代码要访问一些高延迟的资源,比如向远程服务器发送请求并等待响应,那么就会出现长时间的等待。但异步操作何时完成,异步操作中涉及到的数据何时可获取可操作,是不可知的。
以往的异步编程模式
异步行为是 JavaScript
的基础。在早期,只支持定义回调函数来表明异步操作完成。串联多个异步操作是一个常见的问题,通常需要深度嵌套的回调函数。
function double(value) {
setTimeout(() => setTimeout(console.log, 0, value * 2), 1000);
}
double(3);
// 6(大约 1000 毫秒之后)
在这个例子中, setTimtout
定义了一个在指定时间后会被调度执行的回调函数。1000
毫秒之后, JavaScript
执行时会把回调函数推到自己的消息队列上去等待执行。推到队列之后,回调什么时候出列被执行对 JavaScript
代码就完全不可见了。 double
函数会在 setTimeout
成功调度异步操作之后立即退出。
异步返回值
假设
setTimeout
操作会返回一个有用的值。如何把这个值进行传递?一个方法是给异步操作提供一个回调。function double(value, callback) { setTimeout(() => callback(value * 2), 1000); } double(3, x => console.log(`I was given: ${x}`)); // I was given: 6(大约 1000 毫秒之后)
失败处理
异步操作的失败处理在回调模型中也要考虑。因此出现了成功回调与失败回调。
function double(value, success, failure) { setTimeout(() => { try { if (typeof value !== 'number') { throw 'Must provide number as first argument'; } success(2 * value); } catch (e) { failure(e); } }, 1000); } const successCallback = x => console.log(`Success: ${x}`); const failureCallback = e => console.log(`Failure: ${e}`); double(3, successCallback, failureCallback); double('b', successCallback, failureCallback);
这种模式已经不可取了,因为必须在初始化异步操作时定义回调。异步函数的返回值只在短时间内存在,只有预备好将这个短时间存在的值作为参数的回调才能接收到它。
现代 JavaScript 教程
Symbol 类型
根据规范,对象的属性值只能是字符串类型或者 Symbol
类型。
Symbol
不会被隐式转换为字符串。显式转换需要调用 toString()
。或者使用 description
属性来显示描述。
Symbol
会在 for-in
中被跳过。
Object.assign
会同时复制字符串和 Symbol
属性。
全局 symbol
根据相同的名字创建出来的 symbol
都是不一样的。如果想要名字相同的 Symbol
具有相同的实体,可以使用 全局 Symbol 注册表 。
从注册表读取( 不存在则创建 ) Symbol
,使用 Symbol.for(key)
。
使用 Symbol.keyFor(sym)
,根据一个全局 Symbol 来返回一个名字。
系统 Symbol
JavaScript 内部有很多系统 Symbol。
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
- …