js 的编译与执行
- 编译阶段:编译器、作用域源代码 -> 静态地词法分析生层词法作用域 -> 语法分析生成AST -> 将AST转换为可执行代码
- 执行:js引擎,作用域执行时,js引擎沿着作用域链进行LHS与RHS,知道全局作用于,若仍未发现,则自动创建该全局变量(非严格模式下)。
js严格模式有何不同?
- 禁止隐式或自动创建全局变量
- eval无法改变所在的词法作用域
- js全局作用域的执行上下文不会默认绑定到全局对象上
- 对对象的只读数据进行写操作时,会报错。而不会静默失败。
- 无法为undefined赋值
使用函数的优势?函数有什么用?
- 隐藏内部实现,实现封装的效果
- 减少命名冲突
- 保证局部作用域中的undefined的真实性
js匿名函数的缺点
- 可读性变差
- 调用栈中无法根据函数名进行追踪debug
- 递归不够方便,只能使用过期的argument.callee(已被弃用)进行引用
js中的块级作用域举例:
- with
- try…catch 的 catch () 作用域中
- let
- const
什么是闭包?
- 当函数能够记住并访问所在的词法作用域时,就产生了闭包。
- 以自执行函数为例,产生闭包并不是一定就使用了闭包。
- 严格来讲,只要存在函数嵌套即会产生闭包。只不过这个闭包可能很快就被销毁。而我们常说的闭包,多为长期占用堆内存的闭包。
使用闭包的场景?
定时器、事件监听器、ajax、跨窗口通信、webworkers等,凡是使用了回调函数的地方,就会使用闭包。
改变this指向的几种方法:
- 对象挂载 p85
- 隐式绑定 p86
- call、apply
- bind 硬绑定
- new 绑定
将数组展开成参数的几种方法:
- apply() p97
function foo(a, b) {console.log("a:" + a + ", b:" + b);} // 我们的 DMZ 空对象var ø = Object.create( null ); // 把数组展开成参数foo.apply( ø, [2, 3] ); // a:2, b:3 // 使用 bind(..) 进行柯里化
- es6 …
实现new关键字
function newKeyword (constructor, ...param) {let obj = {};let proto = Object.create(constructor.prototype);Object.setPrototypeOf(obj, proto); // obj.__proto__ = proto 在浏览器端存在兼容性问题proto.constructor = constructor;constructor.apply(obj, param);return obj;}function Baby (name, age) {this.name = name;this.age = age;this.show = function () {console.log(\'我是小柚子\');}}let baby = newKeyword(Baby, \'pomelo\', 1)
js数字
- 标注为IEEE754
- 使用64位二进制表示,第一位表示符号位,中间11位表示指数位,最后52位表示小数位。所以可以表示为 1.x 乘以2的y次方,可表示的数值范围是 -2的53次方到2的53次方减一
- 数字的位运算使用32位
##布尔转换
- 假值: undefined、null、0, -0, NaN、\’\’
- 隐式转换为假值:new String(\’\’), new Boolean(false), new Number(0)
new String(\'\') && 1
== 与 === 的区别
- == 在比较时允许使用隐式强制转换,而 === 不允许。
x == y
- x或y都不是null 或undefined时, 且 x,y为除了symbol以外的基础类型, 当在隐式转换时会最终转换为 Number
- x或y存在一个为null或undefined时,只有 null == undefined 为 true
- x或y存在一个为对象时, 调用对象的toPrimitive方法(toString、valueOf)
x < y
- x,y 存在一个为数字,则隐式转换为数字
- x, y 都不是数字,则隐式转换为字符串,并按照字符串顺序比较
运算符的优先级
- && > || > ?> :
回调函数的弊端
- 回到地狱,可读性差,调试困难
- 控制翻转,安全性降低,回调函数的调用情况不受控制
promise的问题
- 在链式调用的最后进行catch时候,不太容易对显示reject的某个promise进行追溯。
- 决议的单一值,在传送多值时,通过对象的封装与解封,会带来一部分开销。即使是使用…,即使是用起来优雅。
- 相较回调的解决方案,肯定会有额外的性能开销。
优化浏览器堵塞的几种思路
- web worker
通过时间差来计算js执行性能的局限性?
- js的执行精度可能无法满足,例如老的IE浏览器,精度仅为15ms
- 执行环境的不确定性,例如:是否受其他因素影响
- 多次执行,总时间除以循环次数,这种方式本身就不够科学,即无法保证测试时每次的执行环境与发布环境下均为一致。
- 多次执行时,js引擎本身就会根据变量的使用频率进行优化,因此会导致结果不够科学严谨。
如何测试?需要注意什么?
- 利用第三方测试库 ,如:benchmark.js
- 尽可能保证测试环境的真实性,跨平台实机测试,以及不同的浏览器内核。利用jsPerf,持续搜集性能数据
- 测试代码应尽可能地真实,不同测试用例的相同逻辑应尽可能保持一致
js代码优化的技巧
- 尾调用栈帧优化(尤其是递归),尽可能使用尾调用。
es6 自定义结构赋值
let test = {};let target = {name: \'tate\', age: 18};({name: test.a, age: test.b} = target);console.log(test)
let o = {a: 1,b: 2,c: 3}let d,e,f, tate;tate = {a: d, b:e, c: f} = o;console.log(tate)
标签模板字面量
function tpl (tpl, val) {console.log(tpl, val)}let name = \'tate\';tpl `my name is ${name}`;
什么情况下,可以将普通函数替换成箭头函数?p130
- 函数内部没有进行任何this的调用
- 函数的调用环境始终与函数所在的词法作用域一致
- 函数中使用了arguments 并且,arguments依赖于父层函数
es6 正则新增模式
- u模式, y模式
unicode 编码方式
- utf-8 变长, 使用1-6个字节表示,兼容ascii
- utf-16 既有定长,又有变长,2个字节或四个字节,js使用utf-16编码
- utf-32 固定四个字节
无法被 Proxy 捕获的行为:
- typeof obj
- String(obj)
- obj + ""
- obj == proxyObj
- obj === proxyObj