据说react和bluebird的优化也特别狠, 但是还没看过.
但是stamp需要我.

核心目标
  • 解决getter/setter无法生效的问题
function getset() {
  const x = stampit()
  .props({ i: 'iiiii' })
  .methods({
    get info() {
      return `get${this.i}`;
    },
    set info(info) {
      this.i += `hahah${info}`;
    },
  });

  const t = x();
  t.info = 'ttttt';
  log('stampt info:', t.info); //这里没生效, 直接就是tttt
  log('stampt i:', t.i); //这里也没生效, 直接就是iiii

  const y = {
    i: 'iiiii',
    get info() {
      return `get${this.i}`;
    },
    set info(info) {
      this.i += `hahah${info}`;
    },
  };

  y.info = 'ttttt';
  log('object info:', y.info);
  log('object i:', y.i);
}

经过debug跟踪发现:

// if we build a object by stamp , 
__proto__: {__proto__:  {__definegetter__:function, __lookupgetter__:function} }

//if we build a original object,
__proto__:  {__definegetter__:function, __lookupgetter__:function} 

也就是说__proto__嵌套导致getter/setter不生效. 因此需要补充一点getter/setter知识, 他们可以被原型链继承吗? — 这个结论是错的, 并不是原型链嵌套造成的, 是我的定义方法不对. getter/setter是properties, 不是methods

  • stampit

    • setter/getter再node里面是用__definegetter__模拟的. 从mdn上看. 在浏览器貌似不是这样的, 在浏览器__definegetter__是不推荐的过时接口.

    • 在浏览器的表现确实不同

      //stamp: 
      {__proto__:
       {__proto__: {
         get __proto__,
         set __proto__,
       }}}
      //object:
      {
        get info: xxx,
       	set info: xxx,
      }
      
  • stamp-specification 也需要测试, 非常不顺利, 浏览器端解决lodash依赖是一个问题.

    • node端, 报同样的错, 也就是说其实浏览器端的引入也是可以的. 直接引入npm里面的包.
    • 浏览器端, 直接引入貌似不行.
  • 更多getter/setter知识

    • getter/setter在object层面是emumerable的, 但是在class层面不是.
//测试下继承是否可以
class X {
  constructor(name) {
    this._name = name;
  }
  get name() {
    return this._name;
  }
  set name(name) {
    this._name = `${name}X`;
  }
}
class Y extends X {}
const yy = new Y('');
yy.name = 'hi';
log('yyyyyyy', yy); //真的可以继承
  • getter/setter是可以继承的, 我之前的结论不对了. 这里可以看到很多层的proto嵌套.
  • 这个博客讲的很透彻: https://nemisj.com/why-getterssetters-is-a-bad-idea-in-javascript/

另外还要补充一点, class/object知识. stampit直接build的是一个class, 那个class()build的才是object.

源代码令人敬佩
  • 含注释400行, 注释应该有100+行了.
核心内容
  • 两个核心函数
    • merge
    • assign
  • 修饰
    • deep
顺序阅读
//第一个函数
function _mergeOrAssign(action, dst) {
  return slice.call(arguments, 2).reduce(action, dst);
}
  • 这里slice是常规操作, 除掉前面两个参数之后的参数作为返回值.
  • 但是reduce就不算常规操作了. 因为reduce有各种限制,
    • 比如数组为空. 数组只有一个元素, 他的表现都有异常.
    • 但是这里没问题, 因为他提供了reduce的第二个参数: initialvalue: dst, 这个是mdn推荐的常规操作.

第二个函数: assignOne 很好理解就是实现object.assign.

接下来是三个判断函数. 到现在为止都没有注释, 估计是因为作者也认为这些很简单.

  • 这里顺手把3个function执行捋一下吧, 作者用的比较多.
    • apply: 指定this的情况下执行一个函数, funciton.apply(this指定, arguments), 第一个参数指出this. 如果不需要可以用null
    • bind: 并不执行函数, 而是为函数指定this和参数, funciton.bind(this指定, arg1, arg2), 参数没有可以不传.
    • call: 也是指定shis的情况下执行一个函数, function.call(this指定, arg1, arg2…..) 几乎和apply一样.
  • 想了一下, 为啥我不用上面这三个, 因为我的写法一般是这样的:
funciton xxx(){}
let p={};
p.ooo=xxx; //这个等于bind
p.ooo(); //这里xxx()的this就是p了. 
(p.ooo=xxx)(); //这是一行写法.

然后是重要的函数: mergeone

  • 这里递归的把src的属性赋值给dst

然后,我们看到下面两个函数:

  • merge
  • extractUniqueFunctions

这里我们要补习一下array的基础函数, 顺便说, stamp的闭包里面改的就是array.proto

  • concat, 合并两个数组.
  • filter, 逐个判断. 回调为true的元素组成新的数组.
  • slice 上文介绍过 slice(begin ,end), 可以用负数标识从尾巴数. end省略代表到结尾.
  • reduce 上文介绍过
    • reduce(回调函数, 初始值), 虽然初始值可以省略, 但是, 千万别省, 省了就是坑.
    • 回调函数(返回时, 当前值, 当前索引, 当前数组) 有四个参数.
    • 每个元素都执行回调函数, 最终汇总为一个值.

merge函数

function _mergeOrAssign(action, dst, ...a) {
  return a.reduce(action, dst);
}
function mergeOne(dst, src) {  } //src -> dst
var merge = _mergeOrAssign.bind(0, mergeOne);

extractUniqueFunctions函数

  • 对参数做筛选, 把不是函数的都去掉.
  • 把参数中重复的函数也去掉, 最终只有一组不重复的函数.
  • 如果啥都不剩了, 那么返回undefined

standardiseDescriptor

  • 把参数主项复制进来.
  • 主要是为了兼容两种写法. 比如props和properties
  • 这个其实不是很有必要. 就一种简写就可以了.

createfactory

  • 工厂函数制造obj.
  • 特殊处理function, 放到__proto__
  • 特殊处理init, 直接调用了, 这个就是为什么init里面写个return, 就会导致method失效的原因了.

createstamp

  • 制造一个新的stamp
  • 依赖工厂函数制造

mergeComposable

  • 从src到dst
  • 合并composable内容

compose

  • 从一组conmposable制造一个stamp

isstamp

  • 判断一个对象是否stamp

这里有一组执行的代码, 这些代码就是副作用了.

createUtilityFunction

  • 实话说这个没看懂.
整体架构
//最外层
!function () {
}();

这里的!的意思是后面的匿名函数是个表达式, 然后直接执行, 不然需要下面这样.

//最外层
(function () {
})();

除了叹号还有很多符号都可以:

  • !
  • ~
  • +
  • -
  •  
  • 这篇文当有测试, 加减号最好用. https://swordair.com/function-and-exclamation-mark/