编译器
var a = 2
需要由 编译器、作用域和引擎 三方参与分 2 步操作:
- 编译器在作用域中创建 a
- 引擎从作用域中拿到 a,并为它赋值(其引用关系也存到作用域中)
以上属于左引用LHS,在没有 var 时作用域会热心的帮忙创建一个全局的。
console.log(a)
属于右引用 RHS,找不到 a 时会报异常:
- 引擎向作用域索要 console RHS
- 引擎向作用域索要 a RHS
- 引擎向作用域询问 console.log 方法的入参,并把 a 赋值给入参 LHS
- 引擎继续执行 console.log 。。。
诡辩题:eval 中代码的作用域,在 strict 中呢
with 方法中使用的变量不在入参对象上,会挂到全局上去。。。严格模式禁用 with
将内部函数传递到词法作用域之外,这个函数会一直持有 在原作用域中使用的变量,即为闭包。
foreach(fn, thisArg) 居然有第二个参数,用来改变 fn 中 this 的指向
关于声明提升(变量提升),即在变量声明在使用处之后(编译器创建了变量,但引擎还没有为它赋值),对于函数会连带着函数体一起提升。
new 相关
关于 this,new 的优先级比 bind call 高
bind 接受第 2+个参数,2+参数会传传递到具体方法里
function foo(a, b) {
console.log(a, b);
}
var emptyThis = Object.create(null);
var bar = foo.bind(emptyThis, 2);
bar(3); // 2, 3
-
assign 和 create 的区别?
assign 为合并,没有改变链指向(
__proto__
指向不变)create 创建一个新对象(newobj.
__proto__
=== obj) -
create 和 new 的区别?
// 只有函数有不可枚举的prototype function Foo(a) { console.log(a); } var linkFoo = Object.create(Foo); var foo = new Foo(); // create创建一个新对象,其链和原对象相同 linkFoo.__proto__ === Foo linkFoo.prototype === Foo.prototype; // new生成一个新实例 foo.__proto__ === Foo; // false foo.__proto__ === Foo.prototype;
defineProperty
configurable: false
不可再改 defineProperty 下的 3 个属性,或删除这个变量,但可以把可写状态改为 falseObject.seal
不可扩展 不可配置Object.freeze
不可扩展 不可配置 不可改变for in
、Object.keys
访问可枚举,hasOwnProperty\getOwnPropertyNames
访问所有属性,keyname in obj
包含链上的属性
原型链
正常情况下Foo.prototype.constructor === Foo
,且new Foo().constructor === Foo
。
但是,当为 Foo.prototype 重新赋值( = {...} ),Foo.prototype.constructor === Object
且 new Foo().constructor === Object
,但此前的实例.constructor === Foo,一切看起来那么合理又怪异。so, DO NOT USE foo.constructor === {...}
-
Object.create 与 Object.setPrototypeOf 的区别?
语法:
Object.setPrototypeOf(obj, prototype)
,Object.create(proto,[props])
(props 同 defineProperty 的第二个参数);兼容性:Object.create 兼容性更好(不兼容 ie6-8、Chrome4,setPrototypeOf 不兼容 ie6-10、Chrome4-33);
性能:setPrototypeOf 有性能警告,MDN 建议使用 Object.create;
Object.getPrototypeOf(obj)
:返回 obj 的原型的对象。 -
instanceof 与 isPrototypeOf 的区别?
语法:
obj1.prototype.isPrototypeOf(obj2)
,实例 instanceof constructor
(Foo.prototype.constructor === Foo)兼容性:isPrototypeOf 不支持 ie6-8
instanceof 常用于判断实例是否属于某个函数(类);isPrototypeOf 单纯判断 obj2 是否存在于 obj1 上的原型链上。两者功能类似,只是调用方式不同
js 中没有真实的 class,继而没有真实的继承,js 中的继承应该称为连接
迭代器模式、工厂、观察者、单例????
Es5 的继承?????
类型
typeof 可以防止出现变量未定义错误
typeof not_defined === "undefined"
关于字符数组和字符串:数组的变更多在自己身上(push、splice 等),字符串则是不变的,字符串操作都是生成一个新的值返回,覆盖在原变量上,所以在借用数组方法时只能借用“非变更”方法(join、slice 等,Array.prototype.slice.call(str))
-
关于 Array,
new Array(item, [item ...])
只有一个参数时,此参数为数组长度,数组内都是空单元数组,最好不要使用空单元数组 因为各遍历方法对其反应不一致。var arr = new Array(3); arr; // 不是[3],而是 [empty × 3],这个值的表现在不同浏览器还不一样 arr.map(...) // 依然是[empty × 3],因为map没有值可遍历 arr.join(',') // ',,' 据说在firefox中会有3个',' 所以最好别这么用! <!-- es6 --> Array.from(arr[, cb]) // 转为标准数组、空单元转为undefined,有cb时作用与map相同
为对象定义 toJSON 方法,在 JSON.stringify 时会取 toJSON 的值进行字符串化
~ 返回 2 的补码( ~x == -(x+1); ~str.indexOf(substr) == true or false)
-
arguments 的抽象泄漏,具名参数不要和 arguments 数组共用,因为 arguments 并不总链接具名参数;且在严格模式下它永不关联
function foo(a) { a = 3; console.log(a, arguments[0]); } foo(); // 3, undefined <!-- dddd -->
诡辩题:NaN != NaN
诡辩题:方法的参数也是用引用方式传递的
```js
function foo (a) {
a.push(4)
a = [6, 7, 8]
a.push(9)
}
var arr = [1, 2, 3]
foo(arr)
console.log(arr) //1234
```
诡辩题:
var a = 1, b = (a++); // a2 b1
诡辩题:
function foo(x = x + 1) {...}
不管外部有没有定义过 x,此方法都会报引用错误,它会先在()中进行查找,找不到才向外找
诡辩题:不引入第三个变量,交换两个变量的值
诡辩题:解构链
var json2 = { a } = json
其中json = { a:1, b:2 }
,json2 的值是最右侧的
强制类型转换
false == "0";
false == 0;
false == "";
false == []; // [].toString() => ""
"" == 0;
"" == [];
[] == 0;
2 == [2]; // [].toString() => "2"
"" == [null]; // [null].toString() => ""
0 == "\n";
[] == ![]; // ![] => 布尔强制转换 => [] == false
有一个 number -> number
有一个 boolean -> number
null == undefined
有一个 object -> 调用 object.valueOf()进行比较,没有则调用 object.toString();
当有一边是 boolean 时最好不要使用==
诡辩题:3=与 2=的区别,允许隐式类型转换
异步
张三是一名冷酷无情的程序员,对于公司安排给他的工作,他会按顺序一件件写在便签上在显示器旁(事件循环队列),并按照这个顺序执行。当需要其它部门协助时(比如后端或 UED)张三通知它们,并马上做便签上下一件工作。当其它部门给出反馈(接口或效果图),张三会贴显示器旁最下方。任何人都不能打乱这个顺序,在张三这里不存在什么优先级、紧急工作,因为张三是一名冷酷无情的程序员!
事件循环队列、任务队列
关于回调地狱,嵌套和缩进虽然会让代码的可读性和可维护性变差,但这只是细枝末节,其地狱的地方在信任链断裂(控制反转):在使用第三方代码(的回调方法)时,为了保证逻辑严禁性需要做很多 latch(比如第三方是否按预期调用了回调方法,在不调用、多次、过早、过晚调用,缺少环境参数、吞异常等情况),且每个回调方法都要做。简单来讲:
- 可读性和可维护性:嵌套和缩进
- 控制反转
Promise
诡辩题:识别 Promise 实例,即 thenable => p 是对象或方法, 且 p.then 是方法
如果 resolve 的是另一个 Promise(innerP),则 innerP 会在 resolve 的位置异步展开,最后外层 Promise 返回是 innerP 的结果。reject 不会像 resolve 一样进行展开
Promise 链式 catch 相比 then 的第 2 个参数,链式 catch 可以捕获 then 第一个回调中的错误
Promise.resolve 用于提供安全的 Promise,比如简单值(封装为 P)、真实 Promise(返回 P 本身)、第三方 thenable 对象(封装为安全的 P)。
原文:Promise.resolve(..)可以接受任何 thenable,将其解封为它的非 thenable 值。从 Promise. resolve(..)得到的是一个真正的 Promise,是一个可以信任的值。如果你传入的已经是真正的 Promise,那么你得到的就是它本身,所以通过 Promise.resolve(..)过滤来获得可信任性完全没有坏处。
defer:拦截错误,在错误处理注册之前
Generator
var it = *foo()
返回的是一个迭代器,it
控制生成器实例。
同行内的 yield 表达式,代码从左到右执行,用到的变量使用是其值,不是引用。
it.return(rval)
会立即终止生成器,且返回 { value: rval, done: true }
it.throw(err)
向生成器内部抛异常,如果生成器内部没有处理异常,该句执行的位置可以 catch 到
自己实现迭代器:
需要有 [Symbol.iterator] 方法,通常
obj[Symbol.iterator] = function () { return this }
需要有 next 方法,返回值为
{ value: any, done: bool }
,根据被调用次数返回不同的值
function BeIterable(obj) {
const keys = Object.keys(obj);
let keysClone = [...keys];
obj[Symbol.iterator] = function () {
return this;
};
obj.next = function () {
if (keysClone.length) {
const key = keysClone.shift();
return { value: this[key], done: false };
} else {
keysClone = [...keys];
return { value: "", done: true };
}
};
}
var json = { a: 3, b: "fdsa" };
BeIterable(json);
for (var v of json) console.log("V:", v);
for (var v of json) console.log("V:", v);
Generator + Promise 模仿 async await:
function autoRun(gen) {
var it = gen(); // 拿到迭代器
return new Promise((resolve, reject) => {
// 自动(递归)执行next
var autoNext = (nextVal) => {
if (nextVal.done === true) return resolve(nextVal.value);
Promise.resolve(nextVal.value).then((res) => {
nextVal = it.next(res);
autoNext(nextVal);
}, reject);
};
try {
autoNext({ done: false });
} catch (e) {
reject(e);
}
});
}
书上给的例子要更复杂,这是一个简单实现版本,如下使用:
var axios = () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(33);
}, 1000);
});
function* main() {
var res = yield axios();
var res2 = res + (yield axios());
console.log("gen fn inner", res);
return res2;
}
// autoRun返回一个promise,决议成功的值为main的return
autoRun(main);
// gen fn inner 66
生成器委托
yield *foo()
表示 foo 的控制权交由当前生成器。当前迭代器暂停,开始执行 foo 的迭代,foo 完成后再恢复。
function* foo() {
console.log("foo:", yield 2);
return 3;
}
function* bar() {
console.log("bar:", yield 1);
// 小心不要少执行一步,可以把yield当做分成单独一行:
// var x = yield *foo(); console...
console.log("bar:", yield* foo());
console.log("bar:", yield 4);
}
var it = bar();
console.log(it.next());
console.log(it.next(10));
console.log(it.next(20));
console.log(it.next(30));
// bar: 10
// foo: 20
// bar: 3
// bar: 30
关于 Symbol
官方定义为,创建一个全局唯一标识符。它不是一个构造器(不可 new),作为属性时不可枚举、不可遍历。使用方法Symbol('desc')
、Symbol.for('desc')
。区别为 for 是全局符号注册,无则创建有则返回;第一种每次都会创建一个新的对象:
Symbol("desc") == Symbol("desc"); // false
Symbol.for("desc") === Symbol.for("desc"); // true
论唯一性的作用,普通全局变量名也能实现。如果担心普通变量名会冲突,Symbol('desc')
倒是不冲突,但多个同 desc 的符号对开发者并不友好,Symbol.for('desc')
本质上又是以 desc 为唯一键的表。所以,我认为
创建伪私有变量。仅内部使用,依赖开发者不要创建重名 desc
关于全局唯一标识符,不感觉它有多优势,最多算是一种代码风格
var sym = Symbol("desc");
sym instanceof Symbol; // false
Symbol.keyFor(sys); // desc
sys.toString(); // Symbol(desc)
var json = {};
json[sym] = 55;
Object.keys(json); // []
Object.getOwnPropertySymbols(json); // [Symbol(api)]
系统内置 symbol:
@@iterator
@@toStringTag: 修改 toString()的返回值
@@hasInstance: 配合 defineProperty 修改 instanceof 的比较方法
@@species: 修改(类的)实例指向的构造器
ES6
CommonJS(Node.js):使用 require 引入,module.exports 导出。
AMD(require.js):Asynchronous Module Definition 基于浏览器的特性,所以模块必须异步加载,且有时对加载的顺序还有要求。引入使用
require(['moduleA', 'moduleB'], function(objA, objB) { ... 具体逻辑在回调函数里,当需要的模块加载完成后,会主动回调此函数 ... })
CMD(seajs):Common Module Definition,将 require, exports, module 作为 define 的回调函数的参数,所有业务写在 define 方法里,引入使用
define(function(require, exports, module) { var clock = require('clock'); });
UMD:Universal Module Definition,AMD 和 CommonJS 的糅合,它先判断是否支持 node.js,不支持使用 amd
ES6 模块:以文件为单位的单例实例,且在编译时静态决议。官方不建议 export 中写具体实现,会导致引擎无法静态分析,即无法对静态 import 进行性能调优。
类
类方法是不可枚举的,而对象方法默认是可枚举的。
function Foo 是“提升的”,而 class Foo 并不是;且全局作用域下的 class 不会像 function 一样挂在 window 上。
父类构造器初始化了实例的 this,所以子类构造器中调用 super(..)之后才能访问 this。
(在且仅在构造器中) new.target 总是指向 new 关键字后面那个构造器。
class B extends A { constructor() { new.target // B } }
Map、Set
- Map 键值都可以是复杂值;拥有
set
get
has
delete
clear
keys
values
方法size
属性。
// 构造器接受一个二维数组
new Map([
[a, 1],
["foo", 44],
]);
WeakMap 键必须是复杂值;(不可暴露键值、迭代)没有
clear
keys
values
方法size
属性;键对象即使还在用也可被 GC。Set 唯一性集合,唯一性使用===判断;拥有的方法和属性与 Map 基本相同 set 变为
add
、没有get
,不是数组无法用下标取值。
set.keys() 、set.values()、[...set] 都一样
- WeakSet 值对象即使还在用也可被 GC,即当它是对象的最后一个引用时,GC 可以回收对象。
代理
var obj = { a: 2 };
var handler = {
get(target, key, ctx) {
console.log("access", key);
return Reflect.get(target, key, ctx);
},
};
var proxy = new Proxy(obj, handler);
console.log(obj.a, proxy.a);
除了 get set 还可以对 prototype 和 defineProperty 等代理,几乎可以代理一切了。且代理可以取消(通过 Proxy.revocable)。
代理可以放在对象的链上,程序直接与对象交互,代理做为对象身后的一个保险(代理在后 proxy last)
关于 Reflect 是像 Math 一样的对象,不是函数没构造器。属性有 get set has apply 等
strict 的优势:尾递归调用,一个方法的最后一件事为调用另一个函数,不再为另一个分配栈帧。(递归不再有层数限制?)
不重要,一个使用 Generator 实现类似 promise.all 的方法
// 需要genFns步骤相同
// function runAll(genFns) {
// var results = [];
// var its = genFns.map((gen) => gen());
// var vals = its.map((it) => it.next());
// return new Promise((resolve, reject) => {
// var autoNext = () => {
// let counter = its.length;
// for (let index = 0; index < its.length; index++) {
// const it = its[index];
// const { done, value } = vals[index];
// if (done) {
// results.push(value);
// if (results.length === its.length) resolve(results);
// continue;
// }
// Promise.resolve(value).then((res) => {
// vals[index] = it.next(res);
// counter--;
// if (!counter) autoNext();
// }, reject);
// }
// };
// try {
// autoNext();
// } catch (e) {
// reject(e);
// }
// });
// }
// 通过 yield "take control"; 交出控制权,对齐异步步骤
function runAll(genFns) {
var its = genFns.map((gen) => gen());
var vals = its.map((it) => it.next());
/** 自动(递归)执行next */
var autoNextOne = (nextVal, it) => {
const { value, done } = nextVal;
// 遇到"take control"和结束,停止自动递归
if (value === "take control") return Promise.resolve({ value: null, done });
if (done === true) return Promise.resolve(nextVal);
return Promise.resolve(nextVal.value).then((res) => {
nextVal = it.next(res);
return autoNextOne(nextVal, it);
});
};
return new Promise((resolve, reject) => {
var results = [];
var autoNext = () => {
let counter = its.length;
for (let index = 0; index < its.length; index++) {
// 执行该生成器的自迭代
autoNextOne(vals[index], its[index]).then((res) => {
vals[index] = res;
// 该生成器首次done
if (res.done && results.length === index) results.push(res.value);
// 所有生成器done -> resolve
if (its.length === results.length) return resolve(results);
// 所有 its 都执行过了,开始执行下轮。强制异步对齐
// 执行到一半的生成器们可以在此处通信
if (--counter === 0) autoNext();
}, reject);
}
};
try {
autoNext();
} catch (e) {
reject(e);
}
});
}
function* q1() {
var res = yield new Promise((reslove) => setTimeout(() => reslove("A"), 900));
yield 44;
yield 44;
yield 44;
yield 44;
yield "take control";
return res;
}
function* q2() {
var res = yield new Promise((reslove) => setTimeout(() => reslove("B"), 500));
yield "take control";
return res;
}
runAll([q1, q2]).then((arr) => console.log(arr));