JavaScript零碎知识点(自用)

263 阅读4分钟

一、类型

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 letvar的不同

在全局使用时,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