null 与 undefined 区别 与 instanceof

1,475 阅读3分钟

nullundefined 为js中两种不同的数据类型

但是 typeof undefined 的结果是 undefinedtypeof null 的结果是 object

从字面含义来说,undefined 的意思是未定义,即应该有值但是没有值,比如说我们访问一个对象里没有的属性,从人类本身的意愿来说,我们觉得他是有值的,而且能可以得到预期结果,但是他偏偏没有这个属性; 又或是函数的形参,当没有实参传入的时候

function fn(parameter) {
    console.log(parameter)
}

当我们不传参数调用时

fn()

控制台将会输出 undefined

fn(null)

又或者调用一个函数没有返回值的时候,

let res = fn()
console.log(res)

也会输出 undefined

null

null 的意思是 ‘空值’,空值不是没有,undefined 才是没有,不存在

如果我们想看见 null ,那么就必须手动赋予 null 类型

坑点

虽说typeof null 的结果是 object,但是他并不算是对象,他没有原型

 console.dir(null)

将会输出 null

MDN 对此方法的描述

可以用次方法打印dom元素,将会看到一长串属性,可见大 常见的 offsetTop, offsetWidth 等属性都作为基本属性存在于此

再说 console.log(null instanceof Object), 输出 false, 更加证实 null 不是对象

instanceof

MDN

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。

翻译一下MDN的意思: A instanceof B A对象的的原型链上是否有B的prototype属性

A必须是一个对象实例, 可以是数组和对象的字面量形式,可以是原始数据类型的 new String('str') 形式

也就是说

let simpleStr = 'hello'
simpleStr instanceof String  // 返回false, 非对象实例

let objStr = new String('hello')
objStr instanceof String  // 返回true
console.log([] instanceof Array) 
console.log([] instanceof Object)

都会输出 true

这是为什么呢?????

之前提到的 console.dir 分别打印原生构造函数 ArrayObject

原生构造函数 还包括 Number, Boolean, String 等, 都可以直接使用new调用

console.dir(Array)
console.dir(Object)

看数组的 prototype 属性,上边都是眼熟的方法呀,这就是能够直接调用数组方法的原因 --原型链

在数组的 prototype 上有个 __proto__ 上面是一个 Object, 他正好等于 console.dir(Object) prototype

因此

前面的数组的两个 instanceof 都是 true

  • 文章写起来没完啊。。。。。

再说 constructor 可以用来检测数据类型

console.log(({}).constructor === Object)	// true
console.log([].constructor === Array)		// true

因为 { } 会被当做一个代码块,所以这里加个小括号

.constructor 将会打印一个对象的构造函数

例如

function AS () {
	
}
	
let ui = new AS()
	
console.log(ui.constructor)

因此之前打印的 数组或者对象的 .constructor 就是原生构造函数

说到数据类型检测,最最安全的方法其实是 Object.prototype.toString

下面直接放出 elementUI的源码

export function isString(obj) {
    return Object.prototype.toString.call(obj) === '[object String]';
}

export function isObject(obj) {
    return Object.prototype.toString.call(obj) === '[object Object]'
}

export function isHtmlElement(node) {
    return node && node.nodeType === Node.ELEMENT_NODE
}

export const isFunction = (functionToCheck) => {
    var getType = {}
    return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'
}

export const isUndefined = (val)=> {
    return val === void 0
}

export const isDefined = (val) => {
    return val !== undefined && val !== null
}

核心就是常见的 toString 方法,但是呢该方法会被 array,number 等数据类型的构造函数重写, 被重写之后,可以找到最近的 toString 方法,就不会沿着原型链一层一层查找了,

因此 源码直接使用 Object原型上的方法

你可能会有个疑惑,为什么要加 .call, 嗯哼,我在最初看的时候也有这个疑惑, 哈哈哈

这个东西并不复杂,别忘了 toString 是个函数,要让被检测的数据类型,调用这个方法,调用函数的方式就是 a.fn(), 这里很明显 toString 是被 Object 调用,但是我们想让它被其他对象调用 因此 .call 来了, 他会改变函数的调用者,第一个参数就是被检测是变量

至于 .bind, .apply 都是大同小异,这里不再讲述,

我是怎么从 null 与 undefined 说道源码的 .............., 这之间的关联好多啊,不知不觉各种小知识串联起来,构成了javascript的基础知识大杂烩