总结下这段时间吸收的许多小知识,以备忘记后翻阅。
关于面向对象
面向对象特征:
- 具有唯一标识性
- 具有状态
- 具有行为
JS的面向对象和JAVA的实现思路不一样,JS是基于原型并非基于类。但是JS为了看起来更像JAVA,为此添加了一些特性。
抛开为了看起来像JAVA引入的特性,JS面向对象总结起来很简单:每个对象都有原型,对象上找不到属性就去自身原型上找,直到Object.prototype。
在ES3时代为了操作原型我们没有办法只能使用 new
但是ES5之后引入了专门操作原型的方法:
- Object.create()
- Object.setPrototypeOf()
- Object.getPrototypeOf()
这样我们就可以抛开类的概念直接面对原型来实现面向对象。
JS关于模拟JAVA的面向对象部分
在我的理解中有以下部分属于模拟JAVA的行为:
- function 可以被new调用
- 引入this机制
- instanceof 操作符
通过 new 调用函数主要做了三件事:
- 使用被new 调用的函数的prototype属性构造一个新对象
- 将新对象作为函数的this,调用该函数
- 如没有引用类型返回则返回这个新建的对象
上面就是在模拟JAVA中的类,在ES5中抛开new我们可以做到同样的事情,并且不会有让人以为这是一个类。
this更是复杂,一大批文章去解释JS中的this,由此也可以看出有些复杂。
JS中的数据类型
JS一共有7中数据类型:
- Null
- Undefined
- Number
- String
- Boolean
- Symbol (ES6新加)
- Object (引用类型)
7种数据类型又被分为两类:基本类型和引用类型。
怎么判断数据对应的是哪种数据类型呢?
总结有三种:typeof
、instanceof
和 Object.prototype.toString.call()
,这三种都有自己的特点。
typeof
基本上可以直接返回数据对应的类型,有两个例外,一个是 typeof null
返回 "object"
,另一个是 typeof function() {}
返回 "function"
instanceof
左边是一个对象右边是一个构造函数。
instanceof会遍历对象的原型,查找是否有函数的prototype属性。
我认为,检查数据是否是数组就很方便了 [] instanceof Array
,当然这个是有问题的,所以后面又提供了Array.isArray()
方法判断是否是数组。
Object.prototype.toString.call()
该方法提供的返回值十分详细,不仅会返回上面其中类型,还包括很多JS内置对象,例如:Array
,Date
,RegExp
等。
但是这个对象有个问题,会强制装箱。也就是说判断不出是基本类型还是引用类型,如果在意的话需要配合上typeof
。
Object.prototype.toString.call(1); // [object Number]Object.prototype.toString.call(new Number(1)); // [object Number]
返回值都一样。
关于装箱和拆箱
说道装箱和拆箱就要提及一下它的表象 – 隐式类型转换。
JS中隐式类型转换应用广泛,最臭名昭著的就是 ==
。还有当我们直接调用 str.indexOf
方法的时候,str明明是基本值还不报错就是装箱在起作用。
装箱
每个基本值都有一个对应的对象类,装箱就是将基本值转换为对应的对象类型。但是Symbol是不能通过new 调用的,所以我们要另想办法。
上面提到的Object.prototype.toString.call
会强制装箱:
function a() {return this;
}
a.call(Symbol()) // 查看打印
拆箱(toPrimitive)
拆箱会先调用对象的valueOf
方法,如果返回值是非基本值再调用toString
方法,返回值非基本值则报错。
还记得偏门的面试题么?
if (a == 2 && a == 3) {console.log(true);}
虽然是语言不好的一面,但也可以了解下。这里就用到了拆箱知识。
如果 a 是对象和基本类型做 ==
比较首先会对 a 拆箱,拆箱步骤如上所述。
let b = 1;
let a = {valueOf: () => ++b
}if (a == 2 && a == 3) {console.log(true);
}
这样就会打印出 true
。
自定义拆箱
关于拆箱最后想说的是现在可以自定义这个行为了。
let o = {[Symbol.toPrimitive]: () => "hello world"
};console.log('' + o);
Number
关于Number一直有一个疑问就是NaN到底是不是数字。typeof NaN
其实返回的是 number 。所以Not a Number 是一个 Number。
我们都知道JS中数字有精度问题:0.1 + 0.2 === 0.3
返回的是false。这个时候们就要借助 Number.EPSILON来帮我们判断,Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON
。
JS中是区分 +0 和 -0 的,但是 +0 === -0
返回true。那么怎么区分+0 和 -0 呢,这也是polyfill Object.is() 需要考虑的问题。
我们可以通过用 +0 和 -0 做分母来区分:
1 / 0 === Infinity // true
1 / -0 === -Infinity // true
1 / 0 === 1 / -0 // false
函数
通过实际开发中我们可以知道,有的函数可以直接调用和使用new
调用,有的函数指能通过new
调用有的函数只能直接调用。
这背后是有机制在控制的。JS中的数据类型大体分为两种,一个是基本类型,一个是引用类型。那么按理说function不是基本类型那就是引用类型,引用类型就是只有对象,函数也是一个对象,为什么函数这个对象可以调用而其他对象不行呢?
是因为函数这个对象具有两个私有属性决定了上面的特性,我们并没有手段去定义和查看这两个私有属性。一个是 [[constructor]]
,另一个是 [[call]]
。分别决定了是否可以通过new调用和直接调用。
注:不能通过new调用函数例如 () => {}
箭头函数,不能直接调用的函数例如Image
这样的环境提供的函数。
总结于即刻时间重学前端栏目,winter说知识是免费的,教育是收费的,这边总结出来的应该算知识,教育还留在了栏目里。