JavaScript体系结构梳理(面试向)

638 阅读6分钟

本文首发于本人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
  • 循环引用(环)
  1. JSON.parse(JSON.stringify(obj)) 这种方式可以满足大多数场景的需求
  2. 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

注意事项:

  1. default是指某些特殊情况(如+,关系运算时,无法确定转换为string还是number时,规则和number一样,不需要特殊记忆
  2. Array, Object等对象类型valueOf返回的是本身
  3. Array, Object, Date等对象类型toString有自定义的规则,例如[].toString()===''
{}==!{}//的转换过程如下
{}==false//1.
'[object Objct]'==0//2
NaN==0//3.
特殊的数学运算:关系和+

关系运算可以通过以小(弱等)见大(若不等, >, <等)的方法推导

弱等(==)
  • 运算数二者为null和undefined时,不进行类型转换,无条件返回true
  • 运算数其一为NaN时,不进行类型转换,无条件返回false
  • 运算数其一为布尔型,不管三七二十一,先转换成数字
  • 自动类型转换的终点是两边都为stringnumber; 若运算数两者都有,则会将一方转换为数字类型
+
  • 作为一元运算符时会将运算数转换为数字
  • 作为二元运算符时,若有任意运算数为字符串时,+会作为字符串拼接符
  • 对象类型中的Date会先调用toString而不是valueOf
+"hello"//NaN
+[]//NaN
new Date+1//结果为string类型

原型和原型链

关于原型你需要知道的

  1. 对象是可扩展的,即可自由添加属性和方法
  2. 所有对象都有__proto__属性,属性值为一个普通对象,即其'原型'对象
  3. 所有函数都有prototype属性,属性值仍然是一个普通对象
  4. 函数也是对象
  5. 所有函数(包括构造函数)的__proto__指向对象Function.prototype
  6. 对象的__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)

继承

继承的核心就是将子类构造函数的原型对象设为父类的实例

注意事项:

  1. 原型对象应该有construtor属性,该属性值应指向构造函数,且该属性不可枚举
  2. 如果父类中属性值为对象类型时,子类实例对该属性的改变将会影响父类实例,这个问题可以用"借用构造函数"的方式解决
  3. 为了复用父对象方法,应该在父构造函数的原型对象上添加方法

示例

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...

参考资料

book.douban.com/subject/105…

javascript.info/object-topr…

github.com/lmxyjy/java…

juejin.cn/book/684473…