本文首发于本人github上,以梳理本人JavaScript知识体系以及应对未来的工作面试。很多解决方案非原创,参考了很多博客和资料。因时间关系,尽管参考资料做过筛选,涉及内容也经过自己的部分实验验证,内容优先保证容易理解,若出现不严谨的地方,请大家斧正。内容将会持续更新中...
类型
值类型 vs. 引用类型
1. 区别
三点:存储位置,访问机制,变量间赋值不同。
前者:栈区,变量访问的是值本身,在栈区复制一份。
后者:堆区(+栈区),变量访问的是其堆区的引用对象,复制的是栈区的地址。
2. 类型判断
两个关键字+构造函数
typeof可正确返回除null以外的值类型以及函数类型,typeof null === 'object'
instanceof可判否所有的引用类型,注意[] instanceof Array === true且[] instanceof Object === true
constructor对象的原型对象都有属性constructor指向其构造函数,因此也可用来判否引用类型注:上面的方式都有其缺陷,借用
Object.prototype.toString可以判断任意类型
3. 深浅拷贝
深浅拷贝的概念是相对于引用类型存在的。
数组的浅拷贝方法:
newArr=arr.concat() //1
newArr=arr.slice() //2
数字及对象的浅拷贝方法
function shallowCopy(obj){
var newObj=Array.isArray(obj)?[]:{};
for(var attr in obj){
if(obj.hasOwnProperty(attr)){
newObj[attr]=obj[attr];
}
}
return newObj;
}
深拷贝方法:
深拷贝可能需要填的坑
- Symbol属性
- 属性值为undefined
- 属性为方法
- 特殊引用类型Date/RegExp
- 循环引用(环)
JSON.parse(JSON.stringify(obj))这种方式可以满足大多数场景的需求- for...in+递归
粗糙版
function isObject(obj){
return typeof obj==='object'&&obj!==null;
}
function deepClone(obj){
var newObj=Array.isArray(obj)?[]:{};
for(var key in obj){
if(obj.hasOwnProperty(key)){
newObj[key]=isObject(obj[key])?deepClone(obj[key]):obj[key];
}
}
return newObj;
}
上面这种方法的缺陷可见这篇掘金文章的分析
精致版(下面这种方法可以解决除Symbol属性外的坑)
function cloneDeep(obj, hash = new WeakMap()) {
// 1 处理null或undefined
if (obj == null) return obj;
// 2 处理特殊类型
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 3 处理原始类型和函数类型
// 由于操作函数的行为一般只是执行,不涉及到修改,故返回即可
if (typeof obj !== "object") return obj;
// 4 处理循环引用
if (hash.has(obj)) return hash.get(obj);
// 5 处理对象和数组
// 使用弱引用WeakMap以对象作为key,将值存起来
const result = new obj.constructor();
// 循环引用
hash.set(obj, result);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = cloneDeep(obj[key], hash);
}
}
return result;
}
4. 隐式类型转换
JS有6种值类型,null, undefined, symbol比较特殊,“正常”的也就3种number,string,boolean,而类型转换的目标也就这三种类型。
注意:number类型有1种特殊值NaN
隐式类型转换的3个场景分别对应3种转换目标boolean, number, string:
if/!等逻辑判断- 数学运算(包括弱等
==,算术以及关系运算等) - 输出
2种源类型:值类型和对象类型。
值类型的转换都有固定的规则,比较简单只列举特殊的几个:
转换为数字时:
- undefined → NaN
- null → 0
- ' ' → 0
对象类型的转换规则
对象类型的转换往往也涉及值类型的转换,需要根据实际情况结合使用。
所有的对象转换为布尔型都为true,所以需要讨论的情况是目标为number以及string。
根据场景的不同,对象类型转换时所谓的hint也不同
hint='string'时,方法不存在或者返回值不为原始类型时,依次调用对象的以下方法:
[Symbol.toPrimitive]→toString→valueOf→throw Error
hint='number'和hint='default'
[Symbol.toPrimitive]→valueOf→toString→throw Error
注意事项:
- default是指某些特殊情况(如
+,关系运算时,无法确定转换为string还是number时,规则和number一样,不需要特殊记忆 - Array, Object等对象类型valueOf返回的是本身
- Array, Object, Date等对象类型toString有自定义的规则,例如
[].toString()===''
{}==!{}//的转换过程如下
{}==false//1.
'[object Objct]'==0//2
NaN==0//3.
特殊的数学运算:关系和+
关系运算可以通过以小(弱等)见大(若不等, >, <等)的方法推导
弱等(==)
- 运算数二者为null和undefined时,不进行类型转换,无条件返回true
- 运算数其一为NaN时,不进行类型转换,无条件返回false
- 运算数其一为布尔型,不管三七二十一,先转换成数字
- 自动类型转换的终点是两边都为
string或number; 若运算数两者都有,则会将一方转换为数字类型
+
- 作为一元运算符时会将运算数转换为数字
- 作为二元运算符时,若有任意运算数为字符串时,
+会作为字符串拼接符 - 对象类型中的Date会先调用
toString而不是valueOf
+"hello"//NaN
+[]//NaN
new Date+1//结果为string类型
原型和原型链
关于原型你需要知道的
- 对象是可扩展的,即可自由添加属性和方法
- 所有对象都有
__proto__属性,属性值为一个普通对象,即其'原型'对象 - 所有函数都有
prototype属性,属性值仍然是一个普通对象 - 函数也是对象
- 所有函数(包括构造函数)的
__proto__指向对象Function.prototype - 对象的
__proto__对象指向其构造函数的prototype对象
Function.__proto__===Function.prototype;//true
Object.__proto__===Function.prototype//true
Function.prototype===Object.prototype;//因为Function.prototype也是普通对象,普通对象的构造函数即Object 第六条
所谓原型链即访问某属性和方法时,从对象本身查找,如没找到则沿着__proto__逐层查找,直到找到终点Object.prototype(Object.prototype.__proto__=null)。
继承
继承的核心就是将子类构造函数的原型对象设为父类的实例。
注意事项:
- 原型对象应该有construtor属性,该属性值应指向构造函数,且该属性不可枚举
- 如果父类中属性值为对象类型时,子类实例对该属性的改变将会影响父类实例,这个问题可以用"借用构造函数"的方式解决
- 为了复用父对象方法,应该在父构造函数的原型对象上添加方法
示例
function Animal(type){
this.type=type;
this.sound="hello";
this.color=['red','green','blue'];
}
Animal.prototype.sayHello=function(){
console.log(`${this.type} say ${this.sound}`);
}
function Cat(type, sound){
Animal.call(this, type);
this.sound=sound;
}
Cat.prototype=new Animal();
Object.defineProperty(Cat.prototype, 'constructor', {
value: Cat,
enumerable: false
});
测试用例
var cat=new Cat("波斯","meow");
cat.sayHello(); // 波斯 say meow
cat.color.push('cyan'); //修改color属性值
console.log(cat.__proto__.constructor);//[Function Cat]
console.log(cat.color); //[ 'red', 'green', 'blue', 'cyan' ]
var animal=new Animal('any');
console.log(animal.color);//[ 'red', 'green', 'blue']
to be continued...