seamless-immutable之源码阅读笔记

1. seamless-immutable简介

seamless-immutableimmutable功能类似,通过共享现有嵌套对象来提高深度复制大型嵌套对象时的速度。seamless-immutable中通过Object.freeze防止对象被修改,并定义了一系列API来实现对不可变数据结构的操作。

1.1 使用

seamless-Immutable的API参考其文档即可


2. seamless-immutable源码

2.1 基础

Object.freeze() 方法可以冻结一个对象,冻结的对象不能添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。尝试修改会静默失败或抛出TypeError类型的错误。相关函数还包括:

Object.freezeObject.defineProperty均为ES5中定义的方法,因此使用seamless-immutable需保证浏览器中这些方法存在

2.2 源码阅读注释

// https://github.com/facebook/react/blob/v15.0.1/src/isomorphic/classic/element/ReactElement.js#L21
var REACT_ELEMENT_TYPE = typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element');
var REACT_ELEMENT_TYPE_FALLBACK = 0xeac7;
react会在react元素上添加一个tag,值为Symbol.for('react.element') || 0xeac7
var globalConfig = {
  use_static: false
};
if (isObject(config)) {
  if (config.use_static !== undefined) {
    globalConfig.use_static = Boolean(config.use_static);
  }
}
判断是否使用静态类型还是实例类型的语法
function isObject(data) {
  return (
    typeof data === 'object' &&
    !Array.isArray(data) &&
    data !== null
  );
}
判断data是否是一个对象(忽略array和null)
function instantiateEmptyObject(obj) {
  var prototype = Object.getPrototypeOf(obj);
  if (!prototype) {
    return {};
  } else {
    return Object.create(prototype);
  }
}
以传入的obj的原型对象创建一个空对象,防止新生成的对象的原型链为空
function addPropertyTo(target, methodName, value) {
  Object.defineProperty(target, methodName, {
    enumerable: false,
    configurable: false,
    writable: false,
    value: value
  });
}
1. 为target对象添加methodName属性,并禁止该属性被修改或遍历
2. 目的是为了禁止在不可变对象上调用会导致对象改变的方法
function banProperty(target, methodName) {
  addPropertyTo(target, methodName, function() {
    throw new ImmutableError("The " + methodName +
      " method cannot be invoked on an Immutable data structure.");
  });
}
禁止target上的methodName属性被访问,否则抛出ImmutableError类型错误
var immutabilityTag = "__immutable_invariants_hold"; 
function addImmutabilityTag(target) {
  addPropertyTo(target, immutabilityTag, true);
}
function isImmutable(target) {
  if (typeof target === "object") {
    return target === null || Boolean(
        Object.getOwnPropertyDescriptor(target, immutabilityTag)
      );
  } else {
    // 在JavaScript中,只有对象可以是可变对象
    // strings, numbers, null和undefined天生不可变
    return true;
  }
}
1. immutabilityTag属性将被用于标识对象是否为不可变类型,如果对象此属性为true,则为不可变对象。
2. isImmutable()通过此属性判断target是否为不可变对象
function isEqual(a, b) {
  // Avoid false positives due to (NaN !== NaN) evaluating to true
  return (a === b || (a !== a && b !== b));
}
1. 判断两个对象是否严格相等,保证isEqual(NaN, NaN)返回true。
2. 与Object.is 的区别在于对+0-0的比较
function isMergableObject(target) {
  return target !== null && 
    typeof target === "object" &&
    !(Array.isArray(target)) &&
    !(target instanceof Date);
}
判断一个对象是否可以被merge,此对象不可以是ArrayDate 类型
var mutatingObjectMethods = [
  "setPrototypeOf"
];

var nonMutatingObjectMethods = [
  "keys"
];

var mutatingArrayMethods = mutatingObjectMethods.concat([
  "push", "pop", "sort", "splice", "shift", "unshift", "reverse"
]);

var nonMutatingArrayMethods = nonMutatingObjectMethods.concat([
  "map", "filter", "slice", "concat", "reduce", "reduceRight"
]);

var mutatingDateMethods = mutatingObjectMethods.concat([
  "setDate", "setFullYear", "setHours", "setMilliseconds", "setMinutes", "setMonth", "setSeconds",
  "setTime", "setUTCDate", "setUTCFullYear", "setUTCHours", "setUTCMilliseconds", "setUTCMinutes",
  "setUTCMonth", "setUTCSeconds", "setYear"
]);
这5个变量分别对应了ObjectArrayDate三种类型的对象中包含的方法是否会导致对象改变。
function ImmutableError(message) {
  this.name = 'MyError';
  this.message = message;
  this.stack = (new Error()).stack;
}
ImmutableError.prototype = new Error();
ImmutableError.prototype.constructor = Error;
定义ImmutableError类型,用于在调用会导致对象改变的方法时抛出错误
function makeImmutable(obj, bannedMethods) {
  // 在对象上打上immutabilityTag标记即表示对象不可变
  addImmutabilityTag(obj);

  if (process.env.NODE_ENV !== "production") {
    // 让所有导致对象改变的方法在调用时抛出错误
    for (var index in bannedMethods) {
      if (bannedMethods.hasOwnProperty(index)) {
        banProperty(obj, bannedMethods[index]);
      }
    }
    // 冻结对象
    Object.freeze(obj);
  }
  return obj;
}
确保对象不可变的分三步:
1. 打上immutabilityTag标记;
2. 禁用会导致对象改变的方法;
3. 冻结对象 。
function makeMethodReturnImmutable(obj, methodName) {
  var currentMethod = obj[methodName];

  addPropertyTo(obj, methodName, function() {
    return Immutable(currentMethod.apply(obj, arguments));
  });
}
1. 通过闭包缓存原始方法,并在对象上定义新方法覆盖(屏蔽)原始方法
2. 新方法将新生成的对象转换为不可变对象
function arraySet(idx, value, config) {
  var deep = config && config.deep;

  if (idx in this) {
    if (deep && this[idx] !== value && isMergableObject(value) && isMergableObject(this[idx])) {
      value = Immutable.merge(this[idx], value, {deep: true, mode: 'replace'});
    }
    if (isEqual(this[idx], value)) {
      return this;
    }
  }
  // 将数组转换为可变对象
  var mutable = asMutableArray.call(this);
  mutable[idx] = Immutable(value); // 设置idx属性的新值
  return makeImmutableArray(mutable);
}
1. 实现数组的Immutable.set() 功能
2. 如果是深层比较,则通过Immutable.mergevalue合并到this[index]
3. 如果原始值this[index] 和设置值value相等,直接返回
4. 将数组转换为可变对象后,再覆盖新值,最后在转换为不可变数组
function asMutableArray(opts) {
  var result = [], i, length;
  // 返回可变数组,如果是深度不可变的,会递归转换
  if(opts && opts.deep) {
    for(i = 0, length = this.length; i < length; i++) {
      result.push(asDeepMutable(this[i]));
    }
  } else {
    for(i = 0, length = this.length; i < length; i++) {
      result.push(this[i]);
    }
  }
  return result;
}
function asDeepMutable(obj) {
  // obj为日期类型、非immutable、非对象,falsey值时,直接返回
  // 即不是immutable对象,判断标准为immutabilityTag属性是否为存在
  if (
    (!obj) ||
    (typeof obj !== 'object') ||
    (!Object.getOwnPropertyDescriptor(obj, immutabilityTag)) ||
    (obj instanceof Date)
  ) { return obj; }
  // 源码此行被注释,替换为其执行结果,便于阅读
  // return Immutable.asMutable(obj, {deep: true});
  return asMutableArray.apply(obj, {deep: true});
}
两个函数之间递归调用,将数组转换为可变对象
function makeImmutableArray(array) {
  // 包装不可变数组方法,确保此类方法返回不可变数组
  for (var index in nonMutatingArrayMethods) {
    if (nonMutatingArrayMethods.hasOwnProperty(index)) {
      var methodName = nonMutatingArrayMethods[index];
      makeMethodReturnImmutable(array, methodName);
    }
  }
  // 如果是有实例语法,则在实例上绑定以下函数
  // 否则使用静态语法,则通过Immutable.methodName调用
  if (!globalConfig.use_static) {
    addPropertyTo(array, "flatMap",  flatMap);
    addPropertyTo(array, "asObject", asObject);
    addPropertyTo(array, "asMutable", asMutableArray);
    addPropertyTo(array, "set", arraySet);
    addPropertyTo(array, "setIn", arraySetIn);
    addPropertyTo(array, "update", update);
    addPropertyTo(array, "updateIn", updateIn);
    addPropertyTo(array, "getIn", getIn);
  }

  for(var i = 0, length = array.length; i < length; i++) {
    array[i] = Immutable(array[i]);
  }
  // 打上immutabilityTag标记等三步
  return makeImmutable(array, mutatingArrayMethods);
}
将数组转换为不可变数组
var immutableEmptyArray = Immutable([]);
function arraySetIn(pth, value, config) {
  var head = pth[0];

  if (pth.length === 1) {
    // 仅一个层级时,直接使用arraySet
    return arraySet.call(this, head, value, config);
  } else {
    var tail = pth.slice(1);
    var thisHead = this[head];
    var newValue;

    if (typeof(thisHead) === "object" && thisHead !== null) {
      // thisHead可能是对象或数组
      // Immutable.setIn根据类型对应的选择使用objectSetIn或arraySetIn
      newValue = Immutable.setIn(thisHead, tail, value);
    } else {
      var nextHead = tail[0];
      // 如果thisHead不是数组或对象,只能创建新数组或对象
      // 并最终递归生成按路径嵌套的值
      if (nextHead !== '' && isFinite(nextHead)) {
        // isFinite('12') -> true
        newValue = arraySetIn.call(immutableEmptyArray, tail, value);
      } else {
        newValue = objectSetIn.call(immutableEmptyObject, tail, value);
      }
    }

    if (head in this && thisHead === newValue) {
      return this;
    }
    // 同过先转换为可变对象,再转换为不可变对象的过程,生成新的不可变对象
    // 实际上新对象是在转换为可变对象的过程中产生的
    var mutable = asMutableArray.call(this);
    mutable[head] = newValue;
    return makeImmutableArray(mutable);
  }
}
1. 实现数组的Immutable.setIn()的功能
2. 执行流程见注释
function makeImmutableDate(date) {
  if (!globalConfig.use_static) {
    addPropertyTo(date, "asMutable", asMutableDate);
  }

  return makeImmutable(date, mutatingDateMethods);
}

function asMutableDate() {
  return new Date(this.getTime());
}
1. makeImmutableDateDate类型的对象转换为不可变对象
2. asMutableDate 将不可变Date 类型对象转换为可变对象
function flatMap(iterator) {
  // 不存在iterator时,操作无效,直接返回同一个对象
  if (arguments.length === 0) {
    return this;
  }

  var result = [],
    length = this.length,
    index;

  for (index = 0; index < length; index++) {
    // 遍历数组项,得到返回值
    var iteratorResult = iterator(this[index], index, this);

    if (Array.isArray(iteratorResult)) {
      // 如果返回值为数组,则将数组每一项压入数组
      result.push.apply(result, iteratorResult);
    } else {
      // 否则直接压入数组
      result.push(iteratorResult);
    }
  }
  return makeImmutableArray(result);
}
实现Immutable.flatMap()的功能
function without(remove) {
  // 不存在iterator时,操作无效,直接返回同一个对象
  if (typeof remove === "undefined" && arguments.length === 0) {
    return this;
  }

  if (typeof remove !== "function") {
    // 如果不是函数,统一转换为过滤函数
    var keysToRemoveArray = (Array.isArray(remove)) ?
      remove.slice() : Array.prototype.slice.call(arguments);

    // 将属性列表中的数字转换为字符串
    // 事实上数组和对象中的键均为字符串,而不是数字
    keysToRemoveArray.forEach(function(el, idx, arr) {
      if(typeof(el) === "number") {
        arr[idx] = el.toString();
      }
    });
    // 默认的过滤函数,属性匹配则返回true
    remove = function (val, key) {
      return keysToRemoveArray.indexOf(key) !== -1;
    };
  }
  // 创建新对象
  var result = instantiateEmptyObject(this);
  // 过滤属性
  for (var key in this) {
    if (this.hasOwnProperty(key) && remove(this[key], key) === false) {
      result[key] = this[key];
    }
  }

  return makeImmutableObject(result);
}
1. 实现Immutable.without()的功能
2. 参数可以是单个属性依次作为参数或属性集合的数组,也可以是函数,类似于Array.prototype.filter()中传入的函数
function asObject(iterator) {
  // 如果iteration未传入,则默认数组的每一项为包含键值对的数组
  if (typeof iterator !== "function") {
    iterator = function(value) { return value; };
  }

  var result = {},
    length = this.length,
    index;

  for (index = 0; index < length; index++) {
    var pair  = iterator(this[index], index, this),
      key   = pair[0],
      value = pair[1];

    result[key] = value;
  }

  return makeImmutableObject(result);
}
实现Immutable.asObject()的功能,变量数组每一项,以键值对的形式生成不可变对象
function asDeepMutable(obj) {
  if (
    (!obj) ||
    (typeof obj !== 'object') ||
    (!Object.getOwnPropertyDescriptor(obj, immutabilityTag)) ||
    (obj instanceof Date)
  ) { return obj; }
  // Immutable.asMutable根据obj类型选择使用如下函数,通过递归实现深度转换
  // asMutableObject, asMutableArray, asMutableDate
  return Immutable.asMutable(obj, {deep: true});
}
结合asMutableObjectasMutableArraymasMutableDate将对象转换为可变对象,这些函数会生成新对象
function quickCopy(src, dest) {
  for (var key in src) {
    if (Object.getOwnPropertyDescriptor(src, key)) {
      dest[key] = src[key];
    }
  }

  return dest;
}
src对象本身包含的属性复制到dest
function merge(other, config) {
  // 如果参数不存在,操作无效,直接返回同一个对象
  if (arguments.length === 0) {
    return this;
  }

  if (other === null || (typeof other !== "object")) {
    throw new TypeError("Immutable#merge can only be invoked with objects or arrays, not " + JSON.stringify(other));
  }

  var receivedArray = (Array.isArray(other)),
    deep          = config && config.deep,
    mode          = config && config.mode || 'merge',
    merger        = config && config.merger,
    result;

  // 参数分别为:被合并的对象,用于合并的对象和当前处理的键
  function addToResult(currentObj, otherObj, key) {
    // 存储当前合并的值,先转换为不可变对象
    var immutableValue = Immutable(otherObj[key]);
    // 存储传入自定义函数返回的用于合并的对象
    var mergerResult = merger && merger(currentObj[key], immutableValue, config);
    // 存储被合并对象上的值
    var currentValue = currentObj[key];

    if ((result !== undefined) ||
      (mergerResult !== undefined) ||
      (!currentObj.hasOwnProperty(key)) ||
      !isEqual(immutableValue, currentValue)) {

      var newValue;

      // newValue的值由mergerResult以及是否深度拷贝决定
      if (mergerResult) {
        newValue = mergerResult;
      } else if (deep && isMergableObject(currentValue) && isMergableObject(immutableValue)) {
        // 如果需要深度合并,通过递归调用实现
        newValue = Immutable.merge(currentValue, immutableValue, config);
      } else {
        newValue = immutableValue;
      }

      if (!isEqual(currentValue, newValue) || !currentObj.hasOwnProperty(key)) {
        // 如果currentValue与newValue相同时不需要处理
        // !currentObj.hasOwnProperty(key)即currentObj本身不存在key
        // 此时currentValue与newValue即使相等,仍需要把newValue拷贝到result对象本身
        if (result === undefined) {
          // 首次调用addToResult时,result为undefined,通过对currentObj浅复制获得新对象
          result = quickCopy(currentObj, instantiateEmptyObject(currentObj));
        }

        result[key] = newValue;
      }
    }
  }

  function clearDroppedKeys(currentObj, otherObj) {
    for (var key in currentObj) {
      if (!otherObj.hasOwnProperty(key)) {
        // result上包含currentObj所有的属性
        // 但是替换模式下仅需要other上包含的属性,需要删除新对象上不需要的属性
        if (result === undefined) {
          // 浅复制currentObj获得新对象
          result = quickCopy(currentObj, instantiateEmptyObject(currentObj));
        }
        delete result[key];
      }
    }
  }

  var key;

  // 后者覆盖前者
  if (!receivedArray) {
    // 如果不是数组使用addToResult遍历对象属性,实现合并
    // 最常见的用法:将一个对象合并到this中
    for (key in other) {
      if (Object.getOwnPropertyDescriptor(other, key)) {
        addToResult(this, other, key);
      }
    }
    if (mode === 'replace') {
      // mode为replace供以下函数内部实现使用:
      // arraySet, objectSet, objectReplace
      clearDroppedKeys(this, other);
    }
  } else {
    // other为数组的时候则对数组中的每一项遍历即可
    // Immutable内部使用时不可能传入数组,所以不需要判断mode === 'replace'
    for (var index = 0, length = other.length; index < length; index++) {
      var otherFromArray = other[index];

      for (key in otherFromArray) {
        if (otherFromArray.hasOwnProperty(key)) {
          addToResult(result !== undefined ? result : this, otherFromArray, key);
        }
      }
    }
  }

  if (result === undefined) {
    return this;
  } else {
    return makeImmutableObject(result);
  }
}
1. MergeImmutable中的核心方法之一, 功能是将提供的对象或对象数组合并到不可变对象中。
2. config参数包括{deep: true}{mode: oneOf['merge', 'replace']}{merger: func} ,其中{mode: 'replace'}仅共内部使用
function objectReplace(value, config) {
  var deep          = config && config.deep;
  // 参数不存在,直接返回
  if (arguments.length === 0) {
    return this;
  }
  if (value === null || typeof value !== "object") {
    throw new TypeError("Immutable#replace can only be invoked with objects or arrays, not " + JSON.stringify(value));
  }
  // 通过Immutable.merge实现对象
  return Immutable.merge(this, value, {deep: deep, mode: 'replace'});
}
1. 实现Immutable.replace的功能
2. 仅返回包含第二个对象的属性和值的不可变对象。
3. 通过深度合并,检查所有子对象是否相等,尽可能返回原始的不可变对象。
function objectSet(property, value, config) {
  var deep = config && config.deep;

  if (this.hasOwnProperty(property)) {
    if (deep && this[property] !== value && isMergableObject(value) && isMergableObject(this[property])) {
      value = Immutable.merge(this[property], value, {deep: true, mode: 'replace'});
    }
    if (isEqual(this[property], value)) {
      return this;
    }
  }

  var mutable = quickCopy(this, instantiateEmptyObject(this));
  mutable[property] = Immutable(value);
  return makeImmutableObject(mutable);
}
1. 和 arraySet功能类似,处理普通对象的属性修改
2. 区别在于 arraySet一个处理数组,一个处理其他对象
var immutableEmptyObject = Immutable({});
function objectSetIn(path, value, config) {
  if (!(Array.isArray(path)) || path.length === 0) {
    throw new TypeError("The first argument to Immutable#setIn must be an array containing at least one \"key\" string.");
  }

  var head = path[0];
  if (path.length === 1) {
    return objectSet.call(this, head, value, config);
  }

  var tail = path.slice(1);
  var newValue;
  var thisHead = this[head];
  
  if (this.hasOwnProperty(head) && typeof(thisHead) === "object" && thisHead !== null) { 
    // thisHead值可能是数组或对象,
    // 使用Immutable.setIn自动选择arraySetIn或objectSetIn
    newValue = Immutable.setIn(thisHead, tail, value);
  } else {
    newValue = objectSetIn.call(immutableEmptyObject, tail, value);
  }

  if (this.hasOwnProperty(head) && thisHead === newValue) {
    return this;
  }

  var mutable = quickCopy(this, instantiateEmptyObject(this));
  mutable[head] = newValue;
  return makeImmutableObject(mutable);
}
1. 和 arraySetIn功能类似,处理普通对象的属性修改
2. 区别在于arraySetIn一个处理数组,一个处理其他对象
function update(property, updater) {
  var restArgs = Array.prototype.slice.call(arguments, 2);
  var initialVal = this[property];
  // 选择性的使用arraySet或objectSet
  return Immutable.set(this, property, updater.apply(initialVal, [initialVal].concat(restArgs)));
}
1. 利用 Immutable.set实现 Immutable.update 的功能
2. 附加的参数将被传递给 updater函数
function getInPath(obj, path) {
  /*jshint eqnull:true */
  for (var i = 0, l = path.length; obj != null && i < l; i++) {
    obj = obj[path[i]];
  }

  return (i && i == l) ? obj : undefined;
}
// 使用demo
const obj = { a: { b: { c: 1 } } };
getInPath(obj, ['a', 'b', 'c']);  // -> 1
通过循环获取obj对象上的 以path指定的属性路径上的值
function getIn(path, defaultValue) {
  var value = getInPath(this, path);
  return value === undefined ? defaultValue : value;
}
getInPath的基础上返回值为undefined是使用默认值输出
function updateIn(path, updater) {
  var restArgs = Array.prototype.slice.call(arguments, 2);
  var initialVal = getInPath(this, path);

  return Immutable.setIn(this, path, updater.apply(initialVal, [initialVal].concat(restArgs)));
}
update的区别在于使用属性数组指定对象上操作目标的路径
function asMutableObject(opts) {
  var result = instantiateEmptyObject(this), key;

  if(opts && opts.deep) {
    for (key in this) {
      if (this.hasOwnProperty(key)) {
        result[key] = asDeepMutable(this[key]);
      }
    }
  } else {
    for (key in this) {
      if (this.hasOwnProperty(key)) {
        result[key] = this[key];
      }
    }
  }

  return result;
}
asMutableArray功能相同,将不可变对象转换为新创建可变对象
// 创建空对象
function instantiatePlainObject() {
  return {};
}

// 将对象转换为不可变对象
function makeImmutableObject(obj) {
  if (!globalConfig.use_static) {
    addPropertyTo(obj, "merge", merge);
    addPropertyTo(obj, "replace", objectReplace);
    addPropertyTo(obj, "without", without);
    addPropertyTo(obj, "asMutable", asMutableObject);
    addPropertyTo(obj, "set", objectSet);
    addPropertyTo(obj, "setIn", objectSetIn);
    addPropertyTo(obj, "update", update);
    addPropertyTo(obj, "updateIn", updateIn);
    addPropertyTo(obj, "getIn", getIn);
  }

  return makeImmutable(obj, mutatingObjectMethods);
}
makeImmutableArray功能相似
function isReactElement(obj) {
  return typeof obj === 'object' &&
    obj !== null &&
    (obj.$$typeof === REACT_ELEMENT_TYPE_FALLBACK || obj.$$typeof === REACT_ELEMENT_TYPE);
}

function isFileObject(obj) {
  return typeof File !== 'undefined' &&
    obj instanceof File;
}

function isBlobObject(obj) {
  return typeof Blob !== 'undefined' &&
    obj instanceof Blob;
}

function isPromise(obj) {
  return typeof obj === 'object' &&
    typeof obj.then === 'function';
}

function isError(obj) {
  return obj instanceof Error;
}
1. 工具函数,判读对象类型
2. reactElement本身就是不可变对象
function Immutable(obj, options, stackRemaining) {
  if (isImmutable(obj) || isReactElement(obj) || isFileObject(obj) || isBlobObject(obj) || isError(obj)) {
    // 如果对象为不可变类型,包括File和Blob类型(File API),直接返回
    return obj;
  } else if (isPromise(obj)) {
    // 如果是thenable,则将解析后得到的值包装成不可变类型
    // 如果符合Promise/A+规范,则返回新的Promise
    return obj.then(Immutable);
  } else if (Array.isArray(obj)) {
    // 数组直接使用makeImmutableArray转换为不可变类型
    // makeImmutableArray会调用Immutable,保证嵌套的数据结构均转换为不可变类型
    return makeImmutableArray(obj.slice());
  } else if (obj instanceof Date) {
    // Date类型仅仅通过绑定新方法、禁用部分方法、冻结对象的方式转换为不可变对象
    return makeImmutableDate(new Date(obj.getTime()));
  } else {
    // 对于普通对象,复制出新对象冻结并返回

    // 获取options的原型对象,一般不会传递options,为undefined
    // options的功能是决定新对象的原型对象,因此个人感觉这个名字起的不好
    var prototype = options && options.prototype;
    var instantiateEmptyObject =
      (!prototype || prototype === Object.prototype) ?
        instantiatePlainObject : (function() { return Object.create(prototype); });
    // 根据options的prototype来确定生成的空对象的原型对象
    var clone = instantiateEmptyObject();

    if (process.env.NODE_ENV !== "production") {
      // 第三个参数控制递归次数,防止由于循环引用而导致递归时的堆栈溢出
      // 如果对象嵌套层级过深,也可能会在此处抛出错误
      if (stackRemaining == null) {
        stackRemaining = 64;
      }
      if (stackRemaining <= 0) {
        throw new ImmutableError("Attempt to construct Immutable from a deeply nested object was detected." +
          " Have you tried to wrap an object with circular references (e.g. React element)?" +
          " See https://github.com/rtfeldman/seamless-immutable/wiki/Deeply-nested-object-was-detected for details.");
      }
      stackRemaining -= 1;
    }

    for (var key in obj) {
      // 遍历对象属性,转换为不可变类型
      if (Object.getOwnPropertyDescriptor(obj, key)) {
        clone[key] = Immutable(obj[key], undefined, stackRemaining);
      }
    }

    return makeImmutableObject(clone);
  }
}
Immutable的核心方法之一,将对象转换为不可变对象
function toStatic(fn) {
  function staticWrapper() {
    var args = [].slice.call(arguments);
    var self = args.shift();
    return fn.apply(self, args);
  }

  return staticWrapper;
}

function toStaticObjectOrArray(fnObject, fnArray) {
  function staticWrapper() {
    var args = [].slice.call(arguments);
    var self = args.shift();
    if (Array.isArray(self)) {
      return fnArray.apply(self, args);
    } else {
      return fnObject.apply(self, args);
    }
  }

  return staticWrapper;
}

function toStaticObjectOrDateOrArray(fnObject, fnArray, fnDate) {
  function staticWrapper() {
    var args = [].slice.call(arguments);
    var self = args.shift();
    if (Array.isArray(self)) {
      return fnArray.apply(self, args);
    } else if (self instanceof Date) {
      return fnDate.apply(self, args);
    } else {
      return fnObject.apply(self, args);
    }
  }

  return staticWrapper;
}
Immutable.merge          = toStatic(merge);
Immutable.replace        = toStatic(objectReplace);
Immutable.without        = toStatic(without);
Immutable.asMutable      = toStaticObjectOrDateOrArray(asMutableObject, asMutableArray, asMutableDate);
Immutable.set            = toStaticObjectOrArray(objectSet, arraySet);
Immutable.setIn          = toStaticObjectOrArray(objectSetIn, arraySetIn);
Immutable.update         = toStatic(update);
Immutable.updateIn       = toStatic(updateIn);
Immutable.getIn          = toStatic(getIn);
Immutable.flatMap        = toStatic(flatMap);
Immutable.asObject       = toStatic(asObject);
工具函数:将属性方法包装为静态方法暴露出去
1. toStatic将第一个参数绑定到this
2. toStaticObjectOrArraytoStaticObjectOrDateOrArray先判断对象类型,再根据类型使用对应的函数并绑定this
编辑于 2018-02-07

文章被以下专栏收录