你不知道的JavaScript_读书笔记 - childlabor/blog GitHub Wiki
你不知道的JavaScript(上)
作用域
编译:分词(词法分析)-> 解析(语法分析/AST抽象语法树)-> 代码生成
在 语法分析 和 代码生成 阶段有特定的步骤来对运行性能进行==优化==,包括对冗余元素进行优化等
作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对 变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询。
如果词法作用域完全由写代码期间函数所声明的位置来定义,怎样才能在运行时来“修改”(也可以说欺骗)词法作用域呢?
JavaScript 中有两种机制来实现这个目的。eval(..) 和 with 会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词 法作用域。
JavaScript 引擎会在编译阶段进行数项的性能优化。引擎在代码中发现了 eval(..) 或 with,所有的优化可能都是无意义的,因此最简单的做法就是完全不做任何优化。因此如果代码中大量使用 eval(..) 或 with,那么运行起来一定会变得非常慢。
当你看到 var a = 2; 时,可能会认为这是一个声明。但 JavaScript 实际上会将其看成两个 声明:var a; 和 a = 2;。第一个定义声明是在==编译阶段==进行的(提升)。第二个赋值声明会被留在 原地等待执行阶段。
// ==
a = 2;
var a;
console.log( a );
// 实际 = >
var a;
a = 2;
console.log( a ); // 2
// ==
console.log( a );
var a = 2;
// 实际 = >
var a;
console.log( a ); // undefined 不会报错
a = 2;
函数声明和变量声明都会被提升。
函数会首先被提升,然后才是变量。
foo(); // 1
var foo;
function foo() {
console.log( 1 );
};
foo = function() {
console.log( 2 );
};
// 会输出 1 而不是 2 !这个代码片段会被引擎理解为如下形式:
function foo() {
console.log( 1 );
};
foo(); // 1
foo = function() {
console.log( 2 );
};
尽管重复的 var 声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。
foo(); // 3
var foo;
function foo() {
console.log( 1 );
};
foo = function() {
console.log( 2 );
};
function foo() {
console.log( 3 );
};
this
this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件。
- 由 new 调用?绑定到新创建的对象。
- 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
- ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。
你不知道的JavaScript(中)
运算符优先级
执行顺序并非我们所设想的从左到右
&& 运算符先于 || 执行,而 || 的优先级又高于 ? :
true || false && false; // true
(true || false) && false; // false
true || (false && false); // true
对 && 和 || 来说,如果从左边的操作数能够得出结果,就可以忽略右边的操作数。我们将 这种现象称为“短路”(即执行最短路径)。
浏览器在后台异步处理控制台 I/O 能够提高性能。如果在调试的过程中遇到对象在 console.log(..) 语句之后被修改,可你却看到了意料之外的结果,要意识到这可能是这种 I/O 的异步化造成的。
var a = {
index: 1
};
// 然后
console.log( a ); // ??
// 再然后
a.index++;
// 输出{ index: 2 }
你不知道的JavaScript(下)
ES6 引入了一个新的运算符 ...,通常称为 spread rest(展开或收集)运算符,取决于 它在哪 / 如何使用。
// 展开
function foo(x,y,z) {
console.log( x, y, z );
}
foo( ...[1,2,3] ); // 1 2 3
// 收集
function foo(x, y, ...z) {
console.log( x, y, z );
}
foo( 1, 2, 3, 4, 5 ); // 1 2 [3,4,5]
set 固有的唯一性是它最有用的特性。举例来说:
var s = new Set( [1,2,3,4,"1",2,4,"5"] ),
uniques = [ ...s ];
uniques; // [1,2,3,4,"1","5"]
// set 的唯一性不允许强制转换,所以 1 和 "1" 被认为是不同的值。
ES6 定义了一个字符串原型方法 repeat(..):
"foo".repeat( 3 ); // "foofoofoo"
Object.observe(); 已废弃
Proxy使用场景
// 1. 抽离校验模块
let numericDataStore = {
count: 0,
amount: 1234,
total: 14
};
numericDataStore = new Proxy(numericDataStore, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("Properties in numericDataStore can only be numbers");
}
return Reflect.set(target, key, value, proxy);
}
});
// 抛出错误,因为 "foo" 不是数值
numericDataStore.count = "foo";
// 赋值成功
numericDataStore.count = 333;
// 2. 私有属性
let api = {
_apiKey: '123abc456def', /* mock methods that use this._apiKey */
getUsers: function(){}
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
get(target, key, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} is restricted. Please see api documentation for further info.`);
}
return Reflect.get(target, key, proxy);
}
});
// 3. 访问日志
...
// 4. 预警和拦截
...
// 5. 过滤操作
...
// Proxy 支持随时取消对 target 的代理
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。
将Object对象的一些明显属于==语言内部==的方法(比如Object.defineProperty),放到Reflect对象上。
Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。