变量和类型

javascript 规定了几种语言类型?

Undefined、Null、Boolean、String、Number、Symbol、Object、Function

简单类型都不是对象
  1. undefined

    表示未定义,任何变量赋值前都是 undefined 类型,值为 undefined (而不是 null)
    undefined 是一个变量,void(0) 返回 undefined

  2. null

    只有一个值,表示空值,是关键字,typeOf(null) 返回 object

  3. String

    字符串 UTF16编码,最大长度 253-1

  4. Number

    typeof(NaN) 和 typeof(Infinity) 都返回 number
    NaN != NaN
    Infinity / Infinity = NaN

  5. Symbol

    表示独一无二的值,是一切非字符串的对象key的集合。
    Symbol(参数):可以接受一个字符串参数,表示对 Symbol 实例的描述。

1
Symbol(1) === Symbol(1)      // false
  1. Object

    为什么给对象添加的方法能用在基本类型上?

1
2
3
4
Symbol.prototype.hello = () => { console.log('hello')}
var a = Symbol('a')
console.log(typeof a) // symbol
a.hello() // hello
运算符提供了装箱操作,会根据基础类型构造一个临时对象,使得基础类型可以调用对象的方法
  1. Function
  • 装箱和拆箱

    在 JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此 Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。

    装箱过程使用 object 函数
    拆箱转换使用 valueOf、toString,如果valueOf 和 toString都不存在,或者没有返回基本类型,则会产生TypeError。

JavaScript对象的底层数据结构是什么?

所有 js 对象都是基于 HeapObject 的类生成的,对象的存储结构:

  1. 头部信息
  2. 属性块,k-v
  3. 元素块

函数 function 类型本身也具有对象化的能力
函数 function 与对象 object 超然的结合能力

数组应该算是线性数据结构,线性数据结构一般有一定的规律,适合进行统一的批量迭代操作等,有点像波。
而对象是离散数据结构,适合描述分散的和个性化的东西,有点像粒子。

JavaScript里的对象到底是波还是粒子?

波粒二象性

javascript 对象是 字典 结构

Symbol类型在实际开发中的应用、可手动实现一个简单的Symbol?

  1. 使用Symbol来作为对象属性名(key)

必须用 对象[key],不能使用 对象.key
Symbol类型的 key 是不能通过 Object.keys() 或者 for…in 来枚举
JSON.stringify(obj) Symbol 类型也会被排除

获取 Symbol定义的对象属性:

Object.getOwnPropertySymbol(obj)
Reflect.ownKeys(obj)

  1. 使用Symbol替代常量

  2. 使用Symbol定义类的私有属性、方法

  3. 可以定义枚举

1
2
3
4
5
let levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
}

Symbol.for(‘foo’):定义使用同一个Symbol

原型和原型链

实现继承的几种方式以及他们的优缺点?

  1. 原型链继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 将子类的原型链指向父类的对象实例

function P(name, id){
this.id = id
this.name = name || 'parent'
this.list = ['p']
}
P.prototype.sayHi = function(){
console.log('Hi')
}
function C(){}

C.prototype = new P()

var c = new C()
console.log(c.name) // parent
c.sayHi() // Hi

优点:继承构造函数的属性,父类构造函数的属性,父类原型的属性
缺点:无法向父类构造函数传参,且原型对象的引用属性是所有实例共享的,即属性没有私有化,原型上属性的改变会作用到所有的实例上。

构造函数继承

实现:子类构造函数使用 call、apply 劫持父类构造函数方法,并传参
原理:call、apply 更改子类函数的作用域,执行父类构造函数,因此子类可以继承父类公有属性

1
2
3
function D(name, id){
P.call(this, name, id)
}

优点:解决原型链继承缺点,可以实现多继承
缺点:

  1. 实例并不是父类的实例,只是子类的实例
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

组合继承

1
2
3
4
5
function E(name, id){
P.call(this, name, id)
}
E.prototype = new P()
E.prototype.constructor = E; // 修复构造函数指向

缺点:执行两次父类的构造函数,消耗内存,子类的构造函数会代替原型上的父类构造函数

原型式继承

1
2
3
4
5
6
7
8
9
10
11
var parent = {
names: ['a']
}

function copy(obj){
function F(){}
F.prototype = obj
return new F()
}

var child = copy(parent)

缺点:共享应用类型

寄生式继承

原理:二次封装原型式继承,并扩展

1
2
3
4
5
6
7
function createObj(obj){
var o = copy(obj)
o.getNames = function(){
return this.names
}
return o
}

寄生组合式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function inheritPrototype(subClass, superClass) {
// 复制一份父类的原型
var p = copy(superClass.prototype);
// 修正构造函数
p.constructor = subClass;
// 设置子类原型
subClass.prototype = p;
}

function Parent(name, id){
this.id = id;
this.name = name;
this.list = ['a'];
this.printName = function(){
console.log(this.name);
}
}
Parent.prototype.sayName = function(){
console.log(this.name);
};
function Child(name, id){
Parent.call(this, name, id);
// Parent.apply(this, arguments);
}
inheritPrototype(Child, Parent);