《你不知道的 js》笔记

编译器

var a = 2 需要由 编译器、作用域和引擎 三方参与分 2 步操作:

  1. 编译器在作用域中创建 a
  2. 引擎从作用域中拿到 a,并为它赋值(其引用关系也存到作用域中)

以上属于左引用LHS,在没有 var 时作用域会热心的帮忙创建一个全局的。

console.log(a) 属于右引用 RHS,找不到 a 时会报异常:

  1. 引擎向作用域索要 console RHS
  2. 引擎向作用域索要 a RHS
  3. 引擎向作用域询问 console.log 方法的入参,并把 a 赋值给入参 LHS
  4. 引擎继续执行 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
  1. assign 和 create 的区别?

    assign 为合并,没有改变链指向(__proto__ 指向不变)

    create 创建一个新对象(newobj.__proto__ === obj)

  2. 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 个属性,或删除这个变量,但可以把可写状态改为 false

  • Object.seal 不可扩展 不可配置

  • Object.freeze 不可扩展 不可配置 不可改变

  • for inObject.keys 访问可枚举,hasOwnProperty\getOwnPropertyNames 访问所有属性,keyname in obj 包含链上的属性

原型链

正常情况下Foo.prototype.constructor === Foo,且new Foo().constructor === Foo

但是,当为 Foo.prototype 重新赋值( = {...} ),Foo.prototype.constructor === Objectnew Foo().constructor === Object,但此前的实例.constructor === Foo,一切看起来那么合理又怪异。so, DO NOT USE foo.constructor === {...}

  1. 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 的原型的对象。

  2. instanceof 与 isPrototypeOf 的区别?

    语法:obj1.prototype.isPrototypeOf(obj2)实例 instanceof constructor (Foo.prototype.constructor === Foo)

    兼容性:isPrototypeOf 不支持 ie6-8

    instanceof 常用于判断实例是否属于某个函数(类);isPrototypeOf 单纯判断 obj2 是否存在于 obj1 上的原型链上。两者功能类似,只是调用方式不同

  3. js 中没有真实的 class,继而没有真实的继承,js 中的继承应该称为连接

  4. 迭代器模式、工厂、观察者、单例????

  5. 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
  1. 有一个 number -> number

  2. 有一个 boolean -> number

  3. null == undefined

  4. 有一个 object -> 调用 object.valueOf()进行比较,没有则调用 object.toString();

当有一边是 boolean 时最好不要使用==

诡辩题:3=与 2=的区别,允许隐式类型转换

异步

张三是一名冷酷无情的程序员,对于公司安排给他的工作,他会按顺序一件件写在便签上在显示器旁(事件循环队列),并按照这个顺序执行。当需要其它部门协助时(比如后端或 UED)张三通知它们,并马上做便签上下一件工作。当其它部门给出反馈(接口或效果图),张三会贴显示器旁最下方。任何人都不能打乱这个顺序,在张三这里不存在什么优先级、紧急工作,因为张三是一名冷酷无情的程序员!

事件循环队列、任务队列

关于回调地狱,嵌套和缩进虽然会让代码的可读性和可维护性变差,但这只是细枝末节,其地狱的地方在信任链断裂(控制反转):在使用第三方代码(的回调方法)时,为了保证逻辑严禁性需要做很多 latch(比如第三方是否按预期调用了回调方法,在不调用、多次、过早、过晚调用,缺少环境参数、吞异常等情况),且每个回调方法都要做。简单来讲:

  1. 可读性和可维护性:嵌套和缩进
  2. 控制反转

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 到

自己实现迭代器:

  1. 需要有 [Symbol.iterator] 方法,通常 obj[Symbol.iterator] = function () { return this }

  2. 需要有 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));
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容