最近我看了 You-Dont-Know-JS 的两个小册,在看书的过程当中,为了方便之后索引与更深刻的了解,也为了不遗忘,我对每一册的较为复杂的点作了总结,编辑以下。javascript
本文地址: blog.xiange.tech/post/js-puz…java
判断如下结果git
var s = 'abc';
s[1] = 'B';
console.log(s);
var l = new String('abc');
l[1] = 'B';
console.log(l);
复制代码
string
及其包装对象 (Boxed Object) 是不可变 (immutable) 类型,所以不能改变它自己(modify in place),因此String
的全部方法都是返回一个新的字符串,而不会改变自身。github
如何逆序一个字符串?数组
s.split('').reverse().join('')
浏览器
接上,为何不能直接使用 Array.prototype.reverse.call(s)
逆序字符串?app
当一个数组逆序时
l.reverse()
会改变 l 自己。正如第一题,string
不能改变自身。函数
判断如下结果,为何会出现这样的状况,如何作出正确的比较?post
0.1 + 0.2 === 0.3;
0.8 - 0.6 === 0.2;
复制代码
浮点数根据 IEEE 754 标准存储64 bit 双精度,可以表示 2^64 个数,而浮点数是无穷的,表明有些浮点数必会有精度的损失,0.1,0.2 表示为二进制会有精度的损失。比较时引入一个很小的数值
Number.EPSILON
容忍偏差,其值为2^-52
。uifunction equal (a, b) { return Math.abs(a - b) < Number.EPSILON } 复制代码
如何判断一个数值为整数?
// ES6
Number.isInteger(num);
// ES5
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}
复制代码
如何判断一个数值为 +0?
function isPosZero (n) {
return n === 0 && 1 / n === Infinity
}
复制代码
'abc'.toUpperCase()
中 'abc' 做为 primitive value,如何访问 toUpperCase
方法
当
primitive value
访问属性或者方法时,会自动转化为它的包装对象。另外也可使用Object.prototype.valueOf()
解包装(Unboxing)。
判断如下结果 (Boxing Wrappers)
function foo() {
console.log(this)
}
foo.call(3);
复制代码
Number(3)。理由如上。
判断如下结果
Array.isArray(Array.prototype)
复制代码
true 内置对象的 prototype 都不是纯对象,好比
Date.prototype
是 Date,Set.prototype
是 Set。
判断如下结果
Boolean(new Boolean(false));
Boolean(document.all);
[] == '';
[3] == 3;
[] == false;
42 == true;
复制代码
new Boolean() 返回 object,为 true document.all,历史问题,参考这里 Falsy value 指会被强制转化为 false 的值,有如下五种。除此以外所有会转化为 true
- undefined
- null
- false
- +0, -0, and NaN
- ""
找出如下代码问题 (TDZ)
var a = 3;
let a;
复制代码
这是暂时性死域(Temporal Dead Zone)的问题,let a 声明以前,不能使用 a。
找出如下代码问题 (TDZ)
var x = 3;
function foo (x=x) {
// ..
}
foo()
复制代码
一样,在函数默认参数中,也有 TDZ。
var a = 2
中,Engine
,Scope
,Compiler
作了什么工做
判断如下结果 (Lexical Scope)
var scope = 'global scope';
function checkScope () {
var scope = 'local scope';
function f() {
return scope;
}
return f;
}
checkScope()();
复制代码
'local scope'
因为 js 为词法做用域(Lexical Scope),访问某个变量时,先在当前做用域中查找,若是查找不到则在嵌套做用域中查找,直到找到。若是找不到,则报
ReferenceError
。
判断如下结果 (Hoisting)
console.log(a);
var a = 3;
复制代码
undefined
以上代码会被编译器理解为
var a; console.log(a); a = 3; 复制代码
判断如下结果 (Function First)
var foo = 1;
function foo () {
}
console.log(foo);
复制代码
1。函数也会有提高,因此会被赋值覆盖。
判断如下结果 (IIFE & Function First)
var foo = 1;
(function () {
foo = 2;
function foo () {}
console.log(foo);
})()
console.log(foo);
复制代码
2,1
以上代码会被编译器理解为以下形式
var foo = 1; (function () { var foo; function foo () { } foo = 2; console.log(foo); })() console.log(foo); 复制代码
判断如下结果,如何按序输出 (Closure)
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);
}, 1000)
}
复制代码
大约 1s 以后连续输出 10 个 10。由于没有块级做用域,能够把 var 改为 let,也能够给 setTimeout 包装一层 IIFE。
注意:如下均为浏览器环境中
判断如下结果 (Default Binding)
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo();
复制代码
会报错,在函数的严格模式下,默认绑定其中的 this 指向 undefined。
判断如下结果
"use strict";
var a = 2;
let b = 3;
console.log(this.a, this.b);
复制代码
2, undefined
在浏览器环境中 this 指向 window,而 var 声明的变量会被挂在 window 上。而 let 声明的变量不会挂在 window 上。
判断如下结果 (Strict Mode & Default Binding)
function foo() {
console.log( this.a );
}
var a = 2;
(function(){
"use strict";
foo();
})();
复制代码
2
只有存在 this 的函数中设置严格模式,this 为 undefined。所以会正常输出。
判断如下结果 (Hard Binding)
function foo () {
console.log(this.a);
}
const o1 = { a: 3 };
const o2 = { a: 4 };
foo.bind(o1).bind(o2)();
复制代码
3
bind 为硬绑定,第一次绑定后 this 没法再次绑定。
如何实现 Function.prototype.bind
与 Function.prototype.softBind
bind 为硬绑定,softBind 能够屡次绑定 this。大体实现代码以下。bind 第二个参数能够预设函数参数,因此,bind 也是一个偏函数。另外,bind 也须要考虑
new
的状况。但如下示例主要集中在硬绑定和软绑定的差别之上。Function.prototype.fakeBind = function (obj) { var self = this; return function () { self.call(obj); } } Function.prototype.softBind = function(obj) { var self = this; return function () { self.call(this === window? obj : this); } }; 复制代码
new
的过程当中发生了什么,判断如下结果 (new)
function F () {
this.a = 3;
return {
a: 4;
}
}
const f = new F();
console.log(f.a);
复制代码
4
new
的过程大体为以下几个步骤
- 建立一个新的对象
- this 指向实例,而且执行函数
- 若是没有显式返回,则默认返回这个实例
由于函数最后显式返回了一个对象,因此打印为 4
什么是 data descriptor
和 accessor descriptor
二者均经过
Object.defineProperty()
定义,有两个公有的键值
- configurable 设置该键是否能够删除
- enumerable 设置是否可被遍历
数据描述符有如下键值
- writable 该键是否能够更改
- value
访问器描述符有如下键值
- set
- get 另外,也能够经过字面量的形式表示访问器描述符
const obj = { get a() {}, set a(val) {} } 复制代码
Vue中
computed
的内部原理即是get
,而watch
的内部原理是set
如何访问一个对象的属性? ([[Get]])
访问对象的属性会触发 [[Get]] 操做,大体简述以下
- 是否被 Proxy 拦截,若是拦截,查看拦截器的返回值,若是没拦截,继续下一步
- 检查自身属性,若是没找到则继续下一步
- 若是没被找到,则在原型链上查找,若是没找到,则返回 undefined
查找过程与 Scope 查找变量很类似,只不过,对象属性找不到,返回 undefined,而变量找不到报 Reference Error。
如何对一个对象的属性赋值 ([[Put]])
对一个对象的属性赋值会触发 [[Put]] 操做,大体简述以下
- 检查是否被 Proxy 拦截
- 若是该对象属性为自身属性 (obj.hasOwnProperty('a') === true)
- 若是属性是访问描述符,则调用 setter 函数
- 若是属性是 data descriptor,则检查 writable 是否可写
- 普通属性,直接赋值
- 若是该对象属性存在于原型链上
- 若是属性是访问描述符,则调用 setter 函数
- 若是属性是 data descriptor,则检查 writable 是否可写。若是可写,被自身属性覆盖,不然在严格模式下将会报错
- 普通属性,被自身属性覆盖
- 若是该对象不存在与原型链上,直接给自身属性赋值
如何遍历一个对象 ($$iterator)
给对象设置 Symbol.iterator 属性
如何实现一个继承 (Object.create & call)
在 ES6 时代能够简单的经过 class & extends 实现继承,ES5 时代用以下方法
function A () {} function B () { A.call(this) } B.prototype = Object.create(A.prototype) // B.prototype = new A() 不推荐 复制代码
如何实现 Object.create
至于为何在继承的时候不推荐
new
,缘由在于你很难保证 A 是一个纯函数,好比它会有自身属性,有可能操做 DOM 等。如下是一个简单版本的实现,省略了第二个参数。Object.create = function (o) { function F() {} F.prototype = o; return new F(); } 复制代码
关注公众号山月行,记录个人技术成长,欢迎交流