reduxlodash

isPlainObject

2019-06-28  本文已影响0人  别过经年
  1. redux v4.0.0的 isPlainObject
/**
 * @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 = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }
  // 拿obj最初的__proto__跟obj次顶层的__proto__做对比
  return Object.getPrototypeOf(obj) === proto
}

依然是那么的简洁,while循环就是不对往obj的原型链往上查找,直到次顶层,一般来说就是Object.prototype,最后一行代码就是判断obj的__proto__跟次顶层的Object.prototype是否相等。那么这么看下来只有对象字面量跟new Object()创建的对象才能满足这个条件。该方法在redux中用来校验action是否为一个原生的JavaScript对象,这点跟lodash的isPlainObject有差别。可以添加测试用了,测试看看。
翻看了redux官网对isPlainObject.spec.js的测试用例

import expect from 'expect'
import isPlainObject from '../../src/utils/isPlainObject'
import vm from 'vm'

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)
  })
})

其中并没有测试到Object.create的情况,我做了补充isPlainObject.spec.js,如下

import expect from 'expect'; // jest 的断言库
import isPlainObject from './isPlainObject';
import vm from 'vm';

describe('isPlainObject', () => {
  it('returns true only if plain object', () => {
    function Test() {
      this.prop = 1;
    }

    const sandbox = { fromAnotherRealm: false };
    vm.runInNewContext('fromAnotherRealm = {}', sandbox);
    expect(sandbox).toEqual({ fromAnotherRealm: {} });
    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(new Object())).toBe(true);
    expect(isPlainObject(Object.create(null))).toBe(false);
    expect(isPlainObject(Object.create({}))).toBe(false);
  });
});

其中expect(isPlainObject(sandbox.fromAnotherRealm)).toBe(true);很难理解,为什么要这么考虑,vm.runInNewContext('fromAnotherRealm = {}', sandbox);这句话又是什么意思?而且经过上面的分析代码可以简化成这样

export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  return Object.getPrototypeOf(obj) === Object.prototype;
}

为什么redux源码不这么写?
为什么 Redux 判断 PlainObject 的写法这么复杂?跟我相同的疑问,大神在下面做出了详细的解释。粗略的理解下大神的意思就是js执行有不同的上下文环境,比如页面嵌套iframe就是不同的context环境,如果dispatch的action是iframe传过来的,那么用Object.getPrototypeOf(obj) === Object.prototype返回的是false,其实这个action是个字面量对象或者new Object对象,只要是这种情况的对象就是被容许的,所以redux的考虑还是比较仔细的。
还有人提出其他疑问为什么redux不使用Object.prototype.toString判断Plain Object?从答案看出Object.prototype.toString判断对象的类型是不严谨的,这是我第一次看到。

Symbol.toStringTag

Symbol.toStringTag可以修改toString的输出

  1. lodash 4.17.11-es isPlainObject.js
import getTag from './.internal/getTag.js'
import isObjectLike from './isObjectLike.js'

/**
 * Checks if `value` is a plain object, that is, an object created by the
 * `Object` constructor or one with a `[[Prototype]]` of `null`.
 *
 * @since 0.8.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
 * @example
 *
 * function Foo() {
 *   this.a = 1
 * }
 *
 * isPlainObject(new Foo)
 * // => false
 *
 * isPlainObject([1, 2, 3])
 * // => false
 *
 * isPlainObject({ 'x': 0, 'y': 0 })
 * // => true
 *
 * isPlainObject(Object.create(null))
 * // => true
 */
function isPlainObject(value) {
  if (!isObjectLike(value) || getTag(value) != '[object Object]') {
    return false
  }
  if (Object.getPrototypeOf(value) === null) {
    return true
  }
  let proto = value
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }
  return Object.getPrototypeOf(value) === proto
}

export default isPlainObject
上一篇 下一篇

猜你喜欢

热点阅读