Stampit源码阅读笔记
据说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/