what is plainObject
- 通过{}或者new Object()方式创建的对象是纯粹对象,Object.create(null) 创建的也是纯粹对象
- 通过jquey.isPlain的源码来看纯粹对象是原型链最简单的一类对象, 对象的原型只能是null或者Object.prototype
- isPlainObject函数的功能的判断依据与对象使用什么方式创建无关,而与的函数原型是否是Object.prototype有关系
下面是react-redux6.0.0里面的源码 isPlainObject.js
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = Object.getPrototypeOf(obj)
// 1. edge case Object.create(null)
if (proto === null) return true
let baseProto = proto
while (Object.getPrototypeOf(baseProto) !== null) {
baseProto = Object.getPrototypeOf(baseProto)
}
// 2. 原型链第一个和最后一个比较
return proto === baseProto
}
代码理解也不难,
- 通过
Object.create(null)生成的对象proto的值是:null, 处理edge caseObject.create(null) 输出: {} - 通过一个while循环拿到参数对象原型链的最后一个值,然后和对象原型链上的第一个值比较
- 因为有前置判断typeof,任意对象while循环结束后
proto的值都是:Object.prototype,只有普通对象plainObject原型链上的第一个值是Object.prototype由此判断相等, - 问题来了,但是为什么不直接进行下面的判断呢?
return Object.getPrototypeOf(obj) === Object.prototype || Object.getPrototypeOf(obj) === null
redux是为了防止跨iframe访问变量时的类型判断错误, 比如说在一个frame里面调用父窗口的函数:
window.parent.someFunction(["hello", "world"])
在父窗口中有someFunction的定义
function someFunction(arg) {
if (arg instanceof Array) {
// ... operate on the array
}
}
这样调用并不会执行if语句的代码,因为两段代码所处的javascript执行环境是不一样的,每个frame都有自己的执行环境,他们也不会共享原型链,也就是说两个执行环境中的Array Object构造函数都是不等的,那么if语句的判断就为false,这个数组并不是继承的父窗口执行环境里的Array。
在看看单元测试源码
describe('isPlainObject', () => {
it('returns true only if plain object', () => {
function Test() {
this.prop = 1
}
const sandbox = { fromAnotherRealm: false }
vm.runInNewContext('fromAnotherRealm = {}', sandbox)
expect(isPlainObject(sandbox.fromAnotherRealm)).toBe(true)
expect(isPlainObject(new Test())).toBe(false)
expect(isPlainObject(new Date())).toBe(false)
expect(isPlainObject([1, 2, 3])).toBe(false)
expect(isPlainObject(null)).toBe(false)
expect(isPlainObject()).toBe(false)
expect(isPlainObject({ x: 1, y: 2 })).toBe(true)
expect(isPlainObject(Object.create(null))).toBe(true)
})
})
可以看到源码并没有加上跨iframe的单元测试,大概是因为很难制造两个不同js执行环境
启发
- 跨iframe的类型检测是个坑,很可能你依赖的一些底层的库就没有处理,比如JQ,所以遇到的跨iframe调用需要小心
- ts的类型有没有设计这种针对iframe的edge case呢
参考文章
- jQuery.isPlainObject方法源码解析
- redux的isPlainObject源码 ps: 这篇文章贴出的源码有误, 但是思维深度却是我等不及的