├── 2018
├── React源码分析与实现(一):组件的初始化与渲染.md
├── React源码分析与实现(二):状态、属性更新 -> setState.md
├── 函数式编程了解一下(下).md
├── 函数式编程了解一下(上).md
└── 窥探Underscore源码系列-开篇介绍.md
├── .gitignore
├── .idea
├── PersonalBlog.iml
├── modules.xml
├── vcs.xml
└── workspace.xml
├── OnceTheBlog
├── 变量、作用域和内存问题.md
└── 编写高质量代码基本要点.md
├── README.md
├── blogImg
└── 47
│ ├── 6E39C3BFC492366A4D3231206111C2C1.jpg
│ ├── pai_filter.gif
│ └── zhu_filter.gif
├── hooks
├── .gitignore
├── README.md
├── images.d.ts
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── index.css
│ ├── index.tsx
│ ├── logo.svg
│ └── registerServiceWorker.ts
├── tsconfig.json
├── tsconfig.prod.json
├── tsconfig.test.json
├── tslint.json
└── yarn.lock
├── img
├── qzqdjx.jpg
└── wx.jpg
├── lib
├── alibaba
│ └── 阿里拍卖2020届毕业生校招启动.md
├── es6Env
│ ├── .babelrc
│ ├── .gitignore
│ ├── index.html
│ ├── index.js
│ ├── package.json
│ ├── src
│ │ ├── index.js
│ │ └── lib
│ │ │ ├── diff.js
│ │ │ ├── diffList.js
│ │ │ ├── element.js
│ │ │ ├── patche.js
│ │ │ └── util.js
│ └── webpack.config.js
├── img
│ └── Wechat.jpeg
├── jsoo
│ ├── .babelrc
│ ├── .gitignore
│ ├── dist
│ │ └── index.js
│ ├── index.html
│ ├── package.json
│ ├── src
│ │ └── index.js
│ ├── test.html
│ └── webpack.config.js
├── preReact
│ ├── .babelrc
│ ├── .eslintignore
│ ├── .eslintrc
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── playground.html
│ ├── src
│ │ ├── playground.js
│ │ └── testDiff.js
│ ├── test-diff
│ │ ├── diff.js
│ │ ├── element.js
│ │ ├── list-diff.js
│ │ ├── patch.js
│ │ └── util.js
│ └── webpack.config.js
├── testCli
│ ├── .gitignore
│ ├── bin
│ │ ├── tcli
│ │ ├── tcli-add
│ │ ├── tcli-delete
│ │ ├── tcli-init
│ │ └── tcli-list
│ ├── package-lock.json
│ ├── package.json
│ └── template.json
└── underscore
│ ├── test.html
│ ├── test.js
│ └── underscore.js
├── test.html
└── webpack4-init
├── .babelrc
├── .gitignore
├── package-lock.json
├── package.json
├── src
├── index.html
└── index.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | **/**/node_modules
3 | .vscode
4 |
--------------------------------------------------------------------------------
/.idea/PersonalBlog.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 | true
54 | DEFINITION_ORDER
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | 1520822348054
126 |
127 |
128 | 1520822348054
129 |
130 |
131 |
132 | 1520822510727
133 |
134 |
135 |
136 | 1520822510727
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
--------------------------------------------------------------------------------
/2018/React源码分析与实现(一):组件的初始化与渲染.md:
--------------------------------------------------------------------------------
1 | # React源码分析与实现(一):组件的初始化与渲染
2 |
3 | > 原文链接地址:[https://github.com/Nealyang](https://github.com/Nealyang/PersonalBlog/blob/master/2018/React%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%8E%E5%AE%9E%E7%8E%B0(%E4%B8%80)%EF%BC%9A%E7%BB%84%E4%BB%B6%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%8E%E6%B8%B2%E6%9F%93.md) 转载请注明出处
4 |
5 | ## 前言
6 |
7 | 战战兢兢写下开篇...也感谢小蘑菇大神以及网上各路大神的博客资料参考~
8 |
9 | 阅读源码的方式有很多种,广度优先法、调用栈调试法等等,此系列文章,采用基线法,顾名思义,就是以低版本为基线,逐渐了解源码的演进过程和思路。
10 |
11 | react最初的设计灵感来源于游戏渲染的机制:当数据变化时,界面仅仅更新变化的部分而形成新的一帧渲染。所以设计react的核心就是认为UI只是把数据通过映射关系变换成另一种形式的数据,也就是展示方式。传统上,web架构使用模板或者HTML指令构造页面。react则处理构建用户界面通过将他们份极为virtual dom,当然这也是react的核心,整个react架构的设计理念也是为此展开的。
12 |
13 | ## 准备工作
14 |
15 | 我们采用基线法去学习react源码,所以目前基于的版本为stable-0.3,后面我们在逐步分析学习演变的版本。
16 |
17 | ### clone代码
18 |
19 | ```
20 | git clone https://github.com/facebook/react.git
21 |
22 | git checkout 0.3-stable
23 | ```
24 | 
25 |
26 | React源代码都在src目录中,src包含了8个目录,其主要内容描述见下表。
27 |
28 | | 目 录| 内容 |
29 | |:--:|:--:|
30 | | core | React 核心类 |
31 | | domUtil | Dom操作和CSS操作的相关工具类 |
32 | | environment | 当前JS执行环境的基本信息 |
33 | | event | React事件机制的核心类 |
34 | | eventPlugins | React事件机制的事件绑定插件类 |
35 | | test | 测试目录 |
36 | | utils | 各种工具类 |
37 | | vendor | 可替换模块存放目录 |
38 |
39 | 
40 |
41 | 我们将该版本编译后的代码放到example下,引入到basic/index.html中运行调试。
42 |
43 | ## 组件初始化
44 |
45 | ### 使用
46 |
47 | 这里还是以basic.html中的代码为例
48 |
49 | ```
50 |
69 | ```
70 | 回到我们说的组件初始化,抽离下上面的代码就是:
71 |
72 | ```
73 | var ExampleApplication = React.createClass({render:function(){ return
Nealyang
}})
74 | ```
75 | 熟悉react使用的人都知道,render方法不能为空,当然,createClass中我们也可以去写一写生命周期的钩子函数,这里我们暂且省略,毕竟目前我们更加的关注react组建的初始化过程。
76 |
77 | 同样,熟悉react使用方法的人也会有疑惑了,怎么实例代码中的render最后return的是```React.DOM.p(null,message)```
78 |
79 | 所以到这里,就不得不说一下react的编译阶段了
80 |
81 | ### 编译阶段
82 |
83 | 我们都知道,在js中直接编写html代码,或者。。。jsx语法这样的AST,在js词法分析阶段就会抛出异常的。
84 |
85 | 对的,所以我们在编写react代码的时候都会借助babel去转码
86 |
87 | 从babel官网上写个例子即可看出:
88 |
89 | 
90 |
91 | 对呀!明明人家用的是react.createElement方法,我们怎么出现个React.DOM.p...
92 |
93 | OK,历史原因:
94 |
95 |
96 | 
97 |
98 | - react现在版本中,使用babel-preset-react来编译jsx,这个preset又包含了4个插件,其中transform-react-jsx负责编译jsx,调用了React.createElement函数生成虚拟组件
99 | - 在react-0.3里,编译结果稍稍有些不同,官方给出的示例文件,使用JSXTransformer.js编译jsx(也就是``````),对于native组件和composite组件编译的方式也不一致。也就是我们看到的React.DOM.p or ReactComponsiteComponent
100 | - native组件:编译成React.DOM.xxx(xxx如div),函数运行返回一个ReactNativeComponent实例。
101 | - composite组件:编译成createClass返回的函数调用,函数运行返回一个ReactCompositeComponent实例
102 |
103 |
104 | 题外话,不管用什么框架,到浏览器这部分的,什么花里胡哨的都不复存在。我这就是js、css、html。所以我们这里的ReactCompositeComponent最终其实还是需要转成原生元素的 。\
105 |
106 | ### 组件创建
107 |
108 | 从React.js中我们可以找到createClass的出处:
109 |
110 | ```
111 |
112 | "use strict";
113 |
114 | var ReactCompositeComponent = require('ReactCompositeComponent');
115 |
116 | ...
117 |
118 | var React = {
119 | ...
120 | createClass: ReactCompositeComponent.createClass,
121 | ...
122 | };
123 |
124 | module.exports = React;
125 | ```
126 |
127 | - createClass 代码
128 |
129 | ```
130 | var ReactCompositeComponentBase = function() {};
131 |
132 | function mixSpecIntoComponent(Constructor, spec) {
133 | var proto = Constructor.prototype;
134 | for (var name in spec) {
135 | if (!spec.hasOwnProperty(name)) {
136 | continue;
137 | }
138 | var property = spec[name];
139 | var specPolicy = ReactCompositeComponentInterface[name];
140 |
141 |
142 | if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
143 | RESERVED_SPEC_KEYS[name](Constructor, property);
144 | } else if (property && property.__reactAutoBind) {
145 | if (!proto.__reactAutoBindMap) {
146 | proto.__reactAutoBindMap = {};
147 | }
148 | proto.__reactAutoBindMap[name] = property.__reactAutoBind;
149 | } else if (proto.hasOwnProperty(name)) {
150 | // For methods which are defined more than once, call the existing methods
151 | // before calling the new property.
152 | proto[name] = createChainedFunction(proto[name], property);
153 | } else {
154 | proto[name] = property;
155 | }
156 | }
157 | }
158 |
159 | createClass: function (spec) {
160 | var Constructor = function (initialProps, children) {
161 | this.construct(initialProps, children);
162 | };
163 | // ReactCompositeComponentBase是React复合组件的原型函数
164 | Constructor.prototype = new ReactCompositeComponentBase();
165 | Constructor.prototype.constructor = Constructor;
166 | // 把消费者声明配置spec合并到Constructor.prototype中
167 | mixSpecIntoComponent(Constructor, spec);
168 | // 判断合并后的结果有没有render,如果没有 render,抛出一个异常
169 | invariant(
170 | Constructor.prototype.render,
171 | 'createClass(...): Class specification must implement a `render` method.'
172 | );
173 |
174 | //工厂
175 | var ConvenienceConstructor = function (props, children) {
176 | return new Constructor(props, children);
177 | };
178 | ConvenienceConstructor.componentConstructor = Constructor;
179 | ConvenienceConstructor.originalSpec = spec;
180 | return ConvenienceConstructor;
181 | },
182 | ```
183 | - mixSpecIntoComponent 方法就是讲spec的属性赋值给Constructor的原型上
184 | - createClass返回一个ConvenienceConstructor构造函数,构造函数接受props、children 构造函数的静态方法componentConstructor和originalSpec分别指向Constructor和spec。
185 | - 有种类似于寄生组合式继承的写法,Constructor为每一个组件实例的原型(```var instance = new Constructor();
186 | instance.construct.apply(instance, arguments);```)。Constructor原型指向ReactCompositeComponentBase,又把构造器指向Constructor自己。然后把传入的spec合并到Constructor.prototype中。判断合并后的结果有没有render,如果没有 render,抛出一个异常
187 |
188 | 其实很多人看到这估计都会很疑惑,为毛这样搞???直接返回个构造函数不就可以了嘛。
189 |
190 | 其实react在后面做diff算法的时候,是采用组件的Constructor来判断组件是否相同的。如此可以保证每个createClass创建出来的组件都是一个新的Constructor。
191 |
192 | ok,那么我直接用寄生继承呀
193 | ```
194 | // 写法1
195 | const createClass = function(spec) {
196 | var Constructor = function (initialProps, children) {
197 | this.construct(initialProps, children);
198 | };
199 | Constructor.prototype = new ReactCompositeComponentBase();
200 | Constructor.prototype.constructor = Constructor;
201 | mixSpecIntoComponent(ReactCompositeComponentBase, spec)
202 | return Constructor
203 | }
204 | const Table1 = new createClass(spec)(props, children);
205 | //console.log(Table1.constructor)
206 | ```
207 | 为什么还需要ConvenienceConstructor呢?说实话,我也不知道,然后看了在网上查到相关信息说道:
208 |
209 | 上面写法在大多数情况下并不会产生什么问题,但是,当团队里的人无意中修改错点什么,比如:
210 | ```
211 | Table1.prototype.onClick = null
212 | ```
213 | 这样,所有Table1实例化的组件,onClick全部为修改后的空值
214 | ```
215 |
216 |
217 | ```
218 | 我们知道,js是动态解释型语言,函数可以运行时被随意篡改。而静态编译语言在运行时期间,函数不可修改(某些静态语言也可以修改)。所以采用这种方式防御用户对代码的篡改。
219 |
220 | ### 组件实例化
221 |
222 | 既然createClass返回的是一个构造函数,那么我们就来看看他的实例化吧
223 |
224 | ```
225 | /**
226 | * Base constructor for all React component.
227 | *
228 | * Subclasses that override this method should make sure to invoke
229 | * `ReactComponent.Mixin.construct.call(this, ...)`.
230 | *
231 | * @param {?object} initialProps
232 | * @param {*} children
233 | * @internal
234 | */
235 | construct: function (initialProps, children) {
236 | this.props = initialProps || {};
237 | if (typeof children !== 'undefined') {
238 | this.props.children = children;
239 | }
240 | // Record the component responsible for creating this component.
241 | this.props[OWNER] = ReactCurrentOwner.current;
242 | // All components start unmounted.
243 | this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
244 | },
245 | ```
246 | 其实也就是将props、children挂载到this.props上 以及生命周期的设置。这里暂且不说,因为我也正在看。。。哇咔咔
247 |
248 | 这里的
249 | ```
250 | this.props[OWNER] = ReactCurrentOwner.current;
251 | ```
252 | this.props[OWNER]指的是当前组件的容器(父)组件实例
253 |
254 | 如果我们直接在basic.html中打印就直接出来的是null,但是如果像如下的方式书写:
255 |
256 | ```
257 | const Children = React.createClass({
258 | componentDidMount = () => console.log(this.props["{owner}"]),
259 | render = () => null
260 | })
261 |
262 | const Parent = React.createClass({
263 | render: () =>
264 | })
265 | ```
266 |
267 | 这里输出的就是Parent组件实例。
268 |
269 | 再看看ReactCurrentOwner.current的赋值就明白了
270 |
271 | ```
272 | _renderValidatedComponent: function () {
273 | ReactCurrentOwner.current = this;
274 | var renderedComponent = this.render();
275 | ReactCurrentOwner.current = null;
276 | invariant(
277 | ReactComponent.isValidComponent(renderedComponent),
278 | '%s.render(): A valid ReactComponent must be returned.',
279 | this.constructor.displayName || 'ReactCompositeComponent'
280 | );
281 | return renderedComponent;
282 | }
283 | ```
284 | 可以看出来,在执行render前后,分别设置了ReactCurrentOwner.current的值,这样就能保证render函数内的子组件能赋上当前组件的实例,也就是this。
285 |
286 | ## 组件渲染
287 |
288 | 我们先撇开事务、事件池、生命周期、diff当然也包括fiber 等,先不谈,其实渲染就是将经过babel编译后的,当然这里是JSXTransformer.js编译后的Ojb给写入到HTML中而已。
289 |
290 | ```
291 | export default function render(vnode, parent) {
292 | let dom;
293 | if (typeof vnode === 'string') {
294 | dom = document.createTextNode(vnode);
295 | // let span_dom = document.createElement('span')
296 | // span_dom.appendChild(dom);
297 | // parent.appendChild(span_dom);
298 | parent.appendChild(dom);
299 | } else if (typeof vnode.nodeName === 'string') {
300 | dom = document.createElement(vnode.nodeName);
301 | setAttrs(dom, vnode.props);
302 | parent.appendChild(dom)
303 | for(let i = 0; i < vnode.children.length; i++) {
304 | render(vnode.children[i], dom)
305 | }
306 | }else if(typeof vnode.nodeName === 'function'){
307 | let innerVnode = vnode.nodeName.prototype.render();
308 | render(innerVnode,parent)
309 | }
310 | }
311 |
312 | function setAttrs(dom, props) {
313 | const ALL_KEYS = Object.keys(props);
314 |
315 | ALL_KEYS.forEach(k =>{
316 | const v = props[k];
317 |
318 | // className
319 | if(k === 'className'){
320 | dom.setAttribute('class',v);
321 | return;
322 | }
323 | if(k == "style") {
324 | if(typeof v == "string") {
325 | dom.style.cssText = v
326 | }
327 |
328 | if(typeof v == "object") {
329 | for (let i in v) {
330 | dom.style[i] = v[i]
331 | }
332 | }
333 | return
334 |
335 | }
336 |
337 | if(k[0] == "o" && k[1] == "n") {
338 | const capture = (k.indexOf("Capture") != -1)
339 | dom.addEventListener(k.substring(2).toLowerCase(),v,capture)
340 | return
341 | }
342 |
343 | dom.setAttribute(k, v)
344 | })
345 | }
346 | ```
347 | 是的,就这样
348 | 
349 |
350 | OK,回到源码~
351 |
352 | 
353 |
354 | 在我们目前使用的react版本中,渲染调用的是ReactDOM.render方法,这里ReactMount.renderComponent为我们的入口方法。
355 |
356 | ReactMount.renderComponent在react初探章节讲过。如果组件渲染过,就更新组件属性,如果组件没有渲染过,挂载组件事件,并把虚拟组件渲染成真实组件插入container内。通常,我们很少去调用两次renderComponent,所以大多数情况下不会更新组件属性而是新创建dom节点并插入到container中。
357 |
358 | ReactComponent.mountComponentIntoNode之内开启了一个事务,事务保证渲染阶段不会有任何事件触发,并阻断的componentDidMount事件,待执行后执行等,事务在功能一章我们会详细讲解,这里不细讨论。
359 | ReactComponent._mountComponentIntoNode这个函数调用mountComponent获得要渲染的innerHTML,然后更新container的innerHTML。
360 | ReactCompositeComponent.mountComponent是最主要的逻辑方法。这个函数内处理了react的生命周期以及componentWillComponent和componentDidMount生命周期钩子函数,调用render返回实际要渲染的内容,如果内容是复合组件,仍然会调用mountComponent,复合组件最终一定会返回原生组件, 并且最终调用ReactNativeComponent的mountComponent函数生成要渲染的innerHTML。
361 |
362 | 
363 |
364 | ```
365 | renderComponent: function(nextComponent, container) {
366 | var prevComponent = instanceByReactRootID[getReactRootID(container)];
367 | if (prevComponent) {
368 | var nextProps = nextComponent.props;
369 | ReactMount.scrollMonitor(container, function() {
370 | prevComponent.replaceProps(nextProps);
371 | });
372 | return prevComponent;
373 | }
374 |
375 | ReactMount.prepareTopLevelEvents(ReactEventTopLevelCallback);
376 |
377 | var reactRootID = ReactMount.registerContainer(container);
378 | instanceByReactRootID[reactRootID] = nextComponent;
379 | nextComponent.mountComponentIntoNode(reactRootID, container);
380 | return nextComponent;
381 | },
382 | ```
383 | 这段代码逻辑大概就是上面的流程图,这里不再赘述。
384 | - mountComponentIntoNode
385 | 从debugger中,可以看出mountComponentIntoNode第一个参数其实传入的是react分配给组件的一个唯一标识
386 | 
387 | ```
388 | mountComponentIntoNode: function(rootID, container) {
389 | var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
390 | transaction.perform(
391 | this._mountComponentIntoNode,
392 | this,
393 | rootID,
394 | container,
395 | transaction
396 | );
397 | ReactComponent.ReactReconcileTransaction.release(transaction);
398 | },
399 | ```
400 | 源码中,这里跟事务扯到了关系,其实我们只要关注渲染本身,所以这里我们直接看this._mountComponentIntoNode的方法实现
401 |
402 | - _mountComponentIntoNode
403 |
404 | ```
405 | _mountComponentIntoNode: function(rootID, container, transaction) {
406 | var renderStart = Date.now();
407 | var markup = this.mountComponent(rootID, transaction);
408 | ReactMount.totalInstantiationTime += (Date.now() - renderStart);
409 |
410 | var injectionStart = Date.now();
411 | // Asynchronously inject markup by ensuring that the container is not in
412 | // the document when settings its `innerHTML`.
413 | var parent = container.parentNode;
414 | if (parent) {
415 | var next = container.nextSibling;
416 | parent.removeChild(container);
417 | container.innerHTML = markup;
418 | if (next) {
419 | parent.insertBefore(container, next);
420 | } else {
421 | parent.appendChild(container);
422 | }
423 | } else {
424 | container.innerHTML = markup;
425 | }
426 | ReactMount.totalInjectionTime += (Date.now() - injectionStart);
427 | },
428 | ```
429 | 上述代码流程大概如下:
430 |
431 | 
432 |
433 | 流程的确如上,作为一个初探源码者,我当然不关心你到底是在哪innerHTML的,我想知道你是肿么把jsx编译后的Obj转成HTML的哇~
434 |
435 | 
436 |
437 | - ReactCompositeComponent.mountComponent
438 |
439 | 这里类变成了ReactCompositeComponent(debugger可以跟踪每一个函数)
440 |
441 | 
442 |
443 | 源码中的this.mountComponent,为什么不是调用ReactComponent.mountComponent呢?这里主要使用了多重继承机制(Mixin,后续讲解)。
444 | ```
445 | mountComponent: function(rootID, transaction) {
446 | // 挂在组件ref(等于当前组件实例)到this.refs上
447 | ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
448 |
449 | // Unset `this._lifeCycleState` until after this method is finished.
450 | // 这是生命周期
451 | this._lifeCycleState = ReactComponent.LifeCycle.UNMOUNTED;
452 | this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
453 |
454 | // 组件声明有props,执行校验
455 | if (this.constructor.propDeclarations) {
456 | this._assertValidProps(this.props);
457 | }
458 | // 为组件声明时间绑定this
459 | if (this.__reactAutoBindMap) {
460 | this._bindAutoBindMethods();
461 | }
462 | //获取state
463 | this.state = this.getInitialState ? this.getInitialState() : null;
464 | this._pendingState = null;
465 |
466 | // 如果组件声明componentWillMount函数,执行并把setState的结果更新到this.state上
467 | if (this.componentWillMount) {
468 | this.componentWillMount();
469 | // When mounting, calls to `setState` by `componentWillMount` will set
470 | // `this._pendingState` without triggering a re-render.
471 | if (this._pendingState) {
472 | this.state = this._pendingState;
473 | this._pendingState = null;
474 | }
475 | }
476 | // 如果声明了componentDidMount,则把其加入到ReactOnDOMReady队列中
477 | if (this.componentDidMount) {
478 | transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
479 | }
480 |
481 | // 调用组件声明的render函数,并返回ReactComponent抽象类实例(ReactComponsiteComponent或
482 | // ReactNativeComponent),调用相应的mountComponent函数
483 | this._renderedComponent = this._renderValidatedComponent();
484 |
485 | // Done with mounting, `setState` will now trigger UI changes.
486 | this._compositeLifeCycleState = null;
487 | this._lifeCycleState = ReactComponent.LifeCycle.MOUNTED;
488 |
489 | return this._renderedComponent.mountComponent(rootID, transaction);
490 | },
491 | ```
492 |
493 | 这个函数式VDom中最为重要的函数,操作也最为复杂,执行操作大概如下:
494 | 
495 |
496 | 如上,很多内容跟我们这part有点超纲。当然,后面都会说道,关于react的渲染,其实我们的工作很简单,不关于任何,在拿到render的东西后,如何解析,其实就是最后一行代码:```this._renderedComponent.mountComponent(rootID, transaction);```
497 |
498 | ```
499 | mountComponent: function(rootID, transaction) {
500 | ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
501 | assertValidProps(this.props);
502 | return (
503 | this._createOpenTagMarkup() +
504 | this._createContentMarkup(transaction) +
505 | this._tagClose
506 | );
507 | },
508 | _createOpenTagMarkup: function() {
509 | var props = this.props;
510 | var ret = this._tagOpen;
511 |
512 | for (var propKey in props) {
513 | if (!props.hasOwnProperty(propKey)) {
514 | continue;
515 | }
516 | var propValue = props[propKey];
517 | if (propValue == null) {
518 | continue;
519 | }
520 | if (registrationNames[propKey]) {
521 | putListener(this._rootNodeID, propKey, propValue);
522 | } else {
523 | if (propKey === STYLE) {
524 | if (propValue) {
525 | propValue = props.style = merge(props.style);
526 | }
527 | propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
528 | }
529 | var markup =
530 | DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
531 | if (markup) {
532 | ret += ' ' + markup;
533 | }
534 | }
535 | }
536 |
537 | return ret + ' id="' + this._rootNodeID + '">';
538 | },
539 |
540 | /**
541 | * Creates markup for the content between the tags.
542 | *
543 | * @private
544 | * @param {ReactReconcileTransaction} transaction
545 | * @return {string} Content markup.
546 | */
547 | _createContentMarkup: function(transaction) {
548 | // Intentional use of != to avoid catching zero/false.
549 | var innerHTML = this.props.dangerouslySetInnerHTML;
550 | if (innerHTML != null) {
551 | if (innerHTML.__html != null) {
552 | return innerHTML.__html;
553 | }
554 | } else {
555 | var contentToUse = this.props.content != null ? this.props.content :
556 | CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
557 | var childrenToUse = contentToUse != null ? null : this.props.children;
558 | if (contentToUse != null) {
559 | return escapeTextForBrowser(contentToUse);
560 | } else if (childrenToUse != null) {
561 | return this.mountMultiChild(
562 | flattenChildren(childrenToUse),
563 | transaction
564 | );
565 | }
566 | }
567 | return '';
568 | },
569 | function ReactNativeComponent(tag, omitClose) {
570 | this._tagOpen = '<' + tag + ' ';
571 | this._tagClose = omitClose ? '' : '' + tag + '>';
572 | this.tagName = tag.toUpperCase();
573 | }
574 | ```
575 |
576 | 代码稍微多一点,但是工作目标很单一,就是为了将描述jsx的obj解析成HTML string。其实可以参照我上面直接亮出来的自己写的代码部分。
577 |
578 | 如上,其实我们已经完成了组件的初始化、渲染~
579 |
580 | 
581 |
582 | 好吧,我们一直说的渲染的核心部分还没有细说~~~
583 |
584 |
585 | ### 挂载组件ref到this.refs上,设置生命周期、状态和rootID
586 | ```
587 | mountComponent: function(rootID, transaction) {
588 | invariant(
589 | this._lifeCycleState === ComponentLifeCycle.UNMOUNTED,
590 | 'mountComponent(%s, ...): Can only mount an unmounted component.',
591 | rootID
592 | );
593 | var props = this.props;
594 | if (props.ref != null) {
595 | ReactOwner.addComponentAsRefTo(this, props.ref, props[OWNER]);
596 | }
597 | this._rootNodeID = rootID;
598 | this._lifeCycleState = ComponentLifeCycle.MOUNTED;
599 | // Effectively: return '';
600 | },
601 | ```
602 | 如果组件ref属性为空,则为组件的this.refs上挂在当前组件,也就是this,实现如下:
603 | ```
604 | addComponentAsRefTo: function(component, ref, owner) {
605 | owner.attachRef(ref, component);
606 | }
607 | ```
608 | ```
609 | attachRef: function(ref, component) {
610 | var refs = this.refs || (this.refs = {});
611 | refs[ref] = component;
612 | },
613 | ```
614 | 上述代码我删除了相关的判断警告。
615 |
616 | ### 设置组件生命状态
617 |
618 | 组件的生命状态和生命周期钩子函数是react的两个概念,在react中存在两种生命周期
619 | - 主:组件生命周期:_lifeCycleState,用来校验react组件在执行函数时状态值是否正确
620 | - 辅:复合组件生命周期:_componsiteLifeCycleState,用来保证setState流程不受其他行为影响
621 |
622 | #### _lifeCycleState
623 |
624 | ```
625 | var ComponentLifeCycle = keyMirror({
626 | /**
627 | * Mounted components have a DOM node representation and are capable of
628 | * receiving new props.
629 | */
630 | MOUNTED: null,
631 | /**
632 | * Unmounted components are inactive and cannot receive new props.
633 | */
634 | UNMOUNTED: null
635 | });
636 | ```
637 | 组件生命周期非常简单,就枚举了两种,MOUNTED and UNMOUNTED
638 |
639 | 在源码中使用其只是为了在相应的阶段触发时候校验,并且给出错误提示
640 | ```
641 | getDOMNode: function() {
642 | invariant(
643 | ExecutionEnvironment.canUseDOM,
644 | 'getDOMNode(): The DOM is not supported in the current environment.'
645 | );
646 | invariant(
647 | this._lifeCycleState === ComponentLifeCycle.MOUNTED,
648 | 'getDOMNode(): A component must be mounted to have a DOM node.'
649 | );
650 | var rootNode = this._rootNode;
651 | if (!rootNode) {
652 | rootNode = document.getElementById(this._rootNodeID);
653 | if (!rootNode) {
654 | // TODO: Log the frequency that we reach this path.
655 | rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
656 | }
657 | this._rootNode = rootNode;
658 | }
659 | return rootNode;
660 | },
661 | ```
662 | #### _compositeLifeCycleState
663 | 复合组件的生命周期只在一个地方使用:setState
664 | ```
665 | var CompositeLifeCycle = keyMirror({
666 | /**
667 | * Components in the process of being mounted respond to state changes
668 | * differently.
669 | */
670 | MOUNTING: null,
671 | /**
672 | * Components in the process of being unmounted are guarded against state
673 | * changes.
674 | */
675 | UNMOUNTING: null,
676 | /**
677 | * Components that are mounted and receiving new props respond to state
678 | * changes differently.
679 | */
680 | RECEIVING_PROPS: null,
681 | /**
682 | * Components that are mounted and receiving new state are guarded against
683 | * additional state changes.
684 | */
685 | RECEIVING_STATE: null
686 | });
687 | ```
688 |
689 | ```
690 | replaceState: function(completeState) {
691 | var compositeLifeCycleState = this._compositeLifeCycleState;
692 | invariant(
693 | this._lifeCycleState === ReactComponent.LifeCycle.MOUNTED ||
694 | compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
695 | 'replaceState(...): Can only update a mounted (or mounting) component.'
696 | );
697 | invariant(
698 | compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
699 | compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
700 | 'replaceState(...): Cannot update while unmounting component or during ' +
701 | 'an existing state transition (such as within `render`).'
702 | );
703 |
704 | this._pendingState = completeState;
705 |
706 | // Do not trigger a state transition if we are in the middle of mounting or
707 | // receiving props because both of those will already be doing this.
708 | if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
709 | compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
710 | this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
711 |
712 | var nextState = this._pendingState;
713 | this._pendingState = null;
714 |
715 | var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
716 | transaction.perform(
717 | this._receivePropsAndState,
718 | this,
719 | this.props,
720 | nextState,
721 | transaction
722 | );
723 | ReactComponent.ReactReconcileTransaction.release(transaction);
724 |
725 | this._compositeLifeCycleState = null;
726 | }
727 | },
728 | ```
729 | setState会调用replaceState ,然后调用_receivePropsAndState来更新界面
730 |
731 | 如果组件正处在mounting的过程或者接受props的过程中,那么将state缓存在_pendingState中,并不会更新界面的值。
732 |
733 | ### 校验props
734 |
735 | ```
736 | _assertValidProps: function(props) {
737 | var propDeclarations = this.constructor.propDeclarations;
738 | var componentName = this.constructor.displayName;
739 | for (var propName in propDeclarations) {
740 | var checkProp = propDeclarations[propName];
741 | if (checkProp) {
742 | checkProp(props, propName, componentName);
743 | }
744 | }
745 | }
746 | ```
747 |
748 | this.constructor.propDeclarations 就是组件声明的props属性,由于props是运行时传入的属性。我们可以看到声明props的属性值即为checkProp
749 |
750 | ## 结束语
751 |
752 | 其实至此,关于本篇组件的初始化、渲染已经介绍完毕,由于代码中关于太多后续章节,生命周期、props、state、对象缓冲池、事务等,所以暂时都先略过,后续学习到的时候再回头查阅。
753 |
754 |
755 |
--------------------------------------------------------------------------------
/2018/React源码分析与实现(二):状态、属性更新 -> setState.md:
--------------------------------------------------------------------------------
1 | # React源码分析与实现(二):状态、属性更新 -> setState
2 |
3 | > 原文链接地址:[https://github.com/Nealyang](https://github.com/Nealyang/PersonalBlog/blob/master/2018/React%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%8E%E5%AE%9E%E7%8E%B0(%E4%BA%8C)%EF%BC%9A%E7%8A%B6%E6%80%81%E3%80%81%E5%B1%9E%E6%80%A7%E6%9B%B4%E6%96%B0%20-%3E%20setState.md) 转载请注明出处
4 |
5 | ## 状态更新
6 |
7 | > 此次分析setState基于0.3版本,实现比较简单,后续会再分析目前使用的版本以及事务机制。
8 |
9 | 流程图大概如下
10 |
11 | 
12 |
13 |
14 | setState的源码比较简单,而在执行更新的过程比较复杂。我们直接跟着源码一点一点屡清楚。
15 |
16 | - ReactCompositeComponent.js
17 |
18 | ```
19 | /**
20 | * Sets a subset of the state. Always use this or `replaceState` to mutate
21 | * state. You should treat `this.state` as immutable.
22 | *
23 | * There is no guarantee that `this.state` will be immediately updated, so
24 | * accessing `this.state` after calling this method may return the old value.
25 | *
26 | * @param {object} partialState Next partial state to be merged with state.
27 | * @final
28 | * @protected
29 | */
30 | setState: function(partialState) {
31 | // Merge with `_pendingState` if it exists, otherwise with existing state.
32 | this.replaceState(merge(this._pendingState || this.state, partialState));
33 | },
34 | ```
35 | 注释部分说的很明确,setState后我们不能够立即拿到我们设置的值。
36 |
37 | 而这段代码也非常简单,就是将我们传入的state和this._pendingState做一次merge,merge的代码在util.js下
38 | ```
39 | var merge = function(one, two) {
40 | var result = {};
41 | mergeInto(result, one);
42 | mergeInto(result, two);
43 | return result;
44 | };
45 |
46 | function mergeInto(one, two) {
47 | checkMergeObjectArg(one);
48 | if (two != null) {
49 | checkMergeObjectArg(two);
50 | for (var key in two) {
51 | if (!two.hasOwnProperty(key)) {
52 | continue;
53 | }
54 | one[key] = two[key];
55 | }
56 | }
57 | }
58 |
59 | checkMergeObjectArgs: function(one, two) {
60 | mergeHelpers.checkMergeObjectArg(one);
61 | mergeHelpers.checkMergeObjectArg(two);
62 | },
63 |
64 | /**
65 | * @param {*} arg
66 | */
67 | checkMergeObjectArg: function(arg) {
68 | throwIf(isTerminal(arg) || Array.isArray(arg), ERRORS.MERGE_CORE_FAILURE);
69 | },
70 |
71 | var isTerminal = function(o) {
72 | return typeof o !== 'object' || o === null;
73 | };
74 |
75 | var throwIf = function(condition, err) {
76 | if (condition) {
77 | throw new Error(err);
78 | }
79 | };
80 | ```
81 |
82 | 诊断代码的逻辑非常简单,其实功能就是```Object.assign()``` ,但是从上面代码我们可以看出react源码中的function大多都具有小而巧的特点。
83 |
84 |
85 | 最终,将merge后的结果传递给```replaceState ```
86 |
87 | ```
88 | replaceState: function(completeState) {
89 | var compositeLifeCycleState = this._compositeLifeCycleState;
90 | invariant(
91 | this._lifeCycleState === ReactComponent.LifeCycle.MOUNTED ||
92 | compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
93 | 'replaceState(...): Can only update a mounted (or mounting) component.'
94 | );
95 | invariant(
96 | compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
97 | compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
98 | 'replaceState(...): Cannot update while unmounting component or during ' +
99 | 'an existing state transition (such as within `render`).'
100 | );
101 |
102 | this._pendingState = completeState;
103 |
104 | // Do not trigger a state transition if we are in the middle of mounting or
105 | // receiving props because both of those will already be doing this.
106 | if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
107 | compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
108 | this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
109 |
110 | var nextState = this._pendingState;
111 | this._pendingState = null;
112 |
113 | var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
114 | transaction.perform(
115 | this._receivePropsAndState,
116 | this,
117 | this.props,
118 | nextState,
119 | transaction
120 | );
121 | ReactComponent.ReactReconcileTransaction.release(transaction);
122 |
123 | this._compositeLifeCycleState = null;
124 | }
125 | },
126 | ```
127 |
128 | 撇开50% 判断warning代码不说,从上面代码我们可以看出,只有在componsiteLifeState不等于mounting和receiving_props 时,才会调用 _receivePropsAndState函数来更新组件。
129 |
130 | 我们可以演示下:
131 | ```
132 | var ExampleApplication = React.createClass({
133 | getInitialState() {
134 | return {}
135 | },
136 | componentWillMount() {
137 | this.setState({
138 | a: 1,
139 | })
140 | console.log('componentWillMount', this.state.a)
141 | this.setState({
142 | a: 2,
143 | })
144 | console.log('componentWillMount', this.state.a)
145 | this.setState({
146 | a: 3,
147 | })
148 | console.log('componentWillMount', this.state.a)
149 | setTimeout(() => console.log('a5'), 0)
150 | setTimeout(() => console.log(this.state.a,'componentWillMount'))
151 |
152 | Promise.resolve('a4').then(console.log)
153 | },
154 |
155 | componentDidMount() {
156 | this.setState({
157 | a: 4,
158 | })
159 | console.log('componentDidMount', this.state.a)
160 | this.setState({
161 | a: 5,
162 | })
163 | console.log('componentDidMount', this.state.a)
164 | this.setState({
165 | a: 6,
166 | })
167 | console.log('componentDidMount', this.state.a)
168 | },
169 | render: function () {
170 | var elapsed = Math.round(this.props.elapsed / 100);
171 | var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0');
172 | var message =
173 | 'React has been successfully running for ' + seconds + ' seconds.';
174 | return React.DOM.p(null, message);
175 | }
176 | });
177 | ```
178 | 
179 |
180 | 所以以上结果我们可以看出,在componentWillMount生命周期内setState后this.state不会改变,在componentDidMount是正常的。因为在上一篇文章中我们也有说到,在mountComponent过程中,会把compositeLifeCycleState设置为MOUNTING状态,在这个过程中,是不会执行receivePropsAndState的,所以this.state也就不会更新,同理,在receivePropsAndState的过程中,会把compositeLifeCycleState置成RECEIVING_PROPS状态,也不会执行state更新以及render执行,在updateComponent过程中又执行了mountComponent函数,mountComponent函数调用了render函数。
181 |
182 | 而在现在我们使用16或者15版本中,我们发现:
183 |
184 | ```
185 | componentDidMount() {
186 | this.setState({val: this.state.val + 1});
187 | console.log(this.state.val); // 第 1 次 log
188 |
189 | this.setState({val: this.state.val + 1});
190 | console.log(this.state.val); // 第 2 次 log
191 |
192 | setTimeout(() => {
193 | this.setState({val: this.state.val + 1});
194 | console.log(this.state.val); // 第 3 次 log
195 |
196 | this.setState({val: this.state.val + 1});
197 | console.log(this.state.val); // 第 4 次 log
198 | }, 0);
199 | }
200 | ```
201 | 最后打印的结果为:0,0,2,3
202 |
203 | 
204 |
205 | 为什么有这样呢?其实源于源码中的这段代码:
206 |
207 | ```
208 | function enqueueUpdate(component) {
209 | ensureInjected();
210 |
211 | // Various parts of our code (such as ReactCompositeComponent's
212 | // _renderValidatedComponent) assume that calls to render aren't nested;
213 | // verify that that's the case. (This is called by each top-level update
214 | // function, like setProps, setState, forceUpdate, etc.; creation and
215 | // destruction of top-level components is guarded in ReactMount.)
216 |
217 | if (!batchingStrategy.isBatchingUpdates) {
218 | batchingStrategy.batchedUpdates(enqueueUpdate, component);
219 | return;
220 | }
221 |
222 | dirtyComponents.push(component);
223 | }
224 | ```
225 | 因为这里涉及到事务的概念、批量更新以及benchUpdate等,在我们目前分析的版本中还未迭代上去,后面我们会跟着版本升级慢慢说道。
226 |
227 | 
228 |
229 | ## 属性更新
230 |
231 | 首先我们知道,属性的更新必然是由于state的更新,所以其实组件属性的更新流程就是setState执行更新的延续,换句话说,也就是setState才能出发组件属性的更新,源码里就是我在处理state更新的时候,顺带检测了属性的更新。所以这段源码的开始,还是从setState中看
232 |
233 | ```
234 | _receivePropsAndState: function(nextProps, nextState, transaction) {
235 | if (!this.shouldComponentUpdate ||
236 | this.shouldComponentUpdate(nextProps, nextState)) {
237 | this._performComponentUpdate(nextProps, nextState, transaction);
238 | } else {
239 | this.props = nextProps;
240 | this.state = nextState;
241 | }
242 | },
243 | ```
244 | 代码非常的简单,一句话解释:当shouldComponentUpdate为true时,则执行更新操作。
245 |
246 | ```
247 | _performComponentUpdate: function(nextProps, nextState, transaction) {
248 | var prevProps = this.props;
249 | var prevState = this.state;
250 |
251 | if (this.componentWillUpdate) {
252 | this.componentWillUpdate(nextProps, nextState, transaction);
253 | }
254 |
255 | this.props = nextProps;
256 | this.state = nextState;
257 |
258 | this.updateComponent(transaction);
259 |
260 | if (this.componentDidUpdate) {
261 | transaction.getReactOnDOMReady().enqueue(
262 | this,
263 | this.componentDidUpdate.bind(this, prevProps, prevState)
264 | );
265 | }
266 | },
267 | ```
268 | 这段代码的核心就是调用```this.updateComponent```,然后对老的属性和状态存一下,新的更新一下而已。如果存在componentWillUpdate就执行一下,然后走更新流程。最后是把执行componentDidUpdate推入getReactOnDOMReady的队列中,等待组件的更新。
269 | ```
270 | _renderValidatedComponent: function() {
271 | ReactCurrentOwner.current = this;
272 | var renderedComponent = this.render();
273 | ReactCurrentOwner.current = null;
274 | return renderedComponent;
275 | },
276 | ...
277 | ...
278 | updateComponent: function(transaction) {
279 | var currentComponent = this._renderedComponent;
280 | var nextComponent = this._renderValidatedComponent();
281 | if (currentComponent.constructor === nextComponent.constructor) {
282 | if (!nextComponent.props.isStatic) {
283 | currentComponent.receiveProps(nextComponent.props, transaction);
284 | }
285 | } else {
286 | var thisID = this._rootNodeID;
287 | var currentComponentID = currentComponent._rootNodeID;
288 | currentComponent.unmountComponent();
289 | var nextMarkup = nextComponent.mountComponent(thisID, transaction);
290 | ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
291 | currentComponentID,
292 | nextMarkup
293 | );
294 | this._renderedComponent = nextComponent;
295 | }
296 | },
297 | ```
298 | 这里我们直接看```updateComponent```更新流程,首先获取当前render函数的组件,然后获取下一次render函数的组件,```_renderValidatedComponent```就是获取下一次的render组件。 通过Constructor来判断组件是否相同,如果相同且组件为非静态,则更新组件的属性,否则卸载当前组件,然后重新mount下一个render组件并且直接暴力更新。
299 |
300 | 接着会调用render组件的receiveProps方法,其实一开始这个地方我也是非常困惑的,this指向傻傻分不清楚,后来经过各种查阅资料知道,它其实是一个多态方法,如果是复合组件,则执行ReactCompositeComponent.receiveProps,如果是原生组件,则执行ReactNativeComponent.receiveProps。源码分别如下:
301 |
302 | ```
303 | receiveProps: function(nextProps, transaction) {
304 | if (this.constructor.propDeclarations) {
305 | this._assertValidProps(nextProps);
306 | }
307 | ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
308 |
309 | this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
310 | if (this.componentWillReceiveProps) {
311 | this.componentWillReceiveProps(nextProps, transaction);
312 | }
313 | this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
314 | var nextState = this._pendingState || this.state;
315 | this._pendingState = null;
316 | this._receivePropsAndState(nextProps, nextState, transaction);
317 | this._compositeLifeCycleState = null;
318 | },
319 | ```
320 |
321 | 有人可能注意到这里的this._receivePropsAndState函数,这不是刚才调用过么?怎么又调用一遍?没错,调用这个的this已经是currentComponent了,并不是上一个this。currentComponent是当前组件的render组件,也就是当前组件的子组件。子组件同样也可能是复合组件或者原生组件。正式通过这种多态的方式,递归的解析每级嵌套组件。最终完成从当前组件到下面的所有叶子节点的树更新。
322 |
323 | 其实话说回来,compositeComponent最终还是会遍历递归到解析原生组件,通过我们整体浏览下ReactNativeComponent.js代码可以看出。
324 |
325 | 
326 |
327 | 我们先从 receiveProps方法开始看
328 | ```
329 | receiveProps: function(nextProps, transaction) {
330 | assertValidProps(nextProps);
331 | ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
332 | this._updateDOMProperties(nextProps);
333 | this._updateDOMChildren(nextProps, transaction);
334 | this.props = nextProps;
335 | },
336 |
337 | function assertValidProps(props) {
338 | if (!props) {
339 | return;
340 | }
341 | var hasChildren = props.children != null ? 1 : 0;
342 | var hasContent = props.content != null ? 1 : 0;
343 | var hasInnerHTML = props.dangerouslySetInnerHTML != null ? 1 : 0;
344 | }
345 | ```
346 | 删除安全警告和注释其实代码非常简答,首先assertValidProps就是校验props是否合法的,更新属性的方法就是```_updateDOMProperties```
347 |
348 | ```
349 | _updateDOMProperties: function(nextProps) {
350 | var lastProps = this.props;
351 | for (var propKey in nextProps) {
352 | var nextProp = nextProps[propKey];
353 | var lastProp = lastProps[propKey];
354 | //判断新老属性中的值是否相等
355 | if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
356 | continue;
357 | }
358 | //如果是style样式,遍历新style,如果去旧style不相同,则把变化的存入styleUpdates对象中。最后调用 updateStylesByID 统一修改dom的style属性。
359 | if (propKey === STYLE) {
360 | if (nextProp) {
361 | nextProp = nextProps.style = merge(nextProp);
362 | }
363 | var styleUpdates;
364 | for (var styleName in nextProp) {
365 | if (!nextProp.hasOwnProperty(styleName)) {
366 | continue;
367 | }
368 | if (!lastProp || lastProp[styleName] !== nextProp[styleName]) {
369 | if (!styleUpdates) {
370 | styleUpdates = {};
371 | }
372 | styleUpdates[styleName] = nextProp[styleName];
373 | }
374 | }
375 | if (styleUpdates) {
376 | ReactComponent.DOMIDOperations.updateStylesByID(
377 | this._rootNodeID,
378 | styleUpdates
379 | );
380 | }
381 | } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
382 | var lastHtml = lastProp && lastProp.__html;
383 | var nextHtml = nextProp && nextProp.__html;
384 | if (lastHtml !== nextHtml) {
385 | ReactComponent.DOMIDOperations.updateInnerHTMLByID(//注意这里是innerHtml,所以dangerouslyInnerHTML会展示正常的HTML
386 | this._rootNodeID,
387 | nextProp
388 | );
389 | }
390 | } else if (propKey === CONTENT) {
391 | ReactComponent.DOMIDOperations.updateTextContentByID(//这里是innerText,所以content与children原封不动的把HTML代码打印到页面上
392 | this._rootNodeID,
393 | '' + nextProp
394 | );
395 | } else if (registrationNames[propKey]) {
396 | putListener(this._rootNodeID, propKey, nextProp);
397 | } else {
398 | ReactComponent.DOMIDOperations.updatePropertyByID(
399 | this._rootNodeID,
400 | propKey,
401 | nextProp
402 | );
403 | }
404 | }
405 | },
406 | ```
407 | 这里面方法没有太多的hack技巧,非常的简单直白,不单独拧出来说,我直接写到注释里面了。
408 |
409 | 最后直接更新组件的属性
410 | ```
411 | setValueForProperty: function(node, name, value) {
412 | if (DOMProperty.isStandardName[name]) {
413 | var mutationMethod = DOMProperty.getMutationMethod[name];
414 | if (mutationMethod) {
415 | mutationMethod(node, value);
416 | } else if (DOMProperty.mustUseAttribute[name]) {
417 | if (DOMProperty.hasBooleanValue[name] && !value) {
418 | node.removeAttribute(DOMProperty.getAttributeName[name]);
419 | } else {
420 | node.setAttribute(DOMProperty.getAttributeName[name], value);
421 | }
422 | } else {
423 | var propName = DOMProperty.getPropertyName[name];
424 | if (!DOMProperty.hasSideEffects[name] || node[propName] !== value) {
425 | node[propName] = value;
426 | }
427 | }
428 | } else if (DOMProperty.isCustomAttribute(name)) {
429 | node.setAttribute(name, value);
430 | }
431 | }
432 | ```
433 |
434 | 整体属性更新的流程图大概如下:
435 |
436 | 
437 |
438 | ## 结束语
439 |
440 | 通篇读完,是不是有种
441 |
442 | 
443 |
444 | react源码中包含很多的点的知识,比如我们之前说的VDOM、包括后面要去学习dom-diff、事务、缓存等等,都是一个点,而但从一个点来切入难免有的会有些枯燥没卵用,别急别急~
445 |
446 | 
--------------------------------------------------------------------------------
/2018/函数式编程了解一下(下).md:
--------------------------------------------------------------------------------
1 | # 函数式编程了解一下(下)
2 |
3 | ## 回顾柯里化、偏应用
4 |
5 | [函数式编程了解一下(上)](https://juejin.im/post/5ad36da76fb9a028c71ee65b)
6 |
7 | 对于上一篇文章,有朋友群里艾特说不是很明白柯里化的函数,这里我们拿出来简单说下
8 |
9 | ```
10 | let curry = (fn) =>{
11 | if(typeof fn !== 'function'){
12 | throw Error('No Function');
13 | }
14 |
15 | return function curriedFn(...args){
16 | if(args.length < fn.length){
17 | return function(){
18 | return curriedFn.apply(null,args.concat([].slice.call(arguments)));
19 | }
20 | }
21 | return fn.apply(null,args);
22 | }
23 | }
24 |
25 | function add (a,b,c) { return a+b+c }
26 |
27 | curry(add)(1)(2)(3)
28 | ```
29 |
30 | 一步一步来理解,第一次调用curry函数的时候,返回一个curried函数,待调用状态,当我们传入1的时候,返回的依旧是一个函数,args是利用闭包,记录你传入的参数是否为函数定义时候的参数个数,如果不是,那我接着等待你在传入。因为我们利用args来记录每次传入的值,所以我们每次拿curry函数后的传入的参数就必须使用arguments了,由于它是类数组,我们想拿到参数值,所以这里我们使用slice。最终,我们其实还是调用a+b+c的运算。
31 |
32 | 同理,偏应用的存在其实就是弥补了柯里化传参顺序的短板
33 |
34 | ```
35 | const partial = function (fn,...partialArgs){
36 | let args = partialArgs;
37 | return function(...fullArgs){
38 | let arg = 0;
39 | for(let i = 0; i console.log('this is Nealyang'));
51 | ```
52 |
53 | 同样利用闭包存储参数,利用undefined来占位
54 |
55 |
56 | ## 组合、管道
57 |
58 | ### 概念
59 | 官方解释为,函数式编程中的函数组合被称之为组合。说的云里雾里的,其实就是多个函数一起完成一件事,组合嘛。那管道呢?咱通俗点,类似gulp的pipe概念,你处理完了,吐出来,我接着处理(此处不禁想起人体蜈蚣,哇~),咳咳,正式点,将最左侧的函数输出所为输入发送给右侧函数,从技术上来说,就是管道。
60 |
61 | 为什么要这样呢?其实还是我们之前说的,函数的原则就是小、单一、简单。因为易测、简单。而我们呢,通过组合使用这些简单的函数而实现一个不简单的函数,完成一个不简单的功能。是不是类似于React编写组件的概念。通过组合各种小组件完成页面编写的感觉?
62 |
63 | bingo~
64 |
65 | ### compose 函数的实现
66 | 先看一个简答的实现
67 | ```
68 | const compose = (a,b)=>(c)=>a(b(c));
69 |
70 | let splitIntoSpaces = (str) => str.split(" ");
71 |
72 | let count = (array) => array.length;
73 |
74 | const countWords = compose(count,splitIntoSpaces);
75 |
76 | countWords('Hello , I am Nealyang');
77 | ```
78 |
79 | 在后面的开发中,我们只需要通过countWords就可以统计出单词的数量,通过这种方式实现的也非常的优雅。
80 |
81 | 其实这种编写的技巧就是将多个小而巧的函数组合完成不一样的功效出来。举个栗子:
82 |
83 | ```
84 | let map = (array,fn) => {
85 | let results = []
86 | for(const value of array)
87 | results.push(fn(value))
88 |
89 | return results;
90 | };
91 | let filter = (array,fn) => {
92 | let results = []
93 | for(const value of array)
94 | (fn(value)) ? results.push(value) : undefined
95 |
96 | return results;
97 | };
98 | let apressBooks = [
99 | {
100 | "id": 111,
101 | "title": "C# 6.0",
102 | "author": "ANDREW TROELSEN",
103 | "rating": [4.7],
104 | "reviews": [{good : 4 , excellent : 12}]
105 | },
106 | {
107 | "id": 222,
108 | "title": "Efficient Learning Machines",
109 | "author": "Rahul Khanna",
110 | "rating": [4.5],
111 | "reviews": []
112 | },
113 | {
114 | "id": 333,
115 | "title": "Pro AngularJS",
116 | "author": "Adam Freeman",
117 | "rating": [4.0],
118 | "reviews": []
119 | },
120 | {
121 | "id": 444,
122 | "title": "Pro ASP.NET",
123 | "author": "Adam Freeman",
124 | "rating": [4.2],
125 | "reviews": [{good : 14 , excellent : 12}]
126 | }
127 | ];
128 |
129 | const compose = (a, b) =>
130 | (c) => a(b(c))
131 |
132 | const partial = function (fn,...partialArgs){
133 | let args = partialArgs.slice(0);
134 | return function(...fullArguments) {
135 | let arg = 0;
136 | for (let i = 0; i < args.length && arg < fullArguments.length; i++) {
137 | if (args[i] === undefined) {
138 | args[i] = fullArguments[arg++];
139 | }
140 | }
141 | return fn.apply(this, args);
142 | };
143 | };
144 |
145 | console.log("筛选结果",map(filter(apressBooks, (book) => book.rating[0] > 4.5),(book) => {
146 | return {title: book.title,author:book.author}
147 | }))
148 | //工具类函数
149 | let filterOutStandingBooks = (book) => book.rating[0] === 5;
150 | let filterGoodBooks = (book) => book.rating[0] > 4.5;
151 | let filterBadBooks = (book) => book.rating[0] < 3.5;
152 |
153 | let projectTitleAndAuthor = (book) => { return {title: book.title,author:book.author} }
154 | let projectAuthor = (book) => { return {author:book.author} }
155 | let projectTitle = (book) => { return {title: book.title} }
156 |
157 | let queryGoodBooks = partial(filter,undefined,filterGoodBooks);
158 | let mapTitleAndAuthor = partial(map,undefined,projectTitleAndAuthor)
159 |
160 | let titleAndAuthorForGoodBooks = compose(mapTitleAndAuthor,queryGoodBooks)
161 |
162 | console.log(titleAndAuthorForGoodBooks(apressBooks))
163 |
164 | let mapTitle = partial(map,undefined,projectTitle)
165 | let titleForGoodBooks = compose(mapTitle,queryGoodBooks)
166 |
167 | //console.log(titleForGoodBooks(apressBooks))
168 | ```
169 |
170 | 通过如上的代码,我们可以很轻松的看出通过组合这些小函数,而实现很多功能。非常的灵活。
171 |
172 | ### 多个函数的组合
173 | 当前版本的compose只实现了俩个函数的组合,那么如果对于多个函数呢?
174 |
175 | ```
176 | const compose = (...fns) => (value) => reduce(fns.reverse(),(acc , fn ) => fn(acc),value);
177 | ```
178 |
179 | 上面最主要的一行是
180 | ```
181 | reduce(fns.reverse(),(acc , fn ) => fn(acc),value)
182 | ```
183 | 此处我们首先fns.reverse()反转了函数数组,并传入了函数(acc,fn)=>fn(acc) ,它会以传入的acc作为其参数依次调用每一个函数。很显然,累加器的初始值为value,它将作为函数的第一个输入。
184 |
185 | ```
186 | const composeN = (...fns) =>
187 | (value) =>
188 | reduce(fns.reverse(),(acc, fn) => fn(acc), value);
189 |
190 | const pipe = (...fns) =>
191 | (value) =>
192 | reduce(fns,(acc, fn) => fn(acc), value);
193 |
194 | let oddOrEven = (ip) => ip % 2 == 0 ? "even" : "odd"
195 | var oddOrEvenWords = composeN(oddOrEven,count,splitIntoSpaces);
196 | let count = (array) => array.length;
197 | console.log(oddOrEvenWords("hello your reading about composition"))
198 |
199 | oddOrEvenWords = pipe(splitIntoSpaces,count,oddOrEven);
200 | console.log(oddOrEvenWords("hello your reading about composition"))
201 | ```
202 | 如上的代码,有没有发现composeN和pipe非常的相似?其实就是执行序列的不同而已。从左至右处理数据流我们称之为管道。
203 |
204 | ## 函子
205 |
206 | ### 概念
207 | 在编写代码中的时候,我们肯定会涉及到关于错误的处理,而我们现在涉及到的新名词:函子,其实也不是什么高大上的东西,简单的说就是在函数式编程中的一种错误处理方式。我们用这种纯函数的方式来帮助我们处理错误。
208 |
209 | > 函子是一个普通对象,它实现了map函数,在遍历每一个对象的时候生成新的对象
210 |
211 | ### 一步步梳理概念
212 |
213 | 首先我们可以将函子理解为容器。
214 |
215 | ```
216 | const Container = function(val){
217 | this.value = val;
218 | }
219 | ```
220 | 优化上面的容器,我们给Container添加一个of的静态方法,就是用来创建对象的
221 | ```
222 | Container.of = function(val){
223 | return new Container(val);
224 | }
225 | ```
226 | 到这一步,我们再回头看概念,函子是一个普通对象,它实现了一个map函数。。。,所以下一步,我们就来实现一个map函数吧
227 |
228 | ```
229 | Container.property.map = function(fn){
230 | return Container.of(fn(this.value));
231 | }
232 | ```
233 |
234 | 如上,我们就编写除了一个函子,是不是也就那么回事?所以有哥们会问了,咱编写这个干嘛呢?有啥用?啊哈,咱接着往下看呗
235 |
236 | ### MayBe 函子
237 | MayBe函子能够让我们能够以更加函数式的方式处理错误
238 |
239 | 简单的看下具体的实现吧
240 | ```
241 | const MayBe = function(val) {
242 | this.value = val;
243 | }
244 |
245 | MayBe.of = function(val) {
246 | return new MayBe(val);
247 | }
248 |
249 | MayBe.prototype.isNothing = function() {
250 | return (this.value === null || this.value === undefined);
251 | };
252 |
253 | MayBe.prototype.map = function(fn) {
254 | return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.value));
255 | };
256 |
257 | console.log("MayBe chaining",MayBe.of("George")
258 | .map((x) => x.toUpperCase())
259 | .map((x) => "Mr. " + x))
260 |
261 | console.log("MayBe chaining null",
262 | MayBe.of("George")
263 | .map(() => undefined)
264 | .map((x) => "Mr. " + x))
265 | ```
266 |
267 | 如上代码的执行结果为:
268 | 
269 |
270 | ### MayBe函子的用途
271 |
272 | 在说用途之前呢,我们可以看一下在之前处理接口返回数据的一般逻辑(hack方式)
273 |
274 | ```
275 | let value = 'string';
276 | if(value != null || value != undefind){
277 | return value.toupperCase();
278 | }
279 |
280 | //实际项目中的例子
281 | getPageModuleData = () => {
282 | return this.getDataFromXctrl(pageData.moduleData).then(moduleData => {
283 | if (moduleData.filter.data.hotBrands.length) {
284 | this.setState({
285 | moduleData: moduleData.filter.data
286 | });
287 | }
288 | // 小于多少个拍品,进行推荐
289 | if (
290 | moduleData &&
291 | moduleData.list &&
292 | moduleData.list.data &&
293 | moduleData.list.data.settings &&
294 | moduleData.list.data.settings.length
295 | ) {
296 | this.recLimit = moduleData.list.data.settings[0].showRecLimit;
297 | }
298 | if (!this.recLimit) {
299 | this.recLimit = 5; // 兜底
300 | }
301 | });
302 | };
303 | ```
304 |
305 | 对,这种命令式的方式总是把一些不必要的逻辑暴露出来,使用MayBe函子就不会有这个问题
306 |
307 | 他的操作,会让你感觉非常的舒服
308 | ```
309 | MayBe.of('Nealyang')
310 | .map((x)=>x.toUpperCase())
311 | .map(x=>`Mr.${x}`);
312 | ```
313 |
314 | 啰嗦了这么多,我们就为了说明两个MayBe函子重要的属性
315 |
316 | 1: 即使给map传入返回null或者undefined的函数,MayBe也依旧可以处理
317 |
318 | 2:所有的map函数都会调用,无论他是否接收到null or undefined
319 |
320 | ### 实际操刀
321 |
322 | 说了这么多,那么在我们的日常开发中,我们MayBe到底如何使用呢。这里我们还是拿项目中常见的请求接口来举栗子~
323 |
324 | 
325 |
326 | ```
327 | var request = require('sync-request');
328 | ...
329 |
330 | let getTopTenSubRedditPosts = (type) => {
331 |
332 | let response
333 | try{
334 | response = JSON.parse(request('GET',"https://www.reddit.com/r/subreddits/" + type + ".json?limit=10").getBody('utf8'))
335 | }catch(err) {
336 | response = { message: "Something went wrong" , errorCode: err['statusCode'] }
337 | }
338 | return response
339 | }
340 |
341 | let getTopTenSubRedditData = (type) => {
342 | let response = getTopTenSubRedditPosts(type);
343 | return MayBe.of(response).map((arr) => arr['data'])
344 | .map((arr) => arr['children'])
345 | .map((arr) => arrayUtils.map(arr,
346 | (x) => {
347 | return {
348 | title : x['data'].title,
349 | url : x['data'].url
350 | }
351 | }
352 | ))
353 | }
354 |
355 | console.log("正确的接收到返回:",getTopTenSubRedditData('new'))
356 | console.log("错误时候的情况",getTopTenSubRedditData('neww'))
357 | //MayBe{value:[{title:...,url:...},{}...]}
358 | ```
359 |
360 | 如上,我们请求一个接口,然后日常处理接口返回数据,并不需要去担心值是否存在而导致程序异常~
361 | 
362 |
363 | ### Either函子
364 |
365 | 上面,我们可以正确的处理数据了,但是错误的数据呢?我们需要将错误信息跑出给出提示,这也是我们常见的需求,但是使用MayBe函子就不能够很好地定位到错误的分支到底在哪了。!!!哇,搞了半天,你MayBe不咋地啊~ 其实不然,只是不同的函子有自己不同的侧重,在这个时候,我们就需要一个更加强大的MayBe函子了:Either函子
366 |
367 | 大家都是聪明人,我就不多介绍了,直接看代码:
368 | ```
369 | const Nothing = function(val) {
370 | this.value = val;
371 | };
372 |
373 | Nothing.of = function(val) {
374 | return new Nothing(val);
375 | };
376 |
377 | Nothing.prototype.map = function(f) {
378 | return this;
379 | };
380 |
381 | const Some = function(val) {
382 | this.value = val;
383 | };
384 |
385 | Some.of = function(val) {
386 | return new Some(val);
387 | };
388 |
389 | Some.prototype.map = function(fn) {
390 | return Some.of(fn(this.value));
391 | }
392 |
393 | const Either = {
394 | Some : Some,
395 | Nothing: Nothing
396 | }
397 | ```
398 | 上面我们写了两个函数,Some和Nothing,Some简单易懂,我们来说说Nothing,他也是一个Container,但是其map不执行指定的函数,而是直接返回对象本身。直接的说就是一些函数可以在Some上运行但是不能再Nothing中运行
399 |
400 | ```
401 | console.log("Something example", Some.of("test").map((x) => x.toUpperCase()))
402 | console.log("Nothing example", Nothing.of("test").map((x) => x.toUpperCase()))
403 | ```
404 |
405 | 比较简单,在实际的应用中,我们只需要简单修改response的处理方式即可
406 | ```
407 | let getTopTenSubRedditPostsEither = (type) => {
408 |
409 | let response
410 | try{
411 | response = Some.of(JSON.parse(request('GET',"https://www.reddit.com/r/subreddits/" + type + ".json?limit=10").getBody('utf8')))
412 | }catch(err) {
413 | response = Nothing.of({ message: "Something went wrong" , errorCode: err['statusCode'] })
414 | }
415 | return response
416 | }
417 |
418 | let getTopTenSubRedditDataEither = (type) => {
419 | let response = getTopTenSubRedditPostsEither(type);
420 | return response.map((arr) => arr['data'])
421 | .map((arr) => arr['children'])
422 | .map((arr) => arrayUtils.map(arr,
423 | (x) => {
424 | return {
425 | title : x['data'].title,
426 | url : x['data'].url
427 | }
428 | }
429 | ))
430 | }
431 |
432 | console.log("正确的运行: ",getTopTenSubRedditDataEither('new'))
433 | console.log("错误:",getTopTenSubRedditDataEither('new2'))//Nothing{value:{ message: "Something went wrong" , errorCode: err['statusCode'] }}
434 | ```
435 |
436 | ### 题外话
437 | 如果大家对Java有些了解的话,一定会发现这个跟Java8 中Optional非常的相似。其实Optional就是一个函子~ 
438 |
439 | ## 最后谈一谈Monad
440 |
441 | ### 概念
442 | 直接点,Monad其实也是一个函子,存在即合理,咱来说一说他到底是一个啥样子的函子。现在我们的需求是获取Reddit的评论,当然,我们可以使用MayBe函子来搞定的,稍后我们来看下实现。只不过,这里需要说明的是,MayBe函子更加的专注问题本身,而不必关心不必要的麻烦例如undefined或者null
443 |
444 | ### 需求
445 | 该需求分为两步:
446 | - 为了搜索指定的帖子或者评论,需要调用接口:https://www.reddit.com/search.json?q=keyword 如上,我们搜索functional programming得到如下结果
447 |
448 | 
449 |
450 |
451 | - 对,标记出来的Permalink是我们的下一步,访问permalink字段,拼接地址为:https://www.reddit.com//r/programming/comments/3ejsyq/john_carmack_why_functional_programming_is_the/.json得到如下结果:
452 |
453 | 
454 |
455 | 我们需要获取评论对象后,将我们需要的title合并结果并返回新对象:{title:...,comments:[Object,Object,...]}
456 |
457 | ### MayBe 版本实现
458 |
459 | #### 第一步的实现
460 |
461 | ```
462 | let searchReddit = (search) => {
463 | let response
464 | try{
465 | response = JSON.parse(request('GET',"https://www.reddit.com/search.json?q=" + encodeURI(search) + "&limit=2").getBody('utf8'))
466 | }catch(err) {
467 | response = { message: "Something went wrong" , errorCode: err['statusCode'] }
468 | }
469 | return response
470 | }
471 |
472 | let getComments = (link) => {
473 | let response
474 | try {
475 | console.log("https://www.reddit.com/" + link)
476 | response = JSON.parse(request('GET',"https://www.reddit.com/" + link).getBody('utf8'))
477 | } catch(err) {
478 | console.log(err)
479 | response = { message: "Something went wrong" , errorCode: err['statusCode'] }
480 | }
481 |
482 | return response
483 | }
484 | ```
485 | 上面代码就是实现了两个请求api。具体实现不解释了,非常简单。
486 |
487 | #### 第二步的实现
488 |
489 | ```
490 | let mergeViaMayBe = (searchText) => {
491 | let redditMayBe = MayBe.of(searchReddit(searchText))
492 | let ans = redditMayBe
493 | .map((arr) => arr['data'])
494 | .map((arr) => arr['children'])
495 | .map((arr) => arrayUtils.map(arr,(x) => {
496 | return {
497 | title : x['data'].title,
498 | permalink : x['data'].permalink
499 | }
500 | }
501 | ))
502 | .map((obj) => arrayUtils.map(obj, (x) => {
503 | return {
504 | title: x.title,
505 | comments: MayBe.of(getComments(x.permalink.replace("?ref=search_posts",".json")))
506 | }
507 | }))
508 |
509 | return ans;
510 | }
511 | ```
512 |
513 | 
514 |
515 | ### 说说问题
516 |
517 | 是的,我们解决了我们的需求,但是仔细看上面代码,貌似丢失我们使用函子的初衷:代码简洁,看着爽~ 而上面的map多到怀疑人生,自己写起来可能会很好,但是别人维护起来是一个非常头疼的事情!
518 |
519 | 最头痛的时候,运行上面的函数后,我们拿到的值也是函子套函子,所以,该如何解决呢?这就是我们要说的Monad函子的用途
520 |
521 |
522 | ```
523 | let answer = mergeViaMayBe("functional programming")
524 |
525 | console.log(answer)
526 |
527 | /*
528 | 需要两次map才能拿到我们想要的
529 | */
530 | answer.map((result) => {
531 | arrayUtils.map(result,(mergeResults) => {
532 | mergeResults.comments.map(comment => {
533 | console.log(comment)
534 | })
535 | })
536 | })
537 | ```
538 | 在我们获取Components的时候,他也是一个函子,所以我们得使用map
539 |
540 | 简单的把问题展开是酱紫的:
541 |
542 | ```
543 | let example=MayBe.of(MayBe.of(5));
544 | //将value 加 4 的需求
545 | example.map(x=>x.map(v=>v+4))
546 | //MayBe{value:MayBe{value:9}}
547 | ```
548 | 得到的结果还是套两层,+4的需求麻烦,得到的结果嵌套也麻烦,那么是否可以将两层,拨开呢????
549 | 
550 |
551 | ### join 来也
552 |
553 | 来的目标很简单,***拨开嵌套!!!***
554 |
555 | 直接看实现:
556 | ```
557 | MayBe.prototype.join = function(){
558 | return this.isNothing?MayBe.of(null):this.value
559 | }
560 | ```
561 | 搞定!
562 |
563 | 在回头看上面的需求:
564 |
565 | ```
566 | let example=MayBe.of(MayBe.of(5));
567 | example.join().map(v=>v+4);//=> MayBe(value:9)
568 | ```
569 |
570 | 搞定!!!
571 |
572 | 再回头看上上面的需求:
573 |
574 | ```
575 | let mergeViaJoin = (searchText) => {
576 | let redditMayBe = MayBe.of(searchReddit(searchText))
577 | let ans = redditMayBe.map((arr) => arr['data'])
578 | .map((arr) => arr['children'])
579 | .map((arr) => arrayUtils.map(arr,(x) => {
580 | return {
581 | title : x['data'].title,
582 | permalink : x['data'].permalink
583 | }
584 | }
585 | ))
586 | .map((obj) => arrayUtils.map(obj, (x) => {
587 | return {
588 | title: x.title,
589 | comments: MayBe.of(getComments(x.permalink.replace("?ref=search_posts",".json"))).join()
590 | }
591 | }))
592 | .join()
593 |
594 | return ans;
595 | }
596 |
597 | let answer = mergeViaJoin("functional programming")
598 |
599 | console.log(answer)
600 | ```
601 |
602 | 如上代码,我们在函子后添加了两个join,成功的解决了函子套函子的问题。
603 |
604 |
605 | 对的,上面的join的确加入的方式有点尴尬~~~~ OK~我们在改造改造。
606 |
607 | 目前,我们总是要在map后调用join方法,下面我们把逻辑封装到一个名为chain中
608 |
609 | ```
610 | MayBe.prototype.chain = function(f){
611 | return this.map(f).join()
612 | }
613 | ...
614 | let mergeViaChain = (searchText) => {
615 | let redditMayBe = MayBe.of(searchReddit(searchText))
616 | let ans = redditMayBe.map((arr) => arr['data'])
617 | .map((arr) => arr['children'])
618 | .map((arr) => arrayUtils.map(arr,(x) => {
619 | return {
620 | title : x['data'].title,
621 | permalink : x['data'].permalink
622 | }
623 | }
624 | ))
625 | .chain((obj) => arrayUtils.map(obj, (x) => {
626 | return {
627 | title: x.title,
628 | comments: MayBe.of(getComments(x.permalink.replace("?ref=search_posts",".json"))).chain(x => {
629 | return x.length
630 | })
631 | }
632 | }))
633 |
634 | return ans;
635 | }
636 |
637 | //trying our old problem with chain
638 | answer = mergeViaChain("functional programming")
639 |
640 | console.log(answer)
641 | ```
642 |
643 | 
644 |
645 | ### 什么是Monad
646 | 啰嗦了这么多,所以到底什么是Monad呢?貌似我们一直以来都在解决问题,这种感觉就像现实中,这个人很面熟了,但是。。。还不知道怎么称呼一样。尴尬~
647 |
648 |
649 | OK,Monad就是一个含有chain方法的函子,这就是Monad!(是不是感觉这个定义非常的山寨,哈哈)
650 |
651 | 如你所见,我们通过添加一个chain(当然也包括join)来展开MayBe函子,是其成为了一个Monad!
652 |
653 | 这种感觉就像~给自行车加了个电瓶,他就叫电瓶车了一样,哈啊
654 |
655 |
656 | ## 结束语
657 |
658 | 函数式编程,意在告诉我们使用数学式函数思维来解决问题,别忘了我们的原则:最小单一原则!
659 |
660 | 我也还在学习的路上,不当的地方,还希望多多指教~
661 |
662 |
663 |
--------------------------------------------------------------------------------
/2018/函数式编程了解一下(上).md:
--------------------------------------------------------------------------------
1 | > 一直以来没有对函数式编程有一个全面的学习和使用,或者说没有一个深刻的思考。最近看到一些博客文章,突然觉得函数式编程还是蛮有意思的。看了些书和文章。这里记载下感悟和收获。 欢迎团队姜某人多多指点@姜少。 由于博客秉持着简短且全面原则。遂分为上下两篇
2 |
3 | > [原文地址 Nealyang](https://github.com/Nealyang/PersonalBlog/blob/master/201804/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%8B%EF%BC%88%E4%B8%8A%EF%BC%89.md)
4 |
5 | ## 部分简介
6 |
7 | ### 函数式编程了解一下(上)
8 | - 入门简介
9 | - HOC简介
10 | - 函数柯里化与偏应用
11 |
12 | ### 函数式编程了解一下(下)
13 | - 组合与管道
14 | - 函子和Monad
15 | - 再回首Generator
16 |
17 |
18 |
19 | ## 入门简介
20 |
21 | > 函数的第一原则是要小,函数的第二原则是要更小
22 |
23 | ### 什么是函数式编程?为什么他重要
24 | 在理解什么是函数式编程的开始,我们先了解下什么数学中,函数具有的特性
25 | - 函数必须总是接受一个参数
26 | - 函数必须总是返回一个值
27 | - 函数应该依据接受到的参数,而不是外部的环境运行
28 | - 对于一个指定的x,必须返回一个确定的y
29 |
30 | 所以我们说,函数式编程是一种范式,我们能够以此创建仅依赖输入就可以完成自身逻辑的函数。这保证了当函数多次调用时,依然可以返回相同的结果。因此可以产生可缓存的、可测试的代码库
31 |
32 | ### 引用透明
33 | 所有的函数对于相同的输入都返回相同的结构,这一特性,我们称之为引用透明。
34 | 比如:
35 | ```
36 | let identity = (i) => {return i};
37 | ```
38 | 这么简单?对,其实就是这样,也就是说他没有依赖任何外部变量、外部环境,只要你给我东西,我经过一顿鼓捣,总是给你返回你所能预测的结果。
39 |
40 | 这也为我们后面的并发代码、缓存成为可能。
41 |
42 | ### 命令式、声明式和抽象
43 | 函数式编程主张声明式编程和编写抽象代码。其实这个比较有意思,感觉更像是面向对象的编程。
44 |
45 | 光说不练都是扯淡。举个栗子
46 |
47 | ```
48 | var array = [1,2,3,4,5,6];
49 | for(let i = 0;i{console.log(item)})
60 | ```
61 | 简单体会下,是不是有那么一丢丢的灵感来了?等等,你这个forEach函数哪来的嘛!对,也是自己写的,但是不是我们通过编写这种抽象逻辑代码,而让整体的业务代码更加的清晰明了了呢?开发者是需要关心手头上的问题就好了,只需要告诉编译器去干嘛而不是怎么干了。是不是轻松了?
62 |
63 | 其实函数式编程主张的就是以抽象的方式创建函数。这些函数可以在代码的其他部分被重用。
64 |
65 | ### 函数式编程的好处
66 | 好处个人不喜欢扯太多,不是因为他没有好处,而是对于刚刚接触函数式编程的哥们,上来就说好处其实是没什么概念的,所以这里我简单提一提,后面文章会细细说明。
67 |
68 | ### 纯函数 => 可缓存
69 | 熟悉redux的同学应该对这个词语都不陌生,所谓的纯函数,其实也就是我们说的引用透明,稳定输出!好处呢?可预测嘛,容易编写测试代码哇,可缓存嘛。什么是可缓存?可以看我之前发的文章哈,这里简单举个栗子
70 | ```
71 | let longRunningFunction = (input)=>{
72 | //进行了非常麻烦的计算,然后返回出来结果
73 | return output;
74 | }
75 | ```
76 | 如果longRunningFunction是一个纯函数,引用透明。我们就可以说对于同样的输出,总是返回同样的结果,所以我们为什么不能够运用一个对象将我们每一次的运算结果存起来呢?
77 | ```
78 | let longRunningFunctionResult = {1:2,2:3,3:4};
79 | //检查key是否存在,存在直接用,不存在再计算
80 | longRunningFunctionResult.hasOwnProperty(input)?longRunningFunctionResult[input]:longRunningFunctionResult[input] = longRunningFunction(input)
81 | ```
82 |
83 | 比较直观。不多说了哈。其实好处还有之前说到的并发。不说的这么冠冕堂皇了,啥并不并发呀,我不依赖别人的任何因素,只依据你的输出我产出。你说我支持什么就是什么咯,只要你给我对的参数传进来就可以了。
84 |
85 | ### 结束语
86 |
87 | 匆匆收尾!仅作为抛砖引玉。后面咱们在系统性的学习下函数式编程。
88 |
89 |
90 | ## 高阶函数(HOC)简介
91 | ### 概念
92 | JavaScript作为一门语言,将函数视为数据。允许函数代替数据传递是一个非常强大的概念。接受一个函数作为参数的函数成为高阶函数(Higher-Order Function)
93 |
94 | ### 从数据入门HOC
95 | JavaScript支持如下几种数据类型:
96 | - Number
97 | - String
98 | - Boolean
99 | - Object
100 | - null
101 | - undefined
102 |
103 | 这里面想强调的是JavaScript将函数也同样是为一种数据类型。当一门语言允许将函数作为数据那样传递和使用的时候,我们就称函数为一等公民。
104 |
105 | 所以说这个就是为了强调说明,在JavaScript中,函数可以被赋值,作为参数传递,也可以被其他函数返回。
106 |
107 | ```javascript
108 | //传递函数
109 | let tellType = (arg)=>{
110 | if(typeof arg === 'function'){
111 | arg();
112 | }else{
113 | console.log(`this data is ${arg}`)
114 | }
115 | }
116 |
117 | let dataFn = ()=> {
118 | console.log('this is a Function');
119 | }
120 |
121 | tellType(dataFn);
122 | ```
123 |
124 | ```javascript
125 | //返回函数
126 | let returnStr = ()=> String;
127 |
128 | returnStr()('Nealyang')
129 |
130 | //let fn = returnStr();
131 | //fn('Nealyang');
132 | ```
133 |
134 | 从上我们可以看到函数可以接受另一个函数作为参数,同样,函数也可以将两一个函数作为返回值返回。
135 |
136 | > 所以高阶函数就是接受函数作为参数并且/或者返回函数作为输出的函数
137 |
138 | ### HOC 到底你是干嘛的
139 | 当我们了解到如何去创建并执行一个高阶函数的时候,同行我们都想去了解,他到底是干嘛的?OK,简单的说,高阶函数常用于抽象通用的问题。换句话说,高阶函数就是定义抽象。简单的说,其实就类似于命令式的编程方式,将具体的实现细节封装、抽象起来,让开发者更加的关心业务。抽象让我们专注于预定的目标而不是去关心底层的系统概念。
140 |
141 | 理解这个概念非常重要,所以下面我们将通过大量的栗子来说明
142 |
143 | #### 举斤栗子
144 | ```javascript
145 | const every = (arr,fn)=>{
146 | let result = true;
147 | for(const value of arr){
148 | result = result && fn(value);
149 | }
150 | return result;
151 | }
152 |
153 | every([NaN,NaN,4],isNaN);
154 |
155 | const some = (arr,fn)=>{
156 | let result = true;
157 | for(const value of arr){
158 | result = result || fn(value);
159 | }
160 | return result;
161 | }
162 | some([3,1,2],isNaN);
163 | //这里都是低效的实现。这里主要是理解高阶函数的概念
164 | ```
165 |
166 | ```javascript
167 | let sortObj = [
168 | {firstName:'aYang',lastName:'dNeal'},
169 | {firstName:'bYang',lastName:'cNeal'},
170 | {firstName:'cYang',lastName:'bNeal'},
171 | {firstName:'dYang',lastName:'aNeal'},
172 | ];
173 |
174 | const sortBy = (property)=>{
175 | return (a,b) => {
176 | return (a[property]b[property])?1:0
177 | }
178 | }
179 |
180 | sortObj.sort(sortBy('lastName'));
181 | //sort函数接受了被sortBy函数返回的比较函数,我们再次抽象出compareFunction的逻辑,让用户更加关注比较,而不用去在乎怎么比较的。
182 | ```
183 |
184 | ### HOC必然离不开闭包
185 |
186 | 上面的sortBy其实大家都应该看到了闭包的踪影。关于闭包的产生、概念这里就不啰嗦了。总之我们知道,闭包非常强大的原因就是它对作用域的访问。
187 |
188 | 简单说下闭包的三个可访问的作用域:
189 | - 在它自身声明之内的变量
190 | - 对全局变量的访问
191 | - 对外部函数变量的访问(*)
192 |
193 | #### 接着举栗子
194 | ```javascript
195 | const forEach = (arr,fn)=>{
196 | for(const item of arr){
197 | fn(item);
198 | }
199 | }
200 | //tap接受一个value,返回一个带有value的闭包函数
201 | const tap = (value)=>(fn)=>{
202 | typeof fn === 'function'?fn(value):console.log(value);
203 | }
204 |
205 | forEach([1,2,3,4,5],(a)=>{
206 | tap(a)(()=>{
207 | console.log(`Nealyang:${a}`)
208 | })
209 | });
210 |
211 | ```
212 |
213 | ## 函数柯里化与偏应用
214 |
215 | ### 函数柯里化
216 |
217 | #### 概念
218 |
219 | 直接看概念,柯里化是把一个多参函数转换为一个嵌套的一元函数的过程
220 |
221 | 不理解,莫方!举个栗子就明白了。
222 |
223 | 假设我们有一个函数,add:
224 | ```
225 | const add = (x,y)=>x+y;
226 | ```
227 | 我们调用的时候当然就是add(1,2),没有什么特别的。当我们柯里化了以后呢,就是如下版本:
228 | ```
229 | const addCurried = x => y => x + y;
230 | ```
231 | 调用的时候呢,就是这个样子的:
232 | ```
233 | addCurried(4)(4)//8
234 | ```
235 | 是不是非常的简单?
236 |
237 | 说到这,我们在来回顾下,柯里化的概念:把一个多参函数转换成一个嵌套的一元函数的过程。
238 |
239 | #### 如何实现多参函数转为一元
240 | 上面的代码中,我们实现了二元函数转为一元函数的过程。那么对于多参我们该如何做呢?
241 |
242 | 这个是比较重要的部分,我们一步一步来实现
243 |
244 | 我们先来添加一个规则,最一层函数检查,如果传入的不是一个函数来调用curry函数则抛出错误。当如果提供了柯里化函数的所有参数,则通过使用这些传入的参数调用真正的函数。
245 |
246 | ```
247 | let curry = (fn) => {
248 | if(typeof fn !== 'function'){
249 | throw Error('not a function');
250 | }
251 | return function curriedFn (...args){
252 | return fn.apply(null,args);
253 | }
254 | }
255 | ```
256 |
257 | 所以如上,我们就可以这么玩了
258 |
259 | ```
260 | const multiply = (x,y,z) => x * y * z;
261 | curry(multiply)(1,2,3);//6
262 | ```
263 |
264 | 革命还未成功,我们继续哈~下面我们的目的就是把多参函数转为嵌套的一元函数(重回概念)
265 |
266 | ```
267 | const multiply = (x,y,z) => x * y * z;
268 | let curry = (fn) => {
269 | if(typeof fn !== 'function'){
270 | throw Error('not a function');
271 | }
272 | return function curriedFn (...args){
273 | if(args.length < fn.length){
274 | return function(){
275 | return curriedFn.apply(null,args.concat([].slice.call(arguments)));
276 | }
277 | }
278 | return fn.apply(null,args);
279 | }
280 | }
281 | curry(multiply)(1)(2)(3)
282 | ```
283 |
284 | 如果是初次看到,可能会有些疑惑。我们一行行来瞅瞅。
285 |
286 | ```
287 | args.length < fn.length
288 | ```
289 | 这段代码比价直接,就是判断,你传入的参数是否小于函数参数长度。
290 |
291 | ```
292 | args.concat([].slice.call(arguments))
293 | ```
294 | 我们使用cancat函数链接一次传入的一个参数,并递归调用curriedFn。由于我们将所有的参数传入组合并递归调用,最终if判断会失效,就返回结果了。
295 |
296 | ####小小实操一下
297 | 我们写一个函数在数组内容中查找到包含数字的项
298 | ```
299 | let curry = (fn) => {
300 | if(typeof fn !== 'function'){
301 | throw Error('not a function');
302 | }
303 | return function curriedFn (...args){
304 | if(args.length < fn.length){
305 | return function(){
306 | return curriedFn.apply(null,args.concat([].slice.call(arguments)));
307 | }
308 | }
309 | return fn.apply(null,args);
310 | }
311 | }
312 | let match = curry(function(expr,str){return str.match(expr)});
313 |
314 | let hasNumber = match(/[0-9]+/);
315 |
316 | let filter = curry(function(f,ary){
317 | return ary.filter(f)
318 | });
319 |
320 | filter(hasNumber)(['js','number1']);
321 | ```
322 | 通过如上的例子,我想我们也应该看出来,为什么我们需要函数的柯里化:
323 |
324 | - 程序片段越小越容易被配置
325 | - 尽可能的函数化
326 |
327 | ### 偏应用
328 |
329 | 假设我们需要10ms后执行某一个特定操作,我们一般的做法是
330 |
331 | ```
332 | setTimeout(() => console.log('do something'),10);
333 | setTimeout(() => console.log('do other thing'),10);
334 | ```
335 |
336 | 如上,我们调用函数都传入了10,能使用curry函数把他在代码中隐藏吗?我擦,咱curry多牛逼!肯定不行的嘛~
337 |
338 | 因为curry函数应用参数列表是从最左到最右的。由于我们是根据需要传递函数,并将10保存在常量中,所以不能以这种方式使用curry。我们可以这么做:
339 |
340 | ```
341 | const setTimeoutFunction = (time , fn) => {
342 | setTimeout(fn,time);
343 | }
344 | ```
345 |
346 | 但是如果这样的话,我们是不是太过于麻烦了呢?为了减少了10的传递,还需要多造一个包装函数?
347 |
348 | 这时候,偏应用就出来了!!!
349 |
350 | 简单看下代码实现:
351 |
352 | ```
353 | const partial = function (fn,...partialArgs){
354 | let args = partialArgs;
355 | return function(...fullArgs){
356 | let arg = 0;
357 | for(let i = 0; i console.log('this is Nealyang'));
369 | ```
370 | 如上大家应该都能够理解。这里不做过多废话解释了。
371 |
372 | 简单总结的说:
373 |
374 |
375 | 所以,像map,filter我们可以轻松的使用curry函数解决问题,但是对于setTimeout这类,最合适的选择当然就是偏函数了。总之,我们使用curry或者partial是为了让函数参数或者函数设置变得更加的简单强大。
376 |
377 | ### 下节预告
378 |
379 | 上一部分说的比较浅显基础,希望大家也能够从中感受到函数式编程的精妙和灵活之处。大神请直接略过~求指正求指导~
380 |
381 | 下一节中,将主要介绍下,函数式编程中的组合、管道、函子以及Monad。最后我们在介绍下es6的Generator,或许我们能从最后的Generator中豁然开朗获得到很多启发哦~~
382 |
383 | ## 技术交流
384 |
385 | nodejs 技术交流 群号:698239345
386 |
387 | React技术栈群号:398240621
388 |
389 | 前端技术杂谈群号:604953717
--------------------------------------------------------------------------------
/2018/窥探Underscore源码系列-开篇介绍.md:
--------------------------------------------------------------------------------
1 | ## 开篇说明
2 |
3 | > 对的,让你所见,又开始造轮子了。哈哈,造轮子我们是认真的~
4 |
5 | 源码阅读是必须的,Underscore是因为刚刚学习整理了一波函数式编程,加上自己曾经没有太多阅读源码的经验,先拿Underscore练练手,跟着前辈们走一走,学一学。也相同时能够夯实js基础,从源码中学习到更多的编码技巧
6 |
7 | Underscore源码阅读大致按照[官方文档](http://www.css88.com/doc/underscore/#collections)来编写.尽量的说明每一个函数的写法,希望自己可以从中可以收获大神的编码功力。
8 |
9 |
10 | [github:Personal_Blog](https://github.com/Nealyang/PersonalBlog/)
11 |
12 |
13 | ## 阅读目录
14 |
15 | - [窥探Underscore源码系列-开篇](https://github.com/Nealyang/PersonalBlog/blob/master/201804/%E7%AA%A5%E6%8E%A2Underscore%E6%BA%90%E7%A0%81%E7%B3%BB%E5%88%97-%E5%BC%80%E7%AF%87%E4%BB%8B%E7%BB%8D.md)
16 | - [窥探Underscore源码系列-工具](#)
17 | - [窥探Underscore源码系列-集合](#)
18 | - [窥探Underscore源码系列-数组](#)
19 | - [窥探Underscore源码系列-函数](#)
20 | - [窥探Underscore源码系列-对象](#)
21 | - [窥探Underscore源码系列-感悟](#)
22 |
23 | > [Underscore源码+注释地址](https://github.com/Nealyang/PersonalBlog/blob/master/lib/underscore/underscore.js)
24 | ## 源码阅读
25 |
26 |
27 | ### 整体结构、变量介绍
28 |
29 | ```
30 | (function(){}())
31 | ```
32 |
33 | 常规操作哈,跟jQuery一毛一样,通过IIFE来包裹业务逻辑,目的简单:1、避免全局污染。2、保护隐私
34 |
35 | ```
36 | var root = typeof self == 'object' && self.self === self && self ||
37 | typeof global == 'object' && global.global === global && global ||
38 | this ||
39 | {};
40 | var previousUnderscore = root._;
41 | ```
42 | 通过global和self来判断是node环境还是window环境,说白了,就是为了拿到全局变量。因为我们需要一个全局的变量_,所以为了防止冲突,我们这里拿到root后,先暂存下之前的root._
43 |
44 | ```
45 | var ArrayProto = Array.prototype, ObjProto = Object.prototype;
46 | var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
47 |
48 | var push = ArrayProto.push,
49 | slice = ArrayProto.slice,
50 | toString = ObjProto.toString,
51 | hasOwnProperty = ObjProto.hasOwnProperty;
52 |
53 | var nativeIsArray = Array.isArray,
54 | nativeKeys = Object.keys,
55 | nativeCreate = Object.create;
56 |
57 | var Ctor = function(){};
58 |
59 | var _ = function(obj) {
60 | if (obj instanceof _) return obj;
61 | if (!(this instanceof _)) return new _(obj);
62 | this._wrapped = obj;
63 | };
64 |
65 | if (typeof exports != 'undefined' && !exports.nodeType) {
66 | if (typeof module != 'undefined' && !module.nodeType && module.exports) {
67 | exports = module.exports = _;
68 | }
69 | exports._ = _;
70 | } else {
71 | root._ = _;
72 | }
73 |
74 | _.VERSION = '1.8.3';
75 | ```
76 | 由于Underscore本身依赖很多原生js的方法,所以这里为了避免原型链的查找性能消耗,Underscore通过局部变量来保存一些常用的对象和方法。既可以提升性能,减少对象成员访问深度也可以减少代码的冗长。
77 |
78 | 下面的Ctor和_ 是为了面向对象而准备的。
79 |
80 | ### 迭代
81 |
82 | ```
83 | var optimizeCb = function(func, context, argCount) {
84 | if (context === void 0) return func;
85 | switch (argCount == null ? 3 : argCount) {
86 | case 1: return function(value) {
87 | return func.call(context, value);
88 | };
89 | case 3: return function(value, index, collection) {
90 | return func.call(context, value, index, collection);
91 | };
92 | case 4: return function(accumulator, value, index, collection) {
93 | return func.call(context, accumulator, value, index, collection);
94 | };
95 | }
96 | return function() {
97 | return func.apply(context, arguments);
98 | };
99 | };
100 |
101 | var builtinIteratee;
102 |
103 | var cb = function(value, context, argCount) {
104 | if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
105 | if (value == null) return _.identity;
106 | if (_.isFunction(value)) return optimizeCb(value, context, argCount);
107 | if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
108 | return _.property(value);
109 | };
110 |
111 | _.iteratee = builtinIteratee = function(value, context) {
112 | return cb(value, context, Infinity);
113 | };
114 | ```
115 |
116 | 这里的迭代,我们需要理清楚一个概念,在Underscore中,我们需要改变那种命令式的编程方式,具体的可以看我之前写的关于函数式编程的文章哈。
117 |
118 | 所以这里想说的就是关于遍历迭代的东西。
119 |
120 | ```
121 | var results = _.map([1,2,3],function(elem){
122 | return elem*2;
123 | }); // => [2,4,6]
124 |
125 | _.map = _.collect = function (obj, iteratee, context) {
126 | iteratee = cb(iteratee, context);
127 | var keys = !isArrayLike(obj) && _.keys(obj),
128 | length = (keys || obj).length,
129 | results = Array(length); // 定长初始化数组
130 | for (var index = 0; index < length; index++) {
131 | var currentKey = keys ? keys[index] : index;
132 | results[index] = iteratee(obj[currentKey], currentKey, obj);
133 | }
134 | return results;
135 | };
136 | ```
137 | 我们传递给的 _.map 的第二个参数就是一个 iteratee,他可能是函数,对象,甚至是字符串。
138 |
139 | - value 为 null。则 iteratee 的行为只是返回当前迭代元素自身
140 | - value 为一个函数。那么通过内置函数 optimizeCb 对其进行优化
141 | - value 为一个对象。那么返回的 iteratee(_.matcher)的目的是想要知道当前被迭代元素是否匹配给定的这个对象
142 | - value 是字面量,如数字,字符串等。他指示了一个对象的属性 key,返回的 iteratee(_.property)将用来获得该属性对应的值
143 |
144 | #### optimizeCb()
145 |
146 | 在上面的分析中,我们知道,当传入的 value 是一个函数时,value 还要经过一个叫 optimizeCb 的内置函数才能获得最终的 iteratee:
147 | ```
148 | var cb = function (value, context, argCount) {
149 | // ...
150 | if (_.isFunction(value)) return optimizeCb(value, context, argCount);
151 | // ...
152 | };
153 | ```
154 |
155 | 所以此处的optimizeCb必然是优化回调的作用了。
156 | ```
157 |
158 | // 优化回调的函数,遍历
159 | var optimizeCb = function(func, context, argCount) {
160 | // void 0 会返回真正的undefined 此处确保上下文的存在
161 | if (context === void 0) return func;
162 | switch (argCount == null ? 3 : argCount) {
163 | case 1: return function(value) {
164 | // argCount为0时候,迭代过程中,我们只需要这个value就可以了
165 | return func.call(context, value);
166 | };
167 | // The 2-parameter case has been omitted only because no current consumers
168 | // 3个参数(值,索引,被迭代集合对象).
169 | case 3: return function(value, index, collection) {
170 | return func.call(context, value, index, collection);
171 | };
172 | // 4个参数(累加器(比如reducer需要的), 值, 索引, 被迭代集合对象)
173 | case 4: return function(accumulator, value, index, collection) {
174 | return func.call(context, accumulator, value, index, collection);
175 | };
176 | }
177 | return function() {
178 | return func.apply(context, arguments);
179 | };
180 | };
181 | ```
182 |
183 | 整体的代码非常清晰,待优化的回调函数func,上下文context以及迭代回调需要的参数个数。
184 |
185 | 上面的这个优化的回调涉及到不同地方使用的不同迭代。这里暂时 先放一放。等过了一遍源码后,看到每一个用到迭代的地方,在回头来看,就会明白很多。
186 |
187 | ### rest参数
188 | 在 ES6中,我们定义不定参方法的时候可以这么写
189 | ```
190 | let a = (b,...c)=>{
191 | console.log(b,c);
192 | }
193 | ```
194 | 但是在此之前,Underscore实现了自己的reset,使用如下:
195 | ```
196 | function a(a,b,c,d,e){
197 | console.log(a,b,c,d,e)
198 | }
199 | let aa = restArgs(a);//let aa = restArgs(a,4)
200 | aa(1,2,3,4,5,6,7,8,8)
201 | ```
202 |
203 | 看下restArgs的实现:
204 |
205 | ```
206 | var restArgs = function(func, startIndex) {
207 | //未传则取形参个数减一
208 |
209 | startIndex = startIndex == null ? func.length - 1 : +startIndex;
210 |
211 | return function() {
212 | // 多传了几个参数
213 | //length为多传了几个参数
214 | var length = Math.max(arguments.length - startIndex, 0),
215 | rest = Array(length),
216 | index = 0;
217 | for (; index < length; index++) {
218 | rest[index] = arguments[index + startIndex];
219 | }
220 |
221 | //优化。注意rest参数总是最后一个参数, 否则会有歧义
222 | switch (startIndex) {
223 | case 0: return func.call(this, rest);
224 | case 1: return func.call(this, arguments[0], rest);
225 | case 2: return func.call(this, arguments[0], arguments[1], rest);
226 | }
227 | //撇去常用的startIndex,这里循环
228 | //先拿到前面参数
229 | var args = Array(startIndex + 1);
230 | for (index = 0; index < startIndex; index++) {
231 | args[index] = arguments[index];
232 | }
233 | //拿到后面的数组
234 | args[startIndex] = rest;
235 | return func.apply(this, args);
236 | };
237 | };
238 | ```
239 |
240 | ### 面向对象
241 |
242 | 关于面向对象,这里不做过多解释了,可以参考我的另一篇文章:
243 | [javasript设计模式之面向对象](https://juejin.im/post/5a252382518825293b50276e)。
244 |
245 | 我们直接看他的继承实现吧
246 |
247 | ```
248 | var Ctor = function(){};
249 |
250 | // 定义了一个用于继承的内部方法
251 | var baseCreate = function(prototype) {
252 | if (!_.isObject(prototype)) return {};
253 | // nativeCreate = Object.create;
254 | if (nativeCreate) return nativeCreate(prototype);
255 | Ctor.prototype = prototype;
256 | var result = new Ctor;
257 | Ctor.prototype = null;
258 | return result;
259 | };
260 | ```
261 |
262 | es5 中,我们有一种创建对象的方式,Object.create 。
263 |
264 | ```
265 | function Animal(name){
266 | this.name = name;
267 | }
268 | Animal.prototype.eat = function(){
269 | console.log(this.name,'鸟为食亡');
270 | }
271 | var dog = Object.create(Animal.prototype);
272 | dog.name = "毛毛";
273 | dog.eat();
274 | ```
275 | ok,大概从上大家就看出来create的作用了。
276 |
277 | baseCrate中,首先判断是否为对象,否则退出。浏览器能力检测是否具备Object.create方法,具备则用。否则采用寄生式继承创建对象。需要注意的是,baseCreate仅仅支持原型继承,而不能像Object.create那样传递属性列表。
278 |
279 | ### 结束语
280 |
281 | 开篇简单的介绍Collection Functions上面的代码部分。在介绍Collection Function每个方法实现之前,我们将在下一篇看一下一些工具方法的编写方式。
282 |
283 |
284 |
285 | > 的确在造造轮子,只是更想自己撸一遍优秀代码。
286 |
287 |
288 |
289 |
--------------------------------------------------------------------------------
/OnceTheBlog/变量、作用域和内存问题.md:
--------------------------------------------------------------------------------
1 | # 夯实JS系列--变量、作用域和内存问题
2 | >最近在忙于写一个react+node的全栈博客demo,没有时间更新文章。但是还是觉得这样一忙起来不更新是不应该的。正好在空闲上下班地铁上都会再去细读js原生知识。所以打算整理、总结、系统性的分享给大家。
3 |
4 | ## 基本类型和引用类型
5 | 在ECMAScript中,变量分为基本类型和引用类型两种。
6 | 基本类型就是存储简单的数据段。而引用类型指的是那些可能由多个值构成的对象。
7 | 在ECMAScript中,基本类型包括:Undefined、Null、Boolean、Number和String。
8 | 这些基本类型的对象都是按值访问的。所以js中我们可以直接操作他们。
9 | 但是引用类型如Object等,是按照引用来操作的。并非直接操作其值。
10 | 并且我们可以动态的为引用类型变量添加属性和方法。而基本类型则不可以。
11 |
12 | ## 变量赋值和传参
13 | 这里其实对于基本类型来说没有什么需要重点说明的。这里就重点说下引用类型吧
14 |
15 | 对于赋值
16 | ```javascript
17 | function setName(obj) {
18 | obj.name = "Neal";
19 | obj = new Object();
20 | obj.name = "yang";
21 | }
22 | var person = new Object();
23 | setName(person);
24 | console.log(person.name);
25 | ```
26 | 如上代码,最后console出来的是Neal。
27 |
28 | 这段代码说明两点:
29 | - 引用类型在传参的时候,是按照引用传递的,不然不可能person.name为Neal
30 | - 即使在函数内部修改了参数的值。原始的引用依然不变。实际上,在重写obj的时候,这个变量的引用已经是一个局部变量了。只是在这儿函数运行完,这个对象被销毁了。
31 |
32 | 所以说到这,对于对象的赋值,一句以概之:引用的赋值。
33 |
34 | ## 执行环境及其作用域
35 | > 这大概是一个非常基础也是重要的部分,后续会在进阶里面详细展开。
36 |
37 | 执行环境定义了变量或者函数有权访问的其他数据,决定了他们的行为。每一个执行环境都有一个与之关联的变量对象(如global、window)。环境中定义的所有变量和函数都保存在这个对象中。
38 |
39 | 某一个执行环境执行完毕后,该环境会被销毁。其中的所有的变量和函数也将随之销毁。全局执行环境知道应用程序退出才被销毁(如关闭网页等)
40 |
41 | 当代码在一个环境中执行的时候,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的变量和函数的有序访问。作用域链的前端,始终是当前执行的代码所在的
42 | 环境的变量对象。全局执行环境始终是作用域链的最后一个对象。
43 |
44 | 标识符的解析也就是沿着作用域链一级一级的搜索的过程。搜索过程从作用域链的前端开始,然后逐级向后回溯。知道找到标识符为止。
45 |
46 | ```javascript
47 | var color = 'red';
48 | function changeColor() {
49 | var anotherColor = 'blue';
50 | function swapColors() {
51 | var tempColor = anotherColor;
52 | anotherColor = color;
53 | color = tempColor;
54 | //这个执行环境中可以访问到 tempColor color antherColor
55 | }
56 | //这里只能访问anotherColor color
57 | swapColors();
58 | }
59 | changeColor();//这里只能访问color
60 | ```
61 |
62 | 所以从上面代码我们可以感受到:内部环境可以通过作用域链访问到外部环境的变量。反之不可。这些环境之间的联系都是线性、有次序的。
63 |
64 | ### 延长作用域链
65 | 虽然执行环境的类型只有两种。局部的和全局的。但是还有一种方法可以延长作用域链。
66 |
67 | 这是因为有些语句可以在作用域链的前端临时添加一个变量对象,改变量对象会在代码执行后被移除。
68 |
69 | - try-catch 语句中的catch
70 | - with语句
71 |
72 | 对于width语句而言,会将指定的对象添加到作用域链中。对于catch语句而言,会创建一个新的变量对象,其中包含被抛出的错误对象的申明。
73 |
74 | 关于作用域、环境之类的话题后续会再细说。这里作为基础篇,就先介绍到这里。
75 |
76 | ## 垃圾收集
77 |
78 | 很开心~js不需要你来收拾垃圾!好~此篇完结!
79 |
80 | 好吧~虽然我们不收拾垃圾,但是也是要稍微了解下js是如何收拾垃圾的。
81 |
82 | 首先什么是垃圾:哪些不再被继续使用的变量都是垃圾。什么叫收拾?释放起垃圾所占用的空间即为释放。
83 |
84 | 局部变量只在函数执行过过程中存在。而在这个过程中,会为局部变量在栈或者堆中分配相应的内存空间(存值呗)。然后函数执行啦,用了这些变量,执行完啦。完啦!则这些变量就没有用了。没用了,则为垃圾,既需清理。
85 |
86 | 但是并非所有的情况下都这么容易的得出结论。垃圾收集器必须跟踪哪个变量用了哪个变量没用。对于不在利用的打上标记,已被将来收回其所占用的内存。
87 |
88 | ### 标记清除
89 | 这是最为常用一种清除方式。当一个变量进入到环境的时候,标记为‘进入环境’,这个基本是不会被清除的,因为执行流进入到相应的环境的时候可能会用到。当变量离开环境的时候,标记为‘离开环境’。
90 |
91 | 可以使用任何方式来标记。我们要知道是如何标记不重要,重要的是采用什么策略。
92 |
93 | 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。他会去掉环境中的变量以及被环境中的变量所引用的变量的标记。剩下的,则视为嫌疑人,准备删除。因为环境中的变量已经无法访问到这些变量了。目前IE、ff 、 opera 、 chrome都是这种标记清除方式
94 |
95 | ### 引用计数
96 | > 因为不常用,简单说下
97 |
98 | 引用计数的意思就是跟踪记录每一个值被引用的次数。当一个引用类型的变量复制给一个变量的时候,这个引用次数则+1,如果有别复制给另一个变量,则再+1,如果包含对这个值的引用的变量又被赋值了别的值。则这个值-1.
99 |
100 | 当引用次数为0的时候,为垃圾~回收!
101 |
102 | 为什么不常用呢?看着也很清晰啊!
103 |
104 | look code:
105 | ```javascript
106 | function test() {
107 | var objectA = new Object();
108 | var objectB = new Object();
109 |
110 | objectA.someOtherObject = objectB;
111 | objectB.someOtherObject = objectA;
112 | }
113 | ```
114 | 如上,对象A和对象B的属性互相引用。也就是说,这两个对象的引用次数永远都是2.哪怕这个函数执行完咯,也没法清理的。对的,这就是bug~
115 |
116 | ## 节制点~你懂得
117 | 虽然垃圾回收机制帮我们做了很多事,但是电脑分配给浏览器的可用内存通常要比桌面应用的内存要小的多,毕竟是为了防止运行js的网页耗尽所有的内存而导致系统崩溃的问题发生。
118 |
119 | 所以我们确保用最少的内存可以让页面获取最好的性能,最佳的执行方案就是执行中的代码都是有必要的数据。就好比用最低的经济拿最多的人头一样,一旦经济不够,技术弥补!一旦数据不要用了,自己主动扫除。
120 |
121 | ```javascript
122 | function createPerson(name) {
123 | var localPerson = new Object();
124 | localPerson.name = name;
125 | return localPerson;
126 | }
127 | var neal = createPerson('Neal');
128 |
129 | //主动清理垃圾
130 | createPerson = null;
131 | ```
132 |
133 | 这里讲createPerson设置为null,并没有就把他给清除了,只是释放了他的引用。让其脱离其执行环境,以便于垃圾收集器更快的将其回收。
134 |
--------------------------------------------------------------------------------
/OnceTheBlog/编写高质量代码基本要点.md:
--------------------------------------------------------------------------------
1 | # 编写高质量代码基本要点
2 |
3 | > 汤姆大叔真大牛
4 |
5 | 在我们日常编码中,编写可维护代码是非常重要的,编写可维护代码,意味着:
6 | - 可读性
7 | - 一致性
8 | - 可预测性
9 | - 团队代码风格一致
10 |
11 | ## 最小全局变量
12 | JavaScript是通过函数来管理作用域的。在函数内部申明的变量只能在函数内部使用,在函数外部不能使用。每一个JavaScript都有一个全局对象,当你在任意的函数外部访问this的时候可以访问到,你创建的每一个全局变量都成为这个全局对象的属性。浏览器中,这个全局对象便是window。
13 |
14 | ```javascript
15 | myglobal = 'Hello';
16 | console.log(myglobal);
17 | console.log(window.myglobal);
18 | console.log(window['myglobal']);
19 | console.log(this.myglobal);
20 | ```
21 | ## 全局变量的问题
22 | 全局变量最大的问题就是变量名冲突,造成不可以预计的后果。而在web页面中包含不是开发者缩写的代码也很常见
23 | - 第三方JavaScript库
24 | - 广告或者统计脚本
25 | - 不同类型的组件
26 |
27 | 所以一旦出现明明冲突了,那么这就gg了。所以尽可能的少用全局变量是非常有必要的。当然,我们有很多减少全局变量的策略,但是我觉得最主要的就是使用使用var来声明变量。当然,es6中的let和const出来后,var基本可以拜拜了,但是这里我们还是要讨论下var的
28 |
29 | 由于JavaScript的两个特征,不自觉的创建变量是出乎意料的容易,首先,你可以甚至不需要声明就可以使用变量;第二,JavaScript有隐含的全局概念,意味着你不声明的任何变量都会成为一个全局对象属性。
30 | ```javascript
31 | function sum(x, y) {
32 | // 不推荐写法: 隐式全局变量
33 | result = x + y;
34 | return result;
35 | }
36 | ```
37 |
38 | 另一个创建隐式全局变量的反例就是使用任务链进行部分var声明。下面的片段中,a是本地变量但是b确实全局变量,这可能不是你希望发生的
39 |
40 | ```javascript
41 | // 反例,勿使用
42 | function foo() {
43 | var a = b = 0;
44 | // ...
45 | }
46 | ```
47 |
48 | ## 忘记var的副作用
49 | 隐式全局变量和明确定义的全局变量间有些小的差异,就是通过delete操作符让变量未定义的能力。
50 | - 通过var创建的全局变量(任何函数之外的程序中创建)是不能被删除的
51 | - 无var创建的隐式全局变量(无视是否在函数中创建)是能被删除的
52 |
53 | 这表明,在技术上,隐式全局变量并不是真正的全局变量,但它们是全局对象的属性。属性是可以通过delete操作符删除的,而变量是不能的
54 |
55 | ## 单var形式
56 | 在函数顶部使用单var语句是比较有用的一种形式,其好处在于
57 |
58 | - 提供了一个单一的地方去寻找功能所需要的所有局部变量
59 | - 防止变量在定义之前使用的逻辑错误
60 | - 帮助你记住声明的全局变量,因此较少了全局变量
61 | - 少代码(类型啊传值啊单线完成)
62 |
63 | ```javascript
64 | function func() {
65 | var a = 1,
66 | b = 2,
67 | sum = a + b,
68 | myobject = {},
69 | i,
70 | j;
71 | // function body...
72 | }
73 | ```
74 |
75 | ## 预解析:var散布的问题
76 | JavaScript中,你可以在函数的任何位置声明多个var语句,并且它们就好像是在函数顶部声明一样发挥作用,这种行为称为 hoisting。当你使用了一个变量,然后不久在函数中又重新声明的话,就可能产生逻辑错误。对于JavaScript,只 要你的变量是在同一个作用域中(同一函数),它都被当做是声明的,即使是它在var声明前使用的时候。
77 |
78 | ```javascript
79 | // 反例
80 | myname = "global"; // 全局变量
81 | function func() {
82 | alert(myname); // "undefined"
83 | var myname = "local";
84 | alert(myname); // "local"
85 | }
86 | func();
87 | ```
88 | 毕竟由于函数申明提升,也就是预解析。为了避免这种混 乱,最好是预先声明你想使用的全部变量。
89 | >为了完整,我们再提一提执行层面的稍微复杂点的东西。代码处理分两个阶段,第一阶段是变量,函数声明,以及正常格式的参数创建,这是一个解析和进入上下文 的阶段。第二个阶段是代码执行,函数表达式和不合格的标识符(为声明的变量)被创建。但是,出于实用的目的,我们就采用了”hoisting”这个概念, 这种ECMAScript标准中并未定义,通常用来描述行为。
90 |
91 | ## for循环
92 | 推荐写法
93 |
94 | ```javascript
95 | for (var i = 0, max = myarray.length; i < max; i++) {
96 | // 使用myarray[i]做点什么
97 | }
98 | ```
99 | 伴随着单var形式,你可以把变量从循环中提出来,就像下面这样:
100 | ```javascript
101 | function looper() {
102 | var i = 0,
103 | max,
104 | myarray = [];
105 | // ...
106 | for (i = 0, max = myarray.length; i < max; i++) {
107 | // 使用myarray[i]做点什么
108 | }
109 | }
110 | ```
111 | 最后一个需要对循环进行调整的是使用下面表达式之一来替换i++。
112 | ```javascript
113 | i = i + 1
114 | i += 1
115 | ```
116 | 原因是++和–-促进了“过分棘手(excessive trickiness)”。
117 |
118 | 还有两种变化的形式,其又有了些微改进,因为:
119 | - 少了一个变量(无max)
120 | - 向下数到0,通常更快,因为和0做比较要比和数组长度或是其他不是0的东西作比较更有效率
121 |
122 | ```javascript
123 | //第一种变化的形式:
124 |
125 | var i, myarray = [];
126 | for (i = myarray.length; i–-;) {
127 | // 使用myarray[i]做点什么
128 | }
129 |
130 | //第二种使用while循环:
131 |
132 | var myarray = [],
133 | i = myarray.length;
134 | while (i–-) {
135 | // 使用myarray[i]做点什么
136 | }
137 | ```
138 |
139 | ## for-in Loops
140 | for-in循环应该用在非数组对象的遍历上,使用for-in进行循环也被称为“枚举”
141 |
142 | 从技术上将,你可以使用for-in循环数组(因为JavaScript中数组也是对象),但这是不推荐的。因为如果数组对象已被自定义的功能增强,就可能发生逻辑错误。另外,在for-in中,属性列表的顺序(序列)是不能保证的。所以最好数组使用正常的for循环,对象使用for-in循环。
143 |
144 | 有个很重要的hasOwnProperty()方法,当遍历对象属性的时候可以过滤掉从原型链上下来的属性。
145 |
146 | ```javascript
147 | // 对象
148 | var man = {
149 | hands: 2,
150 | legs: 2,
151 | heads: 1
152 | };
153 |
154 | // 在代码的某个地方
155 | // 一个方法添加给了所有对象
156 | if (typeof Object.prototype.clone === "undefined") {
157 | Object.prototype.clone = function () {};
158 | }
159 | ```
160 | 在这个例子中,我们有一个使用对象字面量定义的名叫man的对象。在man定义完成后的某个地方,在对象原型上增加了一个很有用的名叫 clone()的方法。此原型链是实时的,这就意味着所有的对象自动可以访问新的方法。为了避免枚举man的时候出现clone()方法,你需要应用hasOwnProperty()方法过滤原型属性。如果不做过滤,会导致clone()函数显示出来,在大多数情况下这是不希望出现的。
161 |
162 | ```javascript
163 | // 1.
164 | // for-in 循环
165 | for (var i in man) {
166 | if (man.hasOwnProperty(i)) { // 过滤
167 | console.log(i, ":", man[i]);
168 | }
169 | }
170 | /* 控制台显示结果
171 | hands : 2
172 | legs : 2
173 | heads : 1
174 | */
175 | // 2.
176 | // 反面例子:
177 | // for-in loop without checking hasOwnProperty()
178 | for (var i in man) {
179 | console.log(i, ":", man[i]);
180 | }
181 | /*
182 | 控制台显示结果
183 | hands : 2
184 | legs : 2
185 | heads : 1
186 | clone: function()
187 | */
188 | ```
189 | 另外一种使用hasOwnProperty()的形式是取消Object.prototype上的方法。像是:
190 | ```javascript
191 | for (var i in man) {
192 | if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤
193 | console.log(i, ":", man[i]);
194 | }
195 | }
196 | ```
197 | 其好处在于在man对象重新定义hasOwnProperty情况下避免命名冲突。也避免了长属性查找对象的所有方法,你可以使用局部变量“缓存”它。
198 | ```javascript
199 | var i, hasOwn = Object.prototype.hasOwnProperty;
200 | for (i in man) {
201 | if (hasOwn.call(man, i)) { // 过滤
202 | console.log(i, ":", man[i]);
203 | }
204 | }
205 | ```
206 | ## (不)扩展内置原型
207 | 增加内置的构造函数原型(如Object(), Array(), 或Function())挺诱人的,但是这严重降低了可维护性,因为它让你的代码变得难以预测。使用你代码的其他开发人员很可能更期望使用内置的 JavaScript方法来持续不断地工作,而不是你另加的方法。
208 |
209 | ## switch模式
210 | ```javascript
211 | var inspect_me = 0,
212 | result = '';
213 | switch (inspect_me) {
214 | case 0:
215 | result = "zero";
216 | break;
217 | case 1:
218 | result = "one";
219 | break;
220 | default:
221 | result = "unknown";
222 | }
223 | ```
224 | - 每个case和switch对齐(花括号缩进规则除外)
225 | - 每个case中代码缩进
226 | - 每个case以break清除结束
227 | - 避免贯穿(故意忽略break)。如果你非常确信贯穿是最好的方法,务必记录此情况,因为对于有些阅读人而言,它们可能看起来是错误的
228 | - 以default结束switch:确保总有健全的结果,即使无情况匹配。
229 |
230 | ## 避免隐式类型转换
231 | JavaScript的变量在比较的时候会隐式类型转换。这就是为什么一些诸如:false == 0 或 “” == 0 返回的结果是true。为避免引起混乱的隐含类型转换,在你比较值和表达式类型的时候始终使用===和!==操作符。
232 |
233 | ## 避免 eval()
234 | 如果代码是在运行时动态生成,有一个更好的方式不使用eval而达到同样的目 标。例如,用方括号表示法来访问动态属性会更好更简单:
235 | ```javascript
236 | // 反面示例
237 | var property = "name";
238 | alert(eval("obj." + property));
239 |
240 | // 更好的
241 | var property = "name";
242 | alert(obj[property]);
243 | ```
244 |
245 | 使用eval()也带来了安全隐患,因为被执行的代码(例如从网络来)可能已被篡改。这是个很常见的反面教材,当处理Ajax请求得到的JSON 相应的时候。在这些情况下,最好使用JavaScript内置方法来解析JSON相应,以确保安全和有效。若浏览器不支持JSON.parse(),你可 以使用来自JSON.org的库。
246 |
247 | 同样重要的是要记住,给setInterval(), setTimeout()和Function()构造函数传递字符串,大部分情况下,与使用eval()是类似的,因此要避免。在幕后,JavaScript仍需要评估和执行你给程序传递的字符串:
248 | ```javascript
249 | // 反面示例
250 | setTimeout("myFunc()", 1000);
251 | setTimeout("myFunc(1, 2, 3)", 1000);
252 |
253 | // 更好的
254 | setTimeout(myFunc, 1000);
255 | setTimeout(function () {
256 | myFunc(1, 2, 3);
257 | }, 1000);
258 | ```
259 | 如果你绝对必须使用eval(),你 可以考虑使用new Function()代替。有一个小的潜在好处,因为在新Function()中作代码评估是在局部函数作用域中运行,所以代码中任何被评估的通过var 定义的变量都不会自动变成全局变量。另一种方法来阻止自动全局变量是封装eval()调用到一个即时函数中。
260 | ```javascript
261 | console.log(typeof un); // "undefined"
262 | console.log(typeof deux); // "undefined"
263 | console.log(typeof trois); // "undefined"
264 |
265 | var jsstring = "var un = 1; console.log(un);";
266 | eval(jsstring); // logs "1"
267 |
268 | jsstring = "var deux = 2; console.log(deux);";
269 | new Function(jsstring)(); // logs "2"
270 |
271 | jsstring = "var trois = 3; console.log(trois);";
272 | (function () {
273 | eval(jsstring);
274 | }()); // logs "3"
275 |
276 | console.log(typeof un); // number
277 | console.log(typeof deux); // "undefined"
278 | console.log(typeof trois); // "undefined"
279 | ```
280 | 另一间eval()和Function构造不同的是eval()可以干扰作用域链,而Function()更安分守己些。不管你在哪里执行 Function(),它只看到全局作用域。所以其能很好的避免本地变量污染。
281 |
282 | ## parseInt()下的数值转换
283 | 使用parseInt()你可以从字符串中获取数值,该方法接受另一个基数参数,这经常省略,但不应该。当字符串以”0″开头的时候就有可能会出问 题,例如,部分时间进入表单域,在ECMAScript 3中,开头为”0″的字符串被当做8进制处理了,但这已在ECMAScript 5中改变了。为了避免矛盾和意外的结果,总是指定基数参数。
284 |
285 | ## 空格
286 | 空格的使用同样有助于改善代码的可读性和一致性。在写英文句子的时候,在逗号和句号后面会使用间隔。在JavaScript中,你可以按照同样的逻辑在列表模样表达式(相当于逗号)和结束语句(相对于完成了“想法”)后面添加间隔。
287 |
288 | 适合使用空格的地方包括:
289 |
290 | - for循环分号分开后的的部分:如for (var i = 0; i < 10; i += 1) {...}
291 | - for循环中初始化的多变量(i和max):for (var i = 0, max = 10; i < max; i += 1) {...}
292 | - 分隔数组项的逗号的后面:var a = [1, 2, 3];
293 | - 对象属性逗号的后面以及分隔属性名和属性值的冒号的后面:var o = {a: 1, b: 2};
294 | - 限定函数参数:myFunc(a, b, c)
295 | - 函数声明的花括号的前面:function myFunc() {}
296 | - 匿名函数表达式function的后面:var myFunc = function () {};
297 |
298 | 使用空格分开所有的操作符和操作对象是另一个不错的使用,这意味着在+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=等前后都需要空格。
299 |
300 | ## 注释、 驼峰命名
301 | 不再多说
302 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PersonalBlog [Nealyang 全栈前端](https://github.com/Nealyang/PersonalBlog/issues/46)
2 | > 随笔、总结、个人、学习、杂记 任何问题交流,提issue
3 |
4 |
5 | ## 搞搞js
6 |
7 | - [函数式编程了解一下(上)](./2018/函数式编程了解一下(上).md)
8 | - [ 函数式编程了解一下(下) ](./2018/函数式编程了解一下(下).md)
9 | - [ 窥探Underscore源码系列-开篇介绍 ](./2018/窥探Underscore源码系列-开篇介绍.md)
10 | - [变量、作用域和内存问题](./OnceTheBlog/变量、作用域和内存问题.md)
11 | - [JavaScript面向对象](https://github.com/Nealyang/YOU-SHOULD-KNOW-JS/blob/master/doc/basic_js/prototype-based.md)
12 | - [忍者级别的操作JavaScript函数](https://github.com/Nealyang/YOU-SHOULD-KNOW-JS/blob/master/doc/basic_js/%E5%BF%8D%E8%80%85%E7%BA%A7%E5%88%AB%E7%9A%84%E6%93%8D%E4%BD%9C%E5%87%BD%E6%95%B0.md)
13 | - [this 其实很简单](https://github.com/Nealyang/YOU-SHOULD-KNOW-JS/blob/master/doc/basic_js/%E5%BD%BB%E5%BA%95%E6%98%8E%E7%99%BDthis%E6%8C%87%E5%90%91.md?1536536968756)
14 | - [Decorator:从原理到实践,我一点都不虚~](https://github.com/Nealyang/PersonalBlog/issues/35)
15 | - [【THE LAST TIME】彻底吃透 JavaScript 执行机制](https://github.com/Nealyang/PersonalBlog/issues/55)
16 | - [【THE LAST TIME】this:call、apply、bind](https://github.com/Nealyang/PersonalBlog/issues/56)
17 | - [【THE LAST TIME】一文吃透所有JS原型相关知识点](https://github.com/Nealyang/PersonalBlog/issues/57)
18 | - [【THE LAST TIME】深入浅出 JavaScript 模块化](https://github.com/Nealyang/PersonalBlog/issues/61)
19 | - [【THE LAST TIME】从零手写pm-cli脚手架,统一阿里拍卖源码架构](https://github.com/Nealyang/PersonalBlog/issues/72)
20 |
21 |
22 | ## Typescript
23 | - [Typescript+Decorator:装饰你的代码](https://github.com/Nealyang/PersonalBlog/issues/59)
24 | - [【THE LAST TIME】Typescript 进阶之重难点梳理](https://github.com/Nealyang/PersonalBlog/issues/65)
25 | - [一张页面引起的前端架构思考](https://github.com/Nealyang/PersonalBlog/issues/64)
26 | - [拍卖源码架构在拍品详情页上的探索](https://github.com/Nealyang/PersonalBlog/issues/69)
27 | - [TypeScript 类型编程](https://github.com/Nealyang/PersonalBlog/issues/135)
28 | - [tsconfig 备忘清单](https://github.com/Nealyang/PersonalBlog/issues/136)
29 |
30 | ## 关于React
31 | - [记一个复杂组件(Filter)的从设计到开发](https://github.com/Nealyang/PersonalBlog/issues/47)
32 | - [ React源码分析与实现(一):组件的初始化与渲染 ](./2018/React源码分析与实现(一):组件的初始化与渲染.md)
33 | - [React源码分析与实现(二):状态、属性更新 -> setState](https://github.com/Nealyang/PersonalBlog/blob/master/2018/React%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%8E%E5%AE%9E%E7%8E%B0(%E4%BA%8C)%EF%BC%9A%E7%8A%B6%E6%80%81%E3%80%81%E5%B1%9E%E6%80%A7%E6%9B%B4%E6%96%B0%20-%3E%20setState.md)
34 | - [React源码分析与实现(三):实操DOM Diff ](https://github.com/Nealyang/PersonalBlog/issues/2)
35 | > 源码部分后续会补充完
36 |
37 | - [create-react-app 源码学习(上)](https://github.com/Nealyang/PersonalBlog/issues/36)
38 | - [(译) 如何使用 React hooks 获取 api 接口数据](https://github.com/Nealyang/PersonalBlog/issues/51)
39 |
40 |
41 | ## 关于flutter
42 | - [Flutter从入门到寄几玩儿](https://github.com/Nealyang/PersonalBlog/issues/11)
43 | - [Flutter 状态管理之 Scoped Model & Redux](https://github.com/Nealyang/PersonalBlog/issues/12)
44 | - [盘一盘Flutter中“小巧”的组件与属性](https://github.com/Nealyang/PersonalBlog/issues/14)
45 | - [Flutter Go 代码开发规范 0.1.0 版](https://github.com/Nealyang/PersonalBlog/issues/34)
46 | - [Flutter 开发者国服最强辅助 App:FlutterGo 2.0 强势归来!!](https://github.com/Nealyang/PersonalBlog/issues/52)
47 |
48 | ### Flutter入门实战:从0到1仿写web版掘金App
49 | - [Dart基础介绍](https://github.com/Nealyang/PersonalBlog/issues/18)
50 | - [flutter入门以及常用Widget介绍](https://github.com/Nealyang/PersonalBlog/issues/19)
51 | - [项目框架搭建](https://github.com/Nealyang/PersonalBlog/issues/20)
52 | - [“flutter”数据model及json处理](https://github.com/Nealyang/PersonalBlog/issues/21)
53 | - [首页List UI编写](https://github.com/Nealyang/PersonalBlog/issues/22)
54 | - [fluro介绍以及路由配置](https://github.com/Nealyang/PersonalBlog/issues/23)
55 | - [网络请求](https://github.com/Nealyang/PersonalBlog/issues/24)
56 | - [下拉刷新 & 加载更多](https://github.com/Nealyang/PersonalBlog/issues/25)
57 | - [webView for Detail](https://github.com/Nealyang/PersonalBlog/issues/26)
58 | - [驻足思考、总结](https://github.com/Nealyang/PersonalBlog/issues/27)
59 | - [沸点 UI & 功能 编写(上)](https://github.com/Nealyang/PersonalBlog/issues/28)
60 | - [沸点 UI & 功能 编写(下)](https://github.com/Nealyang/PersonalBlog/issues/29)
61 | - [小册 UI & 功能 编写](https://github.com/Nealyang/PersonalBlog/issues/30)
62 | - [开源库、活动 UI & 功能 编写](https://github.com/Nealyang/PersonalBlog/issues/31)
63 | - [登陆功能 & App响应](https://github.com/Nealyang/PersonalBlog/issues/32)
64 |
65 |
66 | ## 网络吧
67 |
68 | ## Node相关
69 |
70 |
71 | 实战react技术栈+express前后端博客项目
72 |
73 |
74 | 预热一波
75 |
76 | 整体项目结构搭建、state状态树设计
77 |
78 | 后端路由、代理以及静态资源托管等其他配置说明
79 |
80 | 博客首页代码编写以及redux-saga组织
81 |
82 | 前后端实现登录功能
83 |
84 | 使用session实现免登陆+管理后台权限验证
85 |
86 | 前端管理界面用户查看功能+后端对应接口开发
87 |
88 | 前端管理界面标签管理功能+后端对应接口开发
89 |
90 | 前端管理界面发表文章功能+后端对应接口
91 |
92 | 前端文章列表、路由控制以及对应后端文章管理开发
93 |
94 | 前端文章管理部分完善(修改、预览功能)
95 |
96 | pm2的使用说明
97 |
98 |
99 |
100 | - [FlutterGo 后台代码知识点提炼:midway+Typescript+mysql(sequelize)](https://github.com/Nealyang/PersonalBlog/issues/54)
101 |
102 | ## 数据库相关
103 |
104 | ## vscode-extension
105 | - [基于 monorepo 的 vscode 插件及其相关 packages 开发的架构实践总结](https://github.com/Nealyang/PersonalBlog/issues/99)
106 | - [自动化生成骨架屏的技术方案设计与落地](https://github.com/Nealyang/PersonalBlog/issues/108)
107 | - [BeeMa架构:赋能业务源码开发](https://github.com/Nealyang/PersonalBlog/issues/103)
108 |
109 |
110 | ## 前端综合
111 | - [不就是跨域么。。。慌个xx](https://github.com/Nealyang/YOU-SHOULD-KNOW-JS/blob/master/doc/basic_js/JavaScript%E4%B8%AD%E7%9A%84%E8%B7%A8%E5%9F%9F%E6%80%BB%E7%BB%93.md?1536536995051)
112 | - [编写高质量代码基本要点](./OnceTheBlog/编写高质量代码基本要点.md)
113 | - [一张页面引起的前端架构思考](https://github.com/Nealyang/PersonalBlog/issues/64)
114 |
115 | ## 杂谈前端
116 |
117 | - [Nealyang 2018 前端路](https://github.com/Nealyang/PersonalBlog/issues/13)
118 | - [Nealyang 全栈前端](https://github.com/Nealyang/PersonalBlog/issues/46)
119 | - [一个优秀的前端都应该阅读这些文章](https://github.com/Nealyang/PersonalBlog/issues/48)
120 | - [学习方法分享:为何一年半就能拿到大厂 offer](https://github.com/Nealyang/PersonalBlog/issues/49)
121 | - [大揭秘!“恐怖”的阿里一面,我究竟想问什么](https://github.com/Nealyang/PersonalBlog/issues/50)
122 |
123 | ## 书虫录
124 | - [富爸爸穷爸爸](https://github.com/Nealyang/PersonalBlog/issues/62)
125 | - [小狗钱钱](https://github.com/Nealyang/PersonalBlog/issues/63)
126 |
127 | ## 技术交流
128 |
129 | ### 微信公众号 && 微信群
130 |
131 | - 关注公众号【全栈前端精选】,每日获取好文推荐
132 | - 添加微信号:is_Nealyang(备注来源) ,入群交流
133 |
134 | 公众号【全栈前端精选】 | 个人微信【is_Nealyang】 |
135 | -|-|
136 |  |  |
137 |
138 |
--------------------------------------------------------------------------------
/blogImg/47/6E39C3BFC492366A4D3231206111C2C1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nealyang/PersonalBlog/4686a149b4ea145dd4c9d5981e04770cf3d98b7b/blogImg/47/6E39C3BFC492366A4D3231206111C2C1.jpg
--------------------------------------------------------------------------------
/blogImg/47/pai_filter.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nealyang/PersonalBlog/4686a149b4ea145dd4c9d5981e04770cf3d98b7b/blogImg/47/pai_filter.gif
--------------------------------------------------------------------------------
/blogImg/47/zhu_filter.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nealyang/PersonalBlog/4686a149b4ea145dd4c9d5981e04770cf3d98b7b/blogImg/47/zhu_filter.gif
--------------------------------------------------------------------------------
/hooks/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/hooks/images.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg'
2 | declare module '*.png'
3 | declare module '*.jpg'
4 | declare module '*.jpeg'
5 | declare module '*.gif'
6 | declare module '*.bmp'
7 | declare module '*.tiff'
8 |
--------------------------------------------------------------------------------
/hooks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hooks",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.9.0",
7 | "react-dom": "^16.9.0",
8 | "react-scripts-ts": "3.1.0"
9 | },
10 | "scripts": {
11 | "start": "react-scripts-ts start",
12 | "build": "react-scripts-ts build",
13 | "test": "react-scripts-ts test --env=jsdom",
14 | "eject": "react-scripts-ts eject"
15 | },
16 | "devDependencies": {
17 | "@types/jest": "^24.0.18",
18 | "@types/node": "^12.7.3",
19 | "@types/react": "^16.9.2",
20 | "@types/react-dom": "^16.9.0",
21 | "typescript": "^3.6.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/hooks/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nealyang/PersonalBlog/4686a149b4ea145dd4c9d5981e04770cf3d98b7b/hooks/public/favicon.ico
--------------------------------------------------------------------------------
/hooks/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/hooks/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/hooks/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | @keyframes App-logo-spin {
26 | from { transform: rotate(0deg); }
27 | to { transform: rotate(360deg); }
28 | }
29 |
--------------------------------------------------------------------------------
/hooks/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/hooks/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './App.css';
3 |
4 | import logo from './logo.svg';
5 |
6 | class App extends React.Component {
7 | public render() {
8 | return (
9 |
10 |
11 |
12 | Welcome to React
13 |
14 |
15 | To get started, edit src/App.tsx
and save to reload.
16 |
17 |
18 | );
19 | }
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/hooks/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/hooks/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import App from './App';
4 | import './index.css';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(
8 | ,
9 | document.getElementById('root') as HTMLElement
10 | );
11 | registerServiceWorker();
12 |
--------------------------------------------------------------------------------
/hooks/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/hooks/src/registerServiceWorker.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable:no-console
2 | // In production, we register a service worker to serve assets from local cache.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on the 'N+1' visit to a page, since previously
7 | // cached resources are updated in the background.
8 |
9 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
10 | // This link also includes instructions on opting out of this behavior.
11 |
12 | const isLocalhost = Boolean(
13 | window.location.hostname === 'localhost' ||
14 | // [::1] is the IPv6 localhost address.
15 | window.location.hostname === '[::1]' ||
16 | // 127.0.0.1/8 is considered localhost for IPv4.
17 | window.location.hostname.match(
18 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
19 | )
20 | );
21 |
22 | export default function register() {
23 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
24 | // The URL constructor is available in all browsers that support SW.
25 | const publicUrl = new URL(
26 | process.env.PUBLIC_URL!,
27 | window.location.toString()
28 | );
29 | if (publicUrl.origin !== window.location.origin) {
30 | // Our service worker won't work if PUBLIC_URL is on a different origin
31 | // from what our page is served on. This might happen if a CDN is used to
32 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
33 | return;
34 | }
35 |
36 | window.addEventListener('load', () => {
37 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
38 |
39 | if (isLocalhost) {
40 | // This is running on localhost. Lets check if a service worker still exists or not.
41 | checkValidServiceWorker(swUrl);
42 |
43 | // Add some additional logging to localhost, pointing developers to the
44 | // service worker/PWA documentation.
45 | navigator.serviceWorker.ready.then(() => {
46 | console.log(
47 | 'This web app is being served cache-first by a service ' +
48 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
49 | );
50 | });
51 | } else {
52 | // Is not local host. Just register service worker
53 | registerValidSW(swUrl);
54 | }
55 | });
56 | }
57 | }
58 |
59 | function registerValidSW(swUrl: string) {
60 | navigator.serviceWorker
61 | .register(swUrl)
62 | .then(registration => {
63 | registration.onupdatefound = () => {
64 | const installingWorker = registration.installing;
65 | if (installingWorker) {
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the old content will have been purged and
70 | // the fresh content will have been added to the cache.
71 | // It's the perfect time to display a 'New content is
72 | // available; please refresh.' message in your web app.
73 | console.log('New content is available; please refresh.');
74 | } else {
75 | // At this point, everything has been precached.
76 | // It's the perfect time to display a
77 | // 'Content is cached for offline use.' message.
78 | console.log('Content is cached for offline use.');
79 | }
80 | }
81 | };
82 | }
83 | };
84 | })
85 | .catch(error => {
86 | console.error('Error during service worker registration:', error);
87 | });
88 | }
89 |
90 | function checkValidServiceWorker(swUrl: string) {
91 | // Check if the service worker can be found. If it can't reload the page.
92 | fetch(swUrl)
93 | .then(response => {
94 | // Ensure service worker exists, and that we really are getting a JS file.
95 | if (
96 | response.status === 404 ||
97 | response.headers.get('content-type')!.indexOf('javascript') === -1
98 | ) {
99 | // No service worker found. Probably a different app. Reload the page.
100 | navigator.serviceWorker.ready.then(registration => {
101 | registration.unregister().then(() => {
102 | window.location.reload();
103 | });
104 | });
105 | } else {
106 | // Service worker found. Proceed as normal.
107 | registerValidSW(swUrl);
108 | }
109 | })
110 | .catch(() => {
111 | console.log(
112 | 'No internet connection found. App is running in offline mode.'
113 | );
114 | });
115 | }
116 |
117 | export function unregister() {
118 | if ('serviceWorker' in navigator) {
119 | navigator.serviceWorker.ready.then(registration => {
120 | registration.unregister();
121 | });
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/hooks/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "outDir": "build/dist",
5 | "module": "esnext",
6 | "target": "es5",
7 | "lib": ["es6", "dom"],
8 | "sourceMap": true,
9 | "allowJs": true,
10 | "jsx": "react",
11 | "moduleResolution": "node",
12 | "rootDir": "src",
13 | "forceConsistentCasingInFileNames": true,
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noImplicitAny": true,
17 | "importHelpers": true,
18 | "strictNullChecks": true,
19 | "suppressImplicitAnyIndexErrors": true,
20 | "noUnusedLocals": true
21 | },
22 | "exclude": [
23 | "node_modules",
24 | "build",
25 | "scripts",
26 | "acceptance-tests",
27 | "webpack",
28 | "jest",
29 | "src/setupTests.ts"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/hooks/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json"
3 | }
--------------------------------------------------------------------------------
/hooks/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs"
5 | }
6 | }
--------------------------------------------------------------------------------
/hooks/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
3 | "linterOptions": {
4 | "exclude": [
5 | "config/**/*.js",
6 | "node_modules/**/*.ts",
7 | "coverage/lcov-report/*.js"
8 | ]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/img/qzqdjx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nealyang/PersonalBlog/4686a149b4ea145dd4c9d5981e04770cf3d98b7b/img/qzqdjx.jpg
--------------------------------------------------------------------------------
/img/wx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nealyang/PersonalBlog/4686a149b4ea145dd4c9d5981e04770cf3d98b7b/img/wx.jpg
--------------------------------------------------------------------------------
/lib/alibaba/阿里拍卖2020届毕业生校招启动.md:
--------------------------------------------------------------------------------
1 | # 阿里拍卖2020届毕业生校招启动
2 |
3 | ## 重估一切价值
4 |
5 | 阿里拍卖是全球最大的在线拍卖平台,是3000+法院信赖的司法处置平台,也是中国5亿新中产的网购顶级俱乐部。
6 | 拍卖产品技术团队作为拍卖产品技术的核心力量,为阿里拍卖这个地球上最活跃的拍卖平台,提供产品设计规划与技术解决方案,推动拍卖相关产品从设计到落地实现,支撑珍品、司法、资产等一系列拍卖业务的发展,与拍卖业务共同成长。
7 |
8 | 2020届毕业生招聘正式开始啦~ 重估一切价值,期待有你同行!
9 |
10 | ## 快乐工作、认真生活
11 |
12 | 
13 | #### 520拍卖节庆功宴
14 | 
15 | #### 520拍卖节部门狂欢
16 | 
17 | #### 前端团队team building
18 | 
19 | 
20 | #### 饭后团队桌上足球比赛
21 | 
22 | 
23 | #### 部门出游
24 | 
25 |
26 |
27 | ## 校招风云人物
28 |
29 | #### 霖涛 2017 届校招生,毕业于中国科学院大学,高级开发工程师
30 |
31 | - 从初入职场的紧张迷茫到融入拍卖大家庭的轻松融洽,从怀抱技术梦想到意识到技术价值真正体现,我来到阿里拍卖一年成功晋升到 P6。
32 | - 在这一年中,我和阿里拍卖的小伙伴们一起,用技术推动拍卖这一古老交易方式的革新,用拍卖重估世界的价值。技术是一种工具,需要在实际生活中找到其适用场景和落脚点,立足业务,赋能业务,体现出技术的价值,而拍卖正是这样一个能够体现技术价值的业务。相比于传统的业务实现,拍卖的高并发、高稳定、高可用的业务特性为我们的技术实现提出了更高的要求,也更能体现出技术的价值。
33 | - 阿里拍卖团队浓厚的技术氛围和融洽的团队氛围也为我们技术人创造技术价值提供了良好的环境。
34 |
35 |
36 | #### 奉雨 2015 届校招生,毕业于合肥工业大学,高级前端开发工程师
37 |
38 | - 我负责阿里拍卖的珍品类业务,包括拍卖主搜以及线下新零售产品大屏的开发和维护等。我是2014年通过实习生春招加入到阿里,14年暑假又通过了实习生转正面试然后留在了阿里,在这里实习了半年,15年正式入职加入了阿里拍卖,到现在已经入职3年多了,见证了拍卖团队从不到100人扩大到了200人的大团队。
39 | - 刚入职的时候,觉得自己很菜,幸运的是阿里的体系里,新人入职都会分配一个师兄,师兄会带着做业务,也会跟着做一些技术项目,工作一年,基本上就可以独立承担一些业务和项目了。
40 | - 大学毕业工作这三年时间里,也有过很多煎熬的时刻,有项目临交付的时候在项目室吃盒饭加班赶进度,无数个夜晚咬牙坚持着;也有在马来西亚 EWTP 现场活动前一天改需求,充斥着紧张和压力;还有过辛苦做了三个月的产品交付了因为性能不过关被业务方挑战着,然后含泪改、打磨。就像马总说的那句话,来阿里巴巴不保证你能赚钱,能晋升,但保证你一定会受委屈。
41 | - 所幸受过的这些委屈和煎熬,都是值得的,今年6月份,晋升通过,恰逢入职3周年,在阿里3年醇了,希望能走过5年,因为为这家有使命感的公司工作,我很自豪。
42 |
43 |
44 | #### 玄农 2014 届校招生,毕业于北京邮电大学,高级前端开发工程师
45 |
46 | - 在阿里拍卖部门已经 3 年有余,在这些日子里,业务上既有团队合作也有“单兵作战”的场景,这样既锻炼业务规划、技术方案方面的能力,也培养出独挡一面的能力。
47 | - 随着业务的不断深入,逐渐会发现业务需求并非仅仅是一个点,往往是一个面,更重要的是后续的可维护性、系统的稳定性,这些推动自己在技术实现层面更多考虑程序代码的稳健性以及可扩展性。
48 | - 随着编程经验的积累、能力的提升,编程经验也从最初的组合使用工具,上升到源码阅读、构建工具开发的层面,逐渐完成从“我会使用工具”到“我能创造工具”角色的转换。在过去的两年里,阅读过 webpack 、mobx 等的源码,一方面能从较深层次了解内部运转机理而借此创建提高工作效率的工具,另一方面也能从这些优秀的框架里汲取优秀的编程理念,提升编程水平。
49 | - 在阿里拍卖,在前端部门,很幸运能和这么多对工作热情、对技术富有激情的工程师一起共事,良好的工作和学习氛围让我受益颇多。取长补短、集思广益,他们教会了我团队合作,也教会我以良好的心态、正确的方法去应对工作和技术上的难题。
50 |
51 | ## 拍世界、由你造
52 |
53 | 直接微信扫描二维码,我们期待你的加入
54 |
55 | BU内推直通车二维码:(优先指定评估人)
56 |
57 | 
58 |
59 |
60 | 详情加微信,获取更多内推信息:
61 |
62 | 
63 |
--------------------------------------------------------------------------------
/lib/es6Env/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015"
4 | ],
5 | "plugins": [
6 |
7 | ]
8 | }
--------------------------------------------------------------------------------
/lib/es6Env/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/lib/es6Env/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/es6Env/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nealyang/PersonalBlog/4686a149b4ea145dd4c9d5981e04770cf3d98b7b/lib/es6Env/index.js
--------------------------------------------------------------------------------
/lib/es6Env/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "es6env",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "webpack-dev-server"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {},
13 | "devDependencies": {
14 | "babel-core": "^6.26.3",
15 | "babel-loader": "^7.1.5",
16 | "babel-preset-es2015": "^6.24.1",
17 | "webpack": "^4.17.2",
18 | "webpack-cli": "^3.1.0",
19 | "webpack-dev-server": "^3.1.7"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/es6Env/src/index.js:
--------------------------------------------------------------------------------
1 | import {diffList as diff} from './lib/diffList';
2 |
3 | var oldList = [{id: "a"}, {id: "b"}, {id: "c"}, {id: "d"}, {id: "e"}]
4 | var newList = [{id: "c"}, {id: "a"}, {id: "b"}, {id: "e"}, {id: "f"}]
5 |
6 | var moves = diff(oldList, newList, "id")
7 | // `moves` is a sequence of actions (remove or insert):
8 | // type 0 is removing, type 1 is inserting
9 | // moves: [
10 | // {index: 3, type: 0},
11 | // {index: 0, type: 1, item: {id: "c"}},
12 | // {index: 3, type: 0},
13 | // {index: 4, type: 1, item: {id: "f"}}
14 | // ]
15 | console.log(moves)
16 | moves.moves.forEach(function(move) {
17 | if (move.type === 0) {
18 | oldList.splice(move.index, 1) // type 0 is removing
19 | } else {
20 | oldList.splice(move.index, 0, move.item) // type 1 is inserting
21 | }
22 | })
23 |
24 | // now `oldList` is equal to `newList`
25 | // [{id: "c"}, {id: "a"}, {id: "b"}, {id: "e"}, {id: "f"}]
26 | console.log(oldList)
--------------------------------------------------------------------------------
/lib/es6Env/src/lib/diff.js:
--------------------------------------------------------------------------------
1 | var _ = require('./util')
2 | var patch = require('./patch')
3 | import {
4 | diffList
5 | } from './lib/diffList';
6 |
7 | function diff(oldTree, newTree) {
8 | var index = 0
9 | var patches = {}
10 | dfsWalk(oldTree, newTree, index, patches)
11 | return patches
12 | }
13 |
14 |
15 | function dfsWalk(oldNode, newNode, index, patches) {
16 | var currentPatch = []
17 |
18 | // Node is removed.
19 | if (newNode === null) {
20 | // Real DOM node will be removed when perform reordering, so has no needs to do anthings in here
21 | // TextNode content replacing
22 | } else if (_.isString(oldNode) && _.isString(newNode)) {
23 | if (newNode !== oldNode) {
24 | currentPatch.push({
25 | type: patch.TEXT,
26 | content: newNode
27 | })
28 | }
29 | // Nodes are the same, diff old node's props and children
30 | } else if (
31 | oldNode.tagName === newNode.tagName &&
32 | oldNode.key === newNode.key
33 | ) {
34 | // Diff props
35 | var propsPatches = diffProps(oldNode, newNode)
36 | if (propsPatches) {
37 | currentPatch.push({
38 | type: patch.PROPS,
39 | props: propsPatches
40 | })
41 | }
42 | // Diff children. If the node has a `ignore` property, do not diff children
43 | if (!isIgnoreChildren(newNode)) {
44 | diffChildren(
45 | oldNode.children,
46 | newNode.children,
47 | index,
48 | patches,
49 | currentPatch
50 | )
51 | }
52 | // Nodes are not the same, replace the old node with new node
53 | } else {
54 | currentPatch.push({
55 | type: patch.REPLACE,
56 | node: newNode
57 | })
58 | }
59 |
60 | if (currentPatch.length) {
61 | patches[index] = currentPatch
62 | }
63 | }
64 |
65 | function diffChildren(oldChildren, newChildren, index, patches, currentPatch) {
66 | var diffs = diffList(oldChildren, newChildren, 'key')
67 | newChildren = diffs.children
68 |
69 | if (diffs.moves.length) {
70 | var reorderPatch = {
71 | type: patch.REORDER,
72 | moves: diffs.moves
73 | }
74 | currentPatch.push(reorderPatch)
75 | }
76 |
77 | var leftNode = null
78 | var currentNodeIndex = index
79 | _.each(oldChildren, function (child, i) {
80 | var newChild = newChildren[i]
81 | currentNodeIndex = (leftNode && leftNode.count) ?
82 | currentNodeIndex + leftNode.count + 1 :
83 | currentNodeIndex + 1
84 | dfsWalk(child, newChild, currentNodeIndex, patches)
85 | leftNode = child
86 | })
87 | }
88 |
89 | function diffProps(oldNode, newNode) {
90 | var count = 0
91 | var oldProps = oldNode.props
92 | var newProps = newNode.props
93 |
94 | var key, value
95 | var propsPatches = {}
96 |
97 | // Find out different properties
98 | for (key in oldProps) {
99 | value = oldProps[key]
100 | if (newProps[key] !== value) {
101 | count++
102 | propsPatches[key] = newProps[key]
103 | }
104 | }
105 |
106 | // Find out new property
107 | for (key in newProps) {
108 | value = newProps[key]
109 | if (!oldProps.hasOwnProperty(key)) {
110 | count++
111 | propsPatches[key] = newProps[key]
112 | }
113 | }
114 |
115 | // If properties all are identical
116 | if (count === 0) {
117 | return null
118 | }
119 |
120 | return propsPatches
121 | }
122 |
123 | function isIgnoreChildren(node) {
124 | return (node.props && node.props.hasOwnProperty('ignore'))
125 | }
126 |
127 | module.exports = diff
--------------------------------------------------------------------------------
/lib/es6Env/src/lib/diffList.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Diff two list in O(N).
3 | * @param {Array} oldList - Original List
4 | * @param {Array} newList - List After certain insertions, removes, or moves
5 | * @return {Object} - {moves: }
6 | * - moves is a list of actions that telling how to remove and insert
7 | */
8 | function diff (oldList, newList, key) {
9 | var oldMap = makeKeyIndexAndFree(oldList, key)
10 | var newMap = makeKeyIndexAndFree(newList, key)
11 |
12 | var newFree = newMap.free
13 |
14 | var oldKeyIndex = oldMap.keyIndex
15 | var newKeyIndex = newMap.keyIndex
16 |
17 | var moves = []
18 |
19 | // a simulate list to manipulate
20 | var children = []
21 | var i = 0
22 | var item
23 | var itemKey
24 | var freeIndex = 0
25 |
26 | // first pass to check item in old list: if it's removed or not
27 | // 遍历旧的集合
28 | while (i < oldList.length) {
29 | item = oldList[i]
30 | itemKey = getItemKey(item, key)//itemKey a
31 | // 是否可以取到
32 | if (itemKey) {
33 | // 判断新集合中是否有这个属性,如果没有则push null
34 | if (!newKeyIndex.hasOwnProperty(itemKey)) {
35 | children.push(null)
36 | } else {
37 | // 如果有 去除在新列表中的位置
38 | var newItemIndex = newKeyIndex[itemKey]
39 | children.push(newList[newItemIndex])
40 | }
41 | } else {
42 | var freeItem = newFree[freeIndex++]
43 | children.push(freeItem || null)
44 | }
45 | i++
46 | }
47 |
48 | // children [{id:"a"},{id:"b"},{id:"c"},null,{id:"e"}]
49 |
50 | var simulateList = children.slice(0)//[{id:"a"},{id:"b"},{id:"c"},null,{id:"e"}]
51 |
52 | // remove items no longer exist
53 | i = 0
54 | while (i < simulateList.length) {
55 | if (simulateList[i] === null) {
56 | remove(i)
57 | removeSimulate(i)
58 | } else {
59 | i++
60 | }
61 | }
62 |
63 | // i is cursor pointing to a item in new list
64 | // j is cursor pointing to a item in simulateList
65 | var j = i = 0
66 | while (i < newList.length) {
67 | item = newList[i]
68 | itemKey = getItemKey(item, key)//c
69 |
70 | var simulateItem = simulateList[j] //{id:"a"}
71 | var simulateItemKey = getItemKey(simulateItem, key)//a
72 |
73 | if (simulateItem) {
74 | if (itemKey === simulateItemKey) {
75 | j++
76 | } else {
77 | // 新增项,直接插入
78 | if (!oldKeyIndex.hasOwnProperty(itemKey)) {
79 | insert(i, item)
80 | } else {
81 | // if remove current simulateItem make item in right place
82 | // then just remove it
83 | var nextItemKey = getItemKey(simulateList[j + 1], key)
84 | if (nextItemKey === itemKey) {
85 | remove(i)
86 | removeSimulate(j)
87 | j++ // after removing, current j is right, just jump to next one
88 | } else {
89 | // else insert item
90 | insert(i, item)
91 | }
92 | }
93 | }
94 | } else {
95 | insert(i, item)
96 | }
97 |
98 | i++
99 | }
100 |
101 | //if j is not remove to the end, remove all the rest item
102 | var k = simulateList.length - j
103 | while (j++ < simulateList.length) {
104 | k--
105 | remove(k + i)
106 | }
107 |
108 |
109 | // 记录旧的列表中移除项 {index:3,type:0}
110 | function remove (index) {
111 | var move = {index: index, type: 0}
112 | moves.push(move)
113 | }
114 |
115 | function insert (index, item) {
116 | var move = {index: index, item: item, type: 1}
117 | moves.push(move)
118 | }
119 |
120 | // 删除simulateList中null
121 | function removeSimulate (index) {
122 | simulateList.splice(index, 1)
123 | }
124 |
125 | return {
126 | moves: moves,
127 | children: children
128 | }
129 | }
130 |
131 | /**
132 | * Convert list to key-item keyIndex object.
133 | * 将列表转换为 key-item 的键值对象
134 | * [{id: "a"}, {id: "b"}, {id: "c"}, {id: "d"}, {id: "e"}] -> [a:0,b:1,c:2...]
135 | * @param {Array} list
136 | * @param {String|Function} key
137 | */
138 | function makeKeyIndexAndFree (list, key) {
139 | var keyIndex = {}
140 | var free = []
141 | for (var i = 0, len = list.length; i < len; i++) {
142 | var item = list[i]
143 | var itemKey = getItemKey(item, key)
144 | if (itemKey) {
145 | keyIndex[itemKey] = i
146 | } else {
147 | free.push(item)
148 | }
149 | }
150 | return {
151 | keyIndex: keyIndex,
152 | free: free
153 | }
154 | }
155 |
156 | // 获取置顶key的value
157 | function getItemKey (item, key) {
158 | if (!item || !key) return void 666
159 | return typeof key === 'string'
160 | ? item[key]
161 | : key(item)
162 | }
163 |
164 | exports.makeKeyIndexAndFree = makeKeyIndexAndFree
165 | exports.diffList = diff
--------------------------------------------------------------------------------
/lib/es6Env/src/lib/element.js:
--------------------------------------------------------------------------------
1 | var _ = require('./util')
2 |
3 | function Element(tagName, props, children) {
4 | if (!(this instanceof Element)) {
5 | if (!_.isArray(children) && children != null) {
6 | children = _.slice(arguments, 2).filter(_.truthy)
7 | }
8 | return new Element(tagName, props, children)
9 | }
10 |
11 | if (_.isArray(props)) {
12 | children = props
13 | props = {}
14 | }
15 |
16 | this.tagName = tagName
17 | this.props = props || {}
18 | this.children = children || []
19 | this.key = props ?
20 | props.key :
21 | void 666
22 |
23 | var count = 0
24 |
25 | _.each(this.children, function (child, i) {
26 | if (child instanceof Element) {
27 | count += child.count
28 | } else {
29 | children[i] = '' + child
30 | }
31 | count++
32 | })
33 |
34 | this.count = count
35 | }
36 |
37 | Element.prototype.render = function () {
38 | var el = document.createElement(this.tagName)
39 | var props = this.props
40 |
41 | for (var propName in props) {
42 | var propValue = props[propName]
43 | _.setAttr(el, propName, propValue)
44 | }
45 |
46 | _.each(this.children, function (child) {
47 | var childEl = (child instanceof Element)
48 | ? child.render()
49 | : document.createTextNode(child)
50 | el.appendChild(childEl)
51 | })
52 |
53 | return el
54 | }
55 |
56 | module.exports = Element
--------------------------------------------------------------------------------
/lib/es6Env/src/lib/patche.js:
--------------------------------------------------------------------------------
1 |
2 | var _ = require('./util')
3 |
4 | var REPLACE = 0
5 | var REORDER = 1
6 | var PROPS = 2
7 | var TEXT = 3
8 |
9 | function patch (node, patches) {
10 | var walker = {index: 0}
11 | dfsWalk(node, walker, patches)
12 | }
13 |
14 | function dfsWalk (node, walker, patches) {
15 | var currentPatches = patches[walker.index]
16 |
17 | var len = node.childNodes
18 | ? node.childNodes.length
19 | : 0
20 | for (var i = 0; i < len; i++) {
21 | var child = node.childNodes[i]
22 | walker.index++
23 | dfsWalk(child, walker, patches)
24 | }
25 |
26 | if (currentPatches) {
27 | applyPatches(node, currentPatches)
28 | }
29 | }
30 |
31 | function applyPatches (node, currentPatches) {
32 | _.each(currentPatches, function (currentPatch) {
33 | switch (currentPatch.type) {
34 | case REPLACE:
35 | var newNode = (typeof currentPatch.node === 'string')
36 | ? document.createTextNode(currentPatch.node)
37 | : currentPatch.node.render()
38 | node.parentNode.replaceChild(newNode, node)
39 | break
40 | case REORDER:
41 | reorderChildren(node, currentPatch.moves)
42 | break
43 | case PROPS:
44 | setProps(node, currentPatch.props)
45 | break
46 | case TEXT:
47 | if (node.textContent) {
48 | node.textContent = currentPatch.content
49 | } else {
50 | // fuck ie
51 | node.nodeValue = currentPatch.content
52 | }
53 | break
54 | default:
55 | throw new Error('Unknown patch type ' + currentPatch.type)
56 | }
57 | })
58 | }
59 |
60 | function setProps (node, props) {
61 | for (var key in props) {
62 | if (props[key] === void 666) {
63 | node.removeAttribute(key)
64 | } else {
65 | var value = props[key]
66 | _.setAttr(node, key, value)
67 | }
68 | }
69 | }
70 |
71 | function reorderChildren (node, moves) {
72 | var staticNodeList = _.toArray(node.childNodes)
73 | var maps = {}
74 |
75 | _.each(staticNodeList, function (node) {
76 | if (node.nodeType === 1) {
77 | var key = node.getAttribute('key')
78 | if (key) {
79 | maps[key] = node
80 | }
81 | }
82 | })
83 |
84 | _.each(moves, function (move) {
85 | var index = move.index
86 | if (move.type === 0) { // remove item
87 | if (staticNodeList[index] === node.childNodes[index]) { // maybe have been removed for inserting
88 | node.removeChild(node.childNodes[index])
89 | }
90 | staticNodeList.splice(index, 1)
91 | } else if (move.type === 1) { // insert item
92 | var insertNode = maps[move.item.key]
93 | ? maps[move.item.key].cloneNode(true) // reuse old item
94 | : (typeof move.item === 'object')
95 | ? move.item.render()
96 | : document.createTextNode(move.item)
97 | staticNodeList.splice(index, 0, insertNode)
98 | node.insertBefore(insertNode, node.childNodes[index] || null)
99 | }
100 | })
101 | }
102 |
103 | patch.REPLACE = REPLACE
104 | patch.REORDER = REORDER
105 | patch.PROPS = PROPS
106 | patch.TEXT = TEXT
107 |
108 | module.exports = patch
--------------------------------------------------------------------------------
/lib/es6Env/src/lib/util.js:
--------------------------------------------------------------------------------
1 | var _ = exports
2 |
3 | _.type = function (obj) {
4 | return Object.prototype.toString.call(obj).replace(/\[object\s|\]/g, '')
5 | }
6 |
7 | _.isArray = function isArray (list) {
8 | return _.type(list) === 'Array'
9 | }
10 |
11 | _.slice = function slice (arrayLike, index) {
12 | return Array.prototype.slice.call(arrayLike, index)
13 | }
14 |
15 | _.truthy = function truthy (value) {
16 | return !!value
17 | }
18 |
19 | _.isString = function isString (list) {
20 | return _.type(list) === 'String'
21 | }
22 |
23 | _.each = function each (array, fn) {
24 | for (var i = 0, len = array.length; i < len; i++) {
25 | fn(array[i], i)
26 | }
27 | }
28 |
29 | _.toArray = function toArray (listLike) {
30 | if (!listLike) {
31 | return []
32 | }
33 |
34 | var list = []
35 |
36 | for (var i = 0, len = listLike.length; i < len; i++) {
37 | list.push(listLike[i])
38 | }
39 |
40 | return list
41 | }
42 |
43 | _.setAttr = function setAttr (node, key, value) {
44 | switch (key) {
45 | case 'style':
46 | node.style.cssText = value
47 | break
48 | case 'value':
49 | var tagName = node.tagName || ''
50 | tagName = tagName.toLowerCase()
51 | if (
52 | tagName === 'input' || tagName === 'textarea'
53 | ) {
54 | node.value = value
55 | } else {
56 | // if it is not a input or textarea, use `setAttribute` to set
57 | node.setAttribute(key, value)
58 | }
59 | break
60 | default:
61 | node.setAttribute(key, value)
62 | break
63 | }
64 | }
--------------------------------------------------------------------------------
/lib/es6Env/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | module.exports = {
3 | entry:'./src/index.js',
4 | output:{
5 | filename:'prd/build.js'
6 | },
7 | module:{
8 | rules:[
9 | {test:/\.js$/,loader:'babel-loader'}
10 | ]
11 | },
12 | resolve: {
13 | extensions: ['.js', '.jsx']
14 | },
15 | plugins: [
16 | new webpack.HotModuleReplacementPlugin(),
17 | ],
18 | devtool:'eval-source-map',
19 | mode: "development"
20 | }
--------------------------------------------------------------------------------
/lib/img/Wechat.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nealyang/PersonalBlog/4686a149b4ea145dd4c9d5981e04770cf3d98b7b/lib/img/Wechat.jpeg
--------------------------------------------------------------------------------
/lib/jsoo/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env"
4 | ],
5 | "plugins": [
6 | "transform-runtime"
7 | ]
8 | }
--------------------------------------------------------------------------------
/lib/jsoo/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/lib/jsoo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | javascript组件化
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/jsoo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "neal",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "babel src/index.js -o dist/index.js",
9 | "start": "npx webpack-dev-server --config webpack.config.js --color --progress --hot",
10 | "builds": "npx webpack --config webpack.config.js"
11 | },
12 | "author": "Nealyang",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "babel-cli": "^6.26.0",
16 | "babel-core": "^6.26.3",
17 | "babel-loader": "^7.1.5",
18 | "babel-plugin-transform-runtime": "^6.23.0",
19 | "babel-polyfill": "^6.26.0",
20 | "babel-preset-env": "^1.7.0",
21 | "clean-webpack-plugin": "^0.1.19",
22 | "webpack": "^4.16.2"
23 | },
24 | "dependencies": {
25 | "babel-runtime": "^6.26.0",
26 | "webpack-cli": "^3.1.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/jsoo/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 一凨
3 | * @Date: 2018-07-25 10:01:10
4 | * @Last Modified by: 一凨
5 | * @Last Modified time: 2018-07-25 17:25:56
6 | */
7 | // $(function() {
8 |
9 | // var input = $('#J_input');
10 |
11 | // //用来获取字数
12 | // function value(){
13 | // return input.val();
14 | // }
15 |
16 | // //渲染元素
17 | // function render(){
18 | // var num = value();
19 | // //没有字数的容器就新建一个
20 | // if ($('#J_input_text').length == 0) {
21 | // input.after('');
22 | // };
23 |
24 | // $('#J_input_text').html(num);
25 | // }
26 |
27 | // //监听事件
28 | // input.on('keyup',function(){
29 | // render();
30 | // });
31 |
32 | // //初始化,第一次渲染
33 | // render();
34 | // })
35 |
36 |
37 | // 单变量模拟命名空间
38 |
39 | // const bindValue = {
40 | // input: null,
41 | // init(config) {
42 | // this.input = $(config.id);
43 | // this.addListener();
44 | // return this;
45 | // },
46 | // addListener() {
47 | // let self = this;
48 | // this.input.on('keyup', () => {
49 | // self.render();
50 | // })
51 | // },
52 | // getValue() {
53 | // return this.input.val();
54 | // },
55 | // render() {
56 | // let value = this.getValue();
57 | // if ($('#J_input_text').length == 0) {
58 | // this.input.after('');
59 | // };
60 |
61 | // $('#J_input_text').html(value);
62 | // }
63 | // }
64 | // $(function () {
65 | // bindValue.init({id:'#J_input'}).render()
66 | // })
67 |
68 | // 闭包
69 |
70 | const BindValue = (function () {
71 | // 私有方法
72 | const _addListener = that => {
73 | that.input.on('keyup', () => {
74 | that.render();
75 | });
76 | }
77 |
78 | const _getValue = that => {
79 | return that.input.val();
80 | }
81 |
82 | const ValueFunc = function (config) {};
83 | ValueFunc.prototype.init = function (config) {
84 | this.input = $(config.id);
85 | _addListener(this);
86 | return this;
87 | }
88 | ValueFunc.prototype.render = function () {
89 | let value = _getValue(this);
90 | if ($('#J_input_text').length == 0) {
91 | this.input.after('');
92 | };
93 |
94 | $('#J_input_text').html(value);
95 | }
96 |
97 | return ValueFunc;
98 | })();
99 |
100 | $(function () {
101 | new BindValue().init({
102 | id: '#J_input'
103 | }).render();
104 | })
--------------------------------------------------------------------------------
/lib/jsoo/test.html:
--------------------------------------------------------------------------------
1 | testagergar
2 | eagreag
3 |
4 |
5 | aergreghrteh6j
6 |
7 |
8 | dfgshdytjuykliu;y
9 |
10 |
11 | fsgtrhjytkuytliikurjyrhstge
12 |
--------------------------------------------------------------------------------
/lib/jsoo/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CleanWebpackPlugin = require('clean-webpack-plugin');
3 | module.exports = {
4 | entry: [
5 | "babel-polyfill",
6 | path.join(__dirname, './src/index.js')
7 | ],
8 | output: {
9 | filename: "./index.js",
10 | },
11 | module: {
12 | rules: [{
13 | test: /\.js$/,
14 | use: ['babel-loader'],
15 | include: path.join(__dirname, 'src'),
16 | exclude: /node_modules/
17 | }]
18 | },
19 | resolve: {
20 | extensions: ['.js', '.jsx']
21 | },
22 | mode: 'development',
23 | plugins:[
24 | new CleanWebpackPlugin(['bundle'])
25 | ],
26 | devServer: {
27 | contentBase: path.join(__dirname, 'dist'), //启动路径
28 | host:'localhost', //域名
29 | port: 8018, //端口号
30 | }
31 | };
--------------------------------------------------------------------------------
/lib/preReact/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/react",
4 | "@babel/stage-2"
5 | ],
6 | "plugins": [
7 | [
8 | "@babel/plugin-transform-react-jsx",
9 | {
10 | "pragma": "createElement",
11 | "pragmaFrag": "Fragment"
12 | }
13 | ],
14 | "@babel/plugin-proposal-class-properties"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/lib/preReact/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules/**
2 | **/dist/**
3 | **/coverage/**
4 | **/lib/**
5 |
--------------------------------------------------------------------------------
/lib/preReact/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends":["eslint-config-airbnb"],
3 | "env":{
4 | "browser":true,
5 | "node":true,
6 | "mocha":true,
7 | "jest":true,
8 | "es6":true
9 | },
10 | "parser":"babel-eslint",
11 | "parserOptions":{
12 | "ecmaVersion":6,
13 | "ecmaFeatures":{
14 | "jsx":true,
15 | "experimentalObjectRestSpread":true
16 | }
17 | },
18 | "plugins":[
19 | "react",
20 | "babel"],
21 | "rules":{
22 | "func-names":0,
23 | "arrow-body-style":0,
24 | "react/sort-comp":0,
25 | "react/prop-types":0,
26 | "react/jsx-first-prop-new-line":0,
27 | "react/jsx-filename-extension": 0,
28 | "react/react-in-jsx-scope": 0,
29 | "react/no-children-prop": 0,
30 | "import/no-unresolved":0,
31 | "no-param-reassign":0,
32 | "no-return-assign":0,
33 | "max-len":0,
34 | "consistent-return":0,
35 | "no-redeclare":0,
36 | "semi": [2, "never"],
37 | "no-extra-semi":2,
38 | "import/no-extraneous-dependencies": 0,
39 | "import/extensions": 0,
40 | "jsx-a11y/no-static-element-interactions": 0,
41 | "jsx-a11y/href-no-hash": 0,
42 | "react/no-find-dom-node": 0,
43 | "import/imports-first": 0,
44 | "react/no-string-refs": 0,
45 | "import/prefer-default-export": 0,
46 | "react/forbid-prop-types": 0,
47 | "no-plusplus": 0,
48 | "no-continue": 0,
49 | "no-underscore-dangle": 0,
50 | "no-template-curly-in-string": 0,
51 | "no-nested-ternary": 0,
52 | "no-useless-escape": 0,
53 | "no-unused-vars": ["error", { "varsIgnorePattern": "(createElement|JSDOM)" }]
54 | }
55 | }
--------------------------------------------------------------------------------
/lib/preReact/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/
3 | .ipr
4 | .iws
5 | *~
6 | ~*
7 | *.diff
8 | *.patch
9 | *.bak
10 | .DS_Store
11 | Thumbs.db
12 | .project
13 | .*proj
14 | .svn/
15 | *.swp
16 | *.swo
17 | *.log
18 | node_modules/
19 | .buildpath
20 | .settings
21 | npm-debug.log
22 | nohup.out
23 | _site
24 | _data
25 | dist
26 | lib
27 | typings
28 | coverage
29 | .next
--------------------------------------------------------------------------------
/lib/preReact/README.md:
--------------------------------------------------------------------------------
1 | ## 第一堂作业:实现jsx解析器与渲染器
2 | - 先认真阅读doc目录下面的教材
3 | - 安装依赖运行npm start。
4 | - 调试playground.js,看看jsx编译后的结果是什么。
5 | - 实现一个数据结构,把jsx编译后的结构以嵌套形式保存在数据结构对象中(参考react渲染)。
6 | - 实现render,解析这个嵌套对象,并且把解析结果渲染到页面上。
7 | - 渲染可以调用dom.js里createElement函数。
8 |
9 | 有能力的同学可以自己实现事件,更新等内容。
10 |
11 | ## 提交方式
12 | - 每位同学需要自己建一个git仓库,可以选择github或者码云。
13 | - 每次作业写完后把代码提交到自己的github仓库里,并在README里写入自己的学习文章笔记,以增强学习效果,防止忘记 。
14 | - 然后把文章的链接提交到此地址,我看到后会把你拉入第二次课的微信群并发放第二次课程的资料,每周二一次课,如果过了截止日期将不再接受申请。
15 | - 本次课的开始时间为2018年7月17日23:59:59,截止时间为2018年7月30日23:59:59
16 | - 本次课程不收取任何费用
17 |
--------------------------------------------------------------------------------
/lib/preReact/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ariesate/boilerplate",
3 | "version": "0.0.1",
4 | "description": "you should not publish this package.",
5 | "main": "index.js",
6 | "devDependencies": {
7 | "@babel/core": "^7.0.0-beta.44",
8 | "@babel/plugin-proposal-class-properties": "^7.0.0-beta.44",
9 | "@babel/plugin-transform-react-jsx": "^7.0.0-beta.44",
10 | "@babel/preset-stage-2": "7.0.0-beta.44",
11 | "@babel/preset-env": "^7.0.0-beta.44",
12 | "@babel/preset-react": "^7.0.0-beta.44",
13 | "@babel/preset-stage-0": "^7.0.0-beta.44",
14 | "babel-loader": "^8.0.0-beta.2",
15 | "webpack": "^4.5.0",
16 | "webpack-cli": "^2.0.14",
17 | "webpack-dev-server": "^3.1.3"
18 | },
19 | "scripts": {
20 | "start": "webpack-dev-server",
21 | "lint": "eslint ./render/src",
22 | "eslint-fix": "eslint ./render/src --fix"
23 | },
24 | "author": "ariesate@outlook.com",
25 | "license": "MIT"
26 | }
27 |
--------------------------------------------------------------------------------
/lib/preReact/playground.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lib/preReact/src/playground.js:
--------------------------------------------------------------------------------
1 | import render from '../lib/render';
2 | import createElement from '../lib/createElement';
3 |
4 | class Component { };
5 |
6 | class HelloWorld extends Component {
7 | render() {
8 | return Hello World
9 |
alert('testFunc')}>testFunc
10 |
11 | }
12 | }
13 |
14 | // render('hello world',document.getElementById("root"));
15 | // render(alert(1)} style={{ color: 'red' }}>Nealyang
: study react
, document.getElementById("root"));
16 | render(,document.getElementById("root"));
--------------------------------------------------------------------------------
/lib/preReact/src/testDiff.js:
--------------------------------------------------------------------------------
1 |
2 | import {Element as el} from '../test-diff/element';
3 | import {patch} from '../test-diff/patch';
4 | import {diff} from '../test-diff/diff';
5 | var count = 0
6 | function renderTree () {
7 | console.log(1)
8 | debugger
9 | count++
10 | var items = []
11 | var color = (count % 2 === 0)
12 | ? 'blue'
13 | : 'red'
14 | for (var i = 0; i < count; i++) {
15 | items.push(el('li', ['Item #' + i]))
16 | }
17 | return el('div', {'id': 'container'}, [
18 | el('h1', {style: 'color: ' + color}, ['simple virtal dom']),
19 | el('p', ['the count is :' + count]),
20 | el('ul', items)
21 | ])
22 | }
23 | var tree = renderTree()
24 | // var root = tree.render()
25 | document.body.appendChild(root)
26 | // setInterval(function () {
27 | // var newTree = renderTree()
28 | // var patches = diff(tree, newTree)
29 | // console.log(patches)
30 | // patch(root, patches)
31 | // tree = newTree
32 | // }, 1000)
--------------------------------------------------------------------------------
/lib/preReact/test-diff/diff.js:
--------------------------------------------------------------------------------
1 | var _ = require('./util')
2 | var patch = require('./patch')
3 | var listDiff = require('./list-diff')
4 |
5 | function diff (oldTree, newTree) {
6 | var index = 0
7 | var patches = {}
8 | dfsWalk(oldTree, newTree, index, patches)
9 | return patches
10 | }
11 |
12 | function dfsWalk (oldNode, newNode, index, patches) {
13 | var currentPatch = []
14 |
15 | // Node is removed.
16 | if (newNode === null) {
17 | // Real DOM node will be removed when perform reordering, so has no needs to do anthings in here
18 | // TextNode content replacing
19 | } else if (_.isString(oldNode) && _.isString(newNode)) {
20 | if (newNode !== oldNode) {
21 | currentPatch.push({ type: patch.TEXT, content: newNode })
22 | }
23 | // Nodes are the same, diff old node's props and children
24 | } else if (
25 | oldNode.tagName === newNode.tagName &&
26 | oldNode.key === newNode.key
27 | ) {
28 | // Diff props
29 | var propsPatches = diffProps(oldNode, newNode)
30 | if (propsPatches) {
31 | currentPatch.push({ type: patch.PROPS, props: propsPatches })
32 | }
33 | // Diff children. If the node has a `ignore` property, do not diff children
34 | if (!isIgnoreChildren(newNode)) {
35 | diffChildren(
36 | oldNode.children,
37 | newNode.children,
38 | index,
39 | patches,
40 | currentPatch
41 | )
42 | }
43 | // Nodes are not the same, replace the old node with new node
44 | } else {
45 | currentPatch.push({ type: patch.REPLACE, node: newNode })
46 | }
47 |
48 | if (currentPatch.length) {
49 | patches[index] = currentPatch
50 | }
51 | }
52 |
53 | function diffChildren (oldChildren, newChildren, index, patches, currentPatch) {
54 | var diffs = listDiff(oldChildren, newChildren, 'key')
55 | newChildren = diffs.children
56 |
57 | if (diffs.moves.length) {
58 | var reorderPatch = { type: patch.REORDER, moves: diffs.moves }
59 | currentPatch.push(reorderPatch)
60 | }
61 |
62 | var leftNode = null
63 | var currentNodeIndex = index
64 | _.each(oldChildren, function (child, i) {
65 | var newChild = newChildren[i]
66 | currentNodeIndex = (leftNode && leftNode.count)
67 | ? currentNodeIndex + leftNode.count + 1
68 | : currentNodeIndex + 1
69 | dfsWalk(child, newChild, currentNodeIndex, patches)
70 | leftNode = child
71 | })
72 | }
73 |
74 | function diffProps (oldNode, newNode) {
75 | var count = 0
76 | var oldProps = oldNode.props
77 | var newProps = newNode.props
78 |
79 | var key, value
80 | var propsPatches = {}
81 |
82 | // Find out different properties
83 | for (key in oldProps) {
84 | value = oldProps[key]
85 | if (newProps[key] !== value) {
86 | count++
87 | propsPatches[key] = newProps[key]
88 | }
89 | }
90 |
91 | // Find out new property
92 | for (key in newProps) {
93 | value = newProps[key]
94 | if (!oldProps.hasOwnProperty(key)) {
95 | count++
96 | propsPatches[key] = newProps[key]
97 | }
98 | }
99 |
100 | // If properties all are identical
101 | if (count === 0) {
102 | return null
103 | }
104 |
105 | return propsPatches
106 | }
107 |
108 | function isIgnoreChildren (node) {
109 | return (node.props && node.props.hasOwnProperty('ignore'))
110 | }
111 |
112 | module.exports = diff
--------------------------------------------------------------------------------
/lib/preReact/test-diff/element.js:
--------------------------------------------------------------------------------
1 | var _ = require('./util')
2 |
3 | /**
4 | * Virtual-dom Element.
5 | * @param {String} tagName
6 | * @param {Object} props - Element's properties,
7 | * - using object to store key-value pair
8 | * @param {Array} - This element's children elements.
9 | * - Can be Element instance or just a piece plain text.
10 | */
11 | function Element (tagName, props, children) {
12 | if (!(this instanceof Element)) {
13 | if (!_.isArray(children) && children != null) {
14 | children = _.slice(arguments, 2).filter(_.truthy)
15 | }
16 | return new Element(tagName, props, children)
17 | }
18 |
19 | if (_.isArray(props)) {
20 | children = props
21 | props = {}
22 | }
23 |
24 | this.tagName = tagName
25 | this.props = props || {}
26 | this.children = children || []
27 | this.key = props
28 | ? props.key
29 | : void 666
30 |
31 | var count = 0
32 |
33 | _.each(this.children, function (child, i) {
34 | if (child instanceof Element) {
35 | count += child.count
36 | } else {
37 | children[i] = '' + child
38 | }
39 | count++
40 | })
41 |
42 | this.count = count
43 | }
44 |
45 | /**
46 | * Render the hold element tree.
47 | */
48 | Element.prototype.render = function () {
49 | var el = document.createElement(this.tagName)
50 | var props = this.props
51 |
52 | for (var propName in props) {
53 | var propValue = props[propName]
54 | _.setAttr(el, propName, propValue)
55 | }
56 |
57 | _.each(this.children, function (child) {
58 | var childEl = (child instanceof Element)
59 | ? child.render()
60 | : document.createTextNode(child)
61 | el.appendChild(childEl)
62 | })
63 |
64 | return el
65 | }
66 |
67 | module.exports = Element
--------------------------------------------------------------------------------
/lib/preReact/test-diff/list-diff.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Diff two list in O(N).
3 | * @param {Array} oldList - Original List
4 | * @param {Array} newList - List After certain insertions, removes, or moves
5 | * @return {Object} - {moves: }
6 | * - moves is a list of actions that telling how to remove and insert
7 | */
8 | function diff (oldList, newList, key) {
9 | var oldMap = makeKeyIndexAndFree(oldList, key)
10 | var newMap = makeKeyIndexAndFree(newList, key)
11 |
12 | var newFree = newMap.free
13 |
14 | var oldKeyIndex = oldMap.keyIndex
15 | var newKeyIndex = newMap.keyIndex
16 |
17 | var moves = []
18 |
19 | // a simulate list to manipulate
20 | var children = []
21 | var i = 0
22 | var item
23 | var itemKey
24 | var freeIndex = 0
25 |
26 | // first pass to check item in old list: if it's removed or not
27 | while (i < oldList.length) {
28 | item = oldList[i]
29 | itemKey = getItemKey(item, key)
30 | if (itemKey) {
31 | if (!newKeyIndex.hasOwnProperty(itemKey)) {
32 | children.push(null)
33 | } else {
34 | var newItemIndex = newKeyIndex[itemKey]
35 | children.push(newList[newItemIndex])
36 | }
37 | } else {
38 | var freeItem = newFree[freeIndex++]
39 | children.push(freeItem || null)
40 | }
41 | i++
42 | }
43 |
44 | var simulateList = children.slice(0)
45 |
46 | // remove items no longer exist
47 | i = 0
48 | while (i < simulateList.length) {
49 | if (simulateList[i] === null) {
50 | remove(i)
51 | removeSimulate(i)
52 | } else {
53 | i++
54 | }
55 | }
56 |
57 | // i is cursor pointing to a item in new list
58 | // j is cursor pointing to a item in simulateList
59 | var j = i = 0
60 | while (i < newList.length) {
61 | item = newList[i]
62 | itemKey = getItemKey(item, key)
63 |
64 | var simulateItem = simulateList[j]
65 | var simulateItemKey = getItemKey(simulateItem, key)
66 |
67 | if (simulateItem) {
68 | if (itemKey === simulateItemKey) {
69 | j++
70 | } else {
71 | // new item, just inesrt it
72 | if (!oldKeyIndex.hasOwnProperty(itemKey)) {
73 | insert(i, item)
74 | } else {
75 | // if remove current simulateItem make item in right place
76 | // then just remove it
77 | var nextItemKey = getItemKey(simulateList[j + 1], key)
78 | if (nextItemKey === itemKey) {
79 | remove(i)
80 | removeSimulate(j)
81 | j++ // after removing, current j is right, just jump to next one
82 | } else {
83 | // else insert item
84 | insert(i, item)
85 | }
86 | }
87 | }
88 | } else {
89 | insert(i, item)
90 | }
91 |
92 | i++
93 | }
94 |
95 | //if j is not remove to the end, remove all the rest item
96 | var k = simulateList.length - j
97 | while (j++ < simulateList.length) {
98 | k--
99 | remove(k + i)
100 | }
101 |
102 |
103 | function remove (index) {
104 | var move = {index: index, type: 0}
105 | moves.push(move)
106 | }
107 |
108 | function insert (index, item) {
109 | var move = {index: index, item: item, type: 1}
110 | moves.push(move)
111 | }
112 |
113 | function removeSimulate (index) {
114 | simulateList.splice(index, 1)
115 | }
116 |
117 | return {
118 | moves: moves,
119 | children: children
120 | }
121 | }
122 |
123 | /**
124 | * Convert list to key-item keyIndex object.
125 | * @param {Array} list
126 | * @param {String|Function} key
127 | */
128 | function makeKeyIndexAndFree (list, key) {
129 | var keyIndex = {}
130 | var free = []
131 | for (var i = 0, len = list.length; i < len; i++) {
132 | var item = list[i]
133 | var itemKey = getItemKey(item, key)
134 | if (itemKey) {
135 | keyIndex[itemKey] = i
136 | } else {
137 | free.push(item)
138 | }
139 | }
140 | return {
141 | keyIndex: keyIndex,
142 | free: free
143 | }
144 | }
145 |
146 | function getItemKey (item, key) {
147 | if (!item || !key) return void 666
148 | return typeof key === 'string'
149 | ? item[key]
150 | : key(item)
151 | }
152 |
153 | exports.makeKeyIndexAndFree = makeKeyIndexAndFree // exports for test
154 | exports.listDiff = diff
--------------------------------------------------------------------------------
/lib/preReact/test-diff/patch.js:
--------------------------------------------------------------------------------
1 | var _ = require('./util')
2 |
3 | var REPLACE = 0
4 | var REORDER = 1
5 | var PROPS = 2
6 | var TEXT = 3
7 |
8 | function patch (node, patches) {
9 | var walker = {index: 0}
10 | dfsWalk(node, walker, patches)
11 | }
12 |
13 | function dfsWalk (node, walker, patches) {
14 | var currentPatches = patches[walker.index]
15 |
16 | var len = node.childNodes
17 | ? node.childNodes.length
18 | : 0
19 | for (var i = 0; i < len; i++) {
20 | var child = node.childNodes[i]
21 | walker.index++
22 | dfsWalk(child, walker, patches)
23 | }
24 |
25 | if (currentPatches) {
26 | applyPatches(node, currentPatches)
27 | }
28 | }
29 |
30 | function applyPatches (node, currentPatches) {
31 | _.each(currentPatches, function (currentPatch) {
32 | switch (currentPatch.type) {
33 | case REPLACE:
34 | var newNode = (typeof currentPatch.node === 'string')
35 | ? document.createTextNode(currentPatch.node)
36 | : currentPatch.node.render()
37 | node.parentNode.replaceChild(newNode, node)
38 | break
39 | case REORDER:
40 | reorderChildren(node, currentPatch.moves)
41 | break
42 | case PROPS:
43 | setProps(node, currentPatch.props)
44 | break
45 | case TEXT:
46 | if (node.textContent) {
47 | node.textContent = currentPatch.content
48 | } else {
49 | // fuck ie
50 | node.nodeValue = currentPatch.content
51 | }
52 | break
53 | default:
54 | throw new Error('Unknown patch type ' + currentPatch.type)
55 | }
56 | })
57 | }
58 |
59 | function setProps (node, props) {
60 | for (var key in props) {
61 | if (props[key] === void 666) {
62 | node.removeAttribute(key)
63 | } else {
64 | var value = props[key]
65 | _.setAttr(node, key, value)
66 | }
67 | }
68 | }
69 |
70 | function reorderChildren (node, moves) {
71 | var staticNodeList = _.toArray(node.childNodes)
72 | var maps = {}
73 |
74 | _.each(staticNodeList, function (node) {
75 | if (node.nodeType === 1) {
76 | var key = node.getAttribute('key')
77 | if (key) {
78 | maps[key] = node
79 | }
80 | }
81 | })
82 |
83 | _.each(moves, function (move) {
84 | var index = move.index
85 | if (move.type === 0) { // remove item
86 | if (staticNodeList[index] === node.childNodes[index]) { // maybe have been removed for inserting
87 | node.removeChild(node.childNodes[index])
88 | }
89 | staticNodeList.splice(index, 1)
90 | } else if (move.type === 1) { // insert item
91 | var insertNode = maps[move.item.key]
92 | ? maps[move.item.key].cloneNode(true) // reuse old item
93 | : (typeof move.item === 'object')
94 | ? move.item.render()
95 | : document.createTextNode(move.item)
96 | staticNodeList.splice(index, 0, insertNode)
97 | node.insertBefore(insertNode, node.childNodes[index] || null)
98 | }
99 | })
100 | }
101 |
102 | patch.REPLACE = REPLACE
103 | patch.REORDER = REORDER
104 | patch.PROPS = PROPS
105 | patch.TEXT = TEXT
106 |
107 | module.exports = patch
--------------------------------------------------------------------------------
/lib/preReact/test-diff/util.js:
--------------------------------------------------------------------------------
1 | var _ = exports
2 |
3 | _.type = function (obj) {
4 | return Object.prototype.toString.call(obj).replace(/\[object\s|\]/g, '')
5 | }
6 |
7 | _.isArray = function isArray (list) {
8 | return _.type(list) === 'Array'
9 | }
10 |
11 | _.slice = function slice (arrayLike, index) {
12 | return Array.prototype.slice.call(arrayLike, index)
13 | }
14 |
15 | _.truthy = function truthy (value) {
16 | return !!value
17 | }
18 |
19 | _.isString = function isString (list) {
20 | return _.type(list) === 'String'
21 | }
22 |
23 | _.each = function each (array, fn) {
24 | for (var i = 0, len = array.length; i < len; i++) {
25 | fn(array[i], i)
26 | }
27 | }
28 |
29 | _.toArray = function toArray (listLike) {
30 | if (!listLike) {
31 | return []
32 | }
33 |
34 | var list = []
35 |
36 | for (var i = 0, len = listLike.length; i < len; i++) {
37 | list.push(listLike[i])
38 | }
39 |
40 | return list
41 | }
42 |
43 | _.setAttr = function setAttr (node, key, value) {
44 | switch (key) {
45 | case 'style':
46 | node.style.cssText = value
47 | break
48 | case 'value':
49 | var tagName = node.tagName || ''
50 | tagName = tagName.toLowerCase()
51 | if (
52 | tagName === 'input' || tagName === 'textarea'
53 | ) {
54 | node.value = value
55 | } else {
56 | // if it is not a input or textarea, use `setAttribute` to set
57 | node.setAttribute(key, value)
58 | }
59 | break
60 | default:
61 | node.setAttribute(key, value)
62 | break
63 | }
64 | }
--------------------------------------------------------------------------------
/lib/preReact/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 |
4 | const config = {
5 | entry: {
6 | playground: './src/playground.js',
7 | // playground: './src/testDiff.js',
8 | },
9 | output: {
10 | filename: '[name].js',
11 | publicPath: '/',
12 | },
13 | module: {
14 | rules: [
15 | { test: /\.js?$/, exclude: /node_modules/, loader: 'babel-loader' },
16 | ],
17 | },
18 | plugins: [
19 | new webpack.HotModuleReplacementPlugin(),
20 | ],
21 | mode: "development"
22 | }
23 |
24 | module.exports = config
25 |
--------------------------------------------------------------------------------
/lib/testCli/.gitignore:
--------------------------------------------------------------------------------
1 | ./node_moduels/
--------------------------------------------------------------------------------
/lib/testCli/bin/tcli:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | // console.log('Hello Cli');
4 |
5 | const program = require('commander');
6 |
7 | program
8 | .version(require('../package').version)
9 | .usage(' [options]')
10 | .command('add' ,'add a new template')
11 | .command('delete' ,'delete a template')
12 | .command('list' ,'list all the templates')
13 | .command('init' ,'generate a new project form a template')
14 |
15 | // 解析命令行参数
16 | program.parse(process.argv);
--------------------------------------------------------------------------------
/lib/testCli/bin/tcli-add:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const inquirer = require("inquirer");
4 |
5 | const chalk = require("chalk");
6 |
7 | const fs = require("fs");
8 |
9 | const tplObj = require(`${__dirname}/../template`);
10 |
11 | // 自定义交互式命令行问题以及简单校验
12 |
13 | let question = [
14 | {
15 | name: "name",
16 | type: "input",
17 | message: "请输入模板名称",
18 | validate(val) {
19 | if (val === "") {
20 | return "Name is required";
21 | } else if (tplObj[val]) {
22 | return "Template has already existed!";
23 | } else {
24 | return true;
25 | }
26 | }
27 | },
28 | {
29 | name: "url",
30 | type: "input",
31 | message: "请输入模板地址",
32 | validate(val) {
33 | if (val === "") {
34 | return "The url is required!";
35 | } else {
36 | return true;
37 | }
38 | }
39 | }
40 | ];
41 |
42 |
43 | inquirer
44 | .prompt(question).then(answers =>{
45 | let {url,name} = answers;
46 | // 过滤 Unicode 字符
47 | tplObj[name] = url.replace(/[\u0000-\u0019]/g,'');
48 |
49 | fs.writeFile(`${__dirname}/../template.json`,JSON.stringify(tplObj),'utf-8',err=>{
50 | if(err) console.log(err)
51 | console.log('\n')
52 | console.log(chalk.green('Added successfully!\n'))
53 | console.log(chalk.grey('The latest template list is: \n'))
54 | console.log(tplObj)
55 | console.log('\n')
56 | })
57 | })
--------------------------------------------------------------------------------
/lib/testCli/bin/tcli-delete:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const inquirer = require('inquirer')
4 | const chalk = require('chalk')
5 | const fs = require('fs')
6 | const tplObj = require(`${__dirname}/../template`)
7 |
8 | let question = [
9 | {
10 | name: "name",
11 | message: "请输入要删除的模板名称",
12 | validate (val) {
13 | if (val === '') {
14 | return 'Name is required!'
15 | } else if (!tplObj[val]) {
16 | return 'Template does not exist!'
17 | } else {
18 | return true
19 | }
20 | }
21 | }
22 | ]
23 |
24 | inquirer
25 | .prompt(question).then(answers => {
26 | let { name } = answers;
27 | delete tplObj[name]
28 | // 更新 template.json 文件
29 | fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
30 | if (err) console.log(err)
31 | console.log('\n')
32 | console.log(chalk.green('Deleted successfully!\n'))
33 | console.log(chalk.grey('The latest template list is: \n'))
34 | console.log(tplObj)
35 | console.log('\n')
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/lib/testCli/bin/tcli-init:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const program = require("commander");
4 | const chalk = require("chalk");
5 | const ora = require("ora");
6 | const download = require("download-git-repo");
7 | const tplObj = require(`${__dirname}/../template`);
8 |
9 | program.usage(" [project-name]");
10 | program.parse(process.argv);
11 |
12 | // 当没有输入参数的时候给个提示
13 | if (program.args.length < 1) return program.help();
14 |
15 | let templateName = program.args[0];
16 | let projectName = program.args[1];
17 |
18 | console.log(tplObj,typeof tplObj)
19 | if (!tplObj[templateName]) {
20 | console.log(chalk.redBright("\n Template does not exit! \n"));
21 | process.exit(0);
22 | }
23 |
24 | if (!projectName) {
25 | console.log(chalk.red("\n Project should not be empty! \n "));
26 | return;
27 | }
28 |
29 | url = tplObj[templateName];
30 |
31 | console.log(chalk.white("\n Start generating... \n"));
32 |
33 | // 出现加载图标
34 | const spinner = ora("Downloading...");
35 | spinner.start();
36 |
37 | download(url, projectName, err => {
38 | if (err) {
39 | spinner.fail();
40 | console.log(chalk.red(`Generation failed. ${err}`));
41 | return;
42 | }
43 | // 结束加载图标
44 | spinner.succeed();
45 | console.log(chalk.green("\n Generation completed!"));
46 | console.log("\n To get started");
47 | console.log(`\n cd ${projectName} \n`);
48 | });
49 |
--------------------------------------------------------------------------------
/lib/testCli/bin/tcli-list:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const tplObj = require(`${__dirname}/../template`)
4 | console.log(tplObj)
5 |
--------------------------------------------------------------------------------
/lib/testCli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "testcli",
3 | "version": "1.0.0",
4 | "description": "test cli",
5 | "main": "index.js",
6 | "bin": {
7 | "tcli": "./bin/tcli",
8 | "tcli-add": "./bin/tcli-add",
9 | "tcli-list": "./bin/tcli-list",
10 | "tcli-delete": "./bin/tcli-delete",
11 | "tcli-init": "./bin/tcli-init"
12 | },
13 | "scripts": {
14 | "test": "echo \"Error: no test specified\" && exit 1"
15 | },
16 | "keywords": [
17 | "Nealyang",
18 | "testCli"
19 | ],
20 | "author": "Nealyang",
21 | "license": "ISC",
22 | "dependencies": {
23 | "chalk": "^2.4.2",
24 | "commander": "^2.20.0",
25 | "download-git-repo": "^2.0.0",
26 | "inquirer": "^6.3.1",
27 | "ora": "^3.4.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/testCli/template.json:
--------------------------------------------------------------------------------
1 | {"simple":"Nealyang/React-Fullstack-Dianping-Demo"}
--------------------------------------------------------------------------------
/lib/underscore/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/underscore/test.js:
--------------------------------------------------------------------------------
1 | var html = '吴小蛆的博客';
2 | var escaped = _.escape(html);
3 | console.log(escaped);
4 |
--------------------------------------------------------------------------------
/test.html:
--------------------------------------------------------------------------------
1 | static displayName = 'TitleZone';
2 |
3 | static defaultProps = {
4 | lazyload: true,
5 | resizeMode: 'cover',
6 | titleTag: []
7 | }
8 |
9 | render() {
10 | const {
11 | title,
12 | titleTag,
13 | lazyload,
14 | resizeMode,
15 | placeholder,
16 | styles
17 | } = this.props;
18 |
19 | return (
20 |
21 | {
22 | titleTag && titleTag.map(function (titleTagUri, i) {
23 | return (
24 |