一、类型
1. MDN上规定最新的ECMAScript标准定义了七种数据类型,分别是:
六种基本类型(BUNNSS)
Boolean
Undefined
Null
Number
String
Symbol
和一个对象类型
Object
2. 谈一谈 Null
我们都知道typeof null
会输出Object
,但严格意义上说,不可以认为null
就是Object
类型。这是JS进化史上的一个bug,在较早版本的js是使用在32位系统中的,出于性能考虑因此使用低位存储变量的类型信息,而000开头代表的是对象,null
又是表示全零的,因此错误地将其判断为Object
类型。
当然null
的内部判断逻辑已经修复。null
代表的是“空指针对象”,它是我们用来初始化一个变量的常用手段,因此是意料之中的“值空缺”。而undefined
是在对象被声明但未初始化的时候出现的,它是我们意料之外的“值空缺”
3. 判断类型
typeof
typeof在判断基本类型时,除null之外都可以正确返回其类型,但在判断Object
类型时,无论是什么对象,最终返回的都会是Object
,那么如果希望判断Object
准确的类型,就需要使用别的手段。
适合于判断基本类型
instanceof
instanceof的内部机制是通过原型链来判断它是否是某一个类型的实例化对象。
它非常适用于判断自定义的类型
Object.prototype.toString
Object.prototype.toString(someVar)
返回值是[object 类型名]。
它适用于判断基本类型和js内置的对象类型,无法判断自定义的类型
4. 类型转换
这里记录一些值得注意的转换规则:
- 各种类型转为Boolean:除了undefined, null, false, NaN, '', 0, -0之外都是true
- 所有的引用类型转为Boolean都是true
- 数组转换字符串:[1,2,3] ==> '1,2,3'
- 数组转换为数字:空数组 ==> 0 ; [1] ==> 1(数组只有一个数字类型的元素时可以转换) ; 其他情况皆为NaN
- null转换为数字:0
- 引用类型、Symbol类型转换为数字:NaN
- 加法运算符:有字符串转字符串,没有字符串转为数字或字符串(先数字,再字符串)
- 其他四则运算符:有一方是数字就会转换为数字
引申: == 和 ===:==通过类型转换来判断二者值是否相等;===直截了当,值、类型二者必须皆相等。
二、ES6
1. const
let
与 var
的不同
在全局使用时,const
,let
不会挂载到window上,而var
会挂载到window上。
另外是const
声明的变量是不可赋值的。
2. 谈一谈变量var
的实例化过程
首先,代码执行的过程大概是这样的:
第一步:进入上下文。在这一步中首先初始化this、作用链域、变量对象,然后进行变量实例化
第二步:代码执行
在这里对var变量,声明式函数和函数形参进行分析,先给出结论。
实例化的顺序:函数形参 => 声明式函数 => 变量
命名重复时的优先级:声明式函数 > 函数形参 > 变量
function test(y){
alert(x);
var x = 10;
alert(x);
x = 20;
function x(){}
alert(x);
alert(y);
}
test(1);
分析:test函数外部不必多说,这里只分析test函数内部。首先是实例化过程(也就是代码执行前),按照上文实例化顺序,首先实例化形参y,然后是声明式函数x,然后是var变量x,再根据优先级,后实例化的var变量x会被同名的声明式函数所替换,因此此时x为声明式函数,y为函数形参。
代码执行阶段:首先alert(x)
打印出的是function x(){}
,然后进行了x变量的赋值,因此此时alert(x)
为10,继续x赋值20,alert(x)
为20,然后function x(){}
这一句已经在实例化过程执行完毕所以跳过继续执行,alert(x)
打印20,alert(y)
打印1。
alert结果:
function x(){}
10
20
20
1
3. 谈一谈模块化
3.1 常用的模块化方法CommonJS(用于服务端) & ES Module(用于客户端如浏览器)之间的区别:
- CommonJS 支持动态导入,也就是 require(${path}/xx.js)
- CommonJS是同步导入,ES Module是异步导入
- CommonJS 在导出时都是值拷贝, ES Module 采用实时绑定的方式。区别在于若是导出导入的值是动态改变的话,CommonJS需要重更新导入,而ES Module不需要
- ES Module 会编译成 require/exports 来执行
3.2 module.exports & exports.xxx
记住几点原则:
- 最终导出的对象一定是module.exports对象。exports是引用module.exports,因此exports.xxx=... 相当于module.exports.xxx=...
- module.exports用来导出类型;exports.xxx用来导出类型的实例对象
- 若要用exports.xxx导出,则不可以出现对module.exports的赋值语句,这会导致module.exports原本指向的对象失效,而这时exports依旧指向失效的对象,所以任何exports.xxx的导出都不会生效
4. Proxy
这是ES6中用来自定义对象中的操作的。类似之前的Object.defineProperty( )函数
let p = new Proxy(target, handler)
target 代表需要添加代理的对象,handler 用来自定义对象中的操作(是一个配置对象,内部含有各种自定义的函数)
handler中可以配置的操作有13种,详情见es6文档。以下是几种常用的:
get(target, property, ?receiver)
参数:目标对象、属性名和 proxy 实例本身,其中最后一个参数可选。set(target, property, value, receiver)
参数:目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选apply(target,ctx,args)
参数:目标对象、目标上下文、参数数组(函数内部一般用...arguments而不用这个参数)。可以拦截函数的调用、call和apply操作construct(target,args,newTarget)
参数:目标对象、构造函数的参数对象、创造实例对象时,new命令作用的构造函数。可以拦截new命令
Proxy.revocable方法返回一个可取消的Proxy实例
该方法接收相同的参数target,handler,返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。
let {proxy, revoke} = Proxy.revocable(target, handler); // 对象解构
proxy.foo = 123;
proxy.foo // 123
revoke(); // 调用revoke()后该代理对象就被销毁了
proxy.foo // TypeError: Revoked
若使用出现问题请第一时间关注this
在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理,因此可能会出现很多无法正常使用的问题。
例:简单实现数据响应 ( 例子来自掘金小册 )
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver) {
setBind(value, property)
return Reflect.set(target, property, value)
}
}
return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
obj,
(v, property) => {
console.log(`监听到属性${property}改变为${v}`)
},
(target, property) => {
console.log(`'${property}' = ${target[property]}`)
}
)
p.a = 2 // 监听到属性a改变
p.a // 'a' = 2
5. reflect
功能(摘自阮一峰es6)
- 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上
- 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
- 让Object操作都变成函数行为。name in obj 变成 Reflect.has(obj, name)
- Proxy代理handler对象的函数内部可以通过reflect调用默认行为。eg:Reflect.set(target, name, value, receiver)调用默认的set