嗯,就是学习一下,读读源码。顺便把源码里的文档翻译了一下并添加了一些简单的注释。
本文对应的文件是src/createStore.js
import isPlainObject from 'lodash/isPlainObject' import $$observable from 'symbol-observable' /** * ActionTypes里定义的是Redux保留的私有action。 * 对于任何未知的action,你必须返回store的当前状态。 * 如果传入的当前状态是undefined,你必须返回store的初始状态。 * 不要在应用代码中直接引用这些action。 */ export var ActionTypes = { INIT: '@@redux/INIT' } /** * createStore方法用于创建一个保存程序状态的store。 * 改变store中数据的唯一方法是调用store的`dispatch()`方法。 * * 你的应用中应该只有一个store。为了将程序状态中不同部分的变更逻辑 * 组合在一起,你可以通过`combineReducers`方法将多个reducer组合成一个reducer。 * * @param {Function} reducer 一个返回应用下一状态的函数,入参是程序的当前状态以及 * 要发送的action。 * * @param {any} [preloadedState] store的初始状态。你可以选择性的为store指定一个 * 初始状态。 * 如果你使用了`combineReducers`方法来生成最终的reducer。那么这个初始状态对象的 * 结构必须与调用`combineReducers`方法时传入的参数的结构保持对应关系。 * * @param {Function} enhancer store增强器。你可以选择性的传入一个增强函数来扩展 * store的功能,例如中间件,时间旅行,持久化等。Redux自带的唯一一个增强器是 * `applyMiddleware()`方法。 * * @returns {Store} 返回一个可以读取状态,发送action以及订阅变更通知的Redux store。 */ export default function createStore(reducer, preloadedState, enhancer) { // 如果只传入reducer和enhancer,则store的初始状态为undefined if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } // enhancer必须是一个函数 if (typeof enhancer !
最近在学习ES6 Generator特性时发现了koa这个基于Generator的Web框架,它可以让开发者以一种“同步的方式”编写包含各种异步请求的Web应用。下面是关于它的一段中文介绍:
由 Express 原班人马打造的 koa,致力于成为一个更小、更健壮、更富有表现力的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。
从介绍中可以看出这又是一个小而美的框架。到GitHub项目页面上看了一下发现源码里只有4个JS文件,总代码量只有1571行(以2015年12月26日最新稳定版1.1.2为准)。于是决定把代码clone下来学习一下,本篇会先从整个框架的入口文件lib/application.js说起。
总体说明 lib/application.js文件export出的是一个构造函数,用来创建一个koa应用。一个koa应用最常用的方法有2个: - listen(port) 执行listen后会通过http.createServer启动一个服务器并监听指定端口 - use(middleware) 注册一个中间件,一个koa应用可以注册多个中间件, 处理请求时会按照中间件注册的顺序执行这些中间件。
更多详细信息可以参考GitHub文档页面。
代码注释 function Application() { if (!(this instanceof Application)) return new Application; this.env = process.env.NODE_ENV || 'development'; this.subdomainOffset = 2; this.middleware = []; this.proxy = false; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } 以上就是Application构造函数的定义,很简洁,主要做了以下几件事情:
- 通过instanceof判断来支持不带new关键字的调用。 - 设置应用运行环境,会从环境变量NODE_ENV读取,默认值为development - 声明this.
数据的双向绑定可能是Angular最为人们熟知的特性之一。举个最简单的例子
可以在result页面中看到,每当在input中输入时,$scope中对应model的值也改变了。反之,当用户点击Set按钮在$scope中更新了model的值时,input输入框中的内容也对应更新了。
所有的这些魔法只需要我们在input元素上指定一个ng-model属性。可见ng-model这个directive是双向绑定这一特性不可缺少一点。本文就继续从源代码入手看看ngModel的实现方式以及其它directive是如何与ngModel交互的。
ngModel被定义在input.js。从名字上可看出,该文件还定义input这个directive,从这点也可以看出ngModel与input之间的紧密关系。
注: 文中所有涉及到的AngularJS源码均来自angular-1.2.8版本。
var ngModelDirective = function() { return { require: ['ngModel', '^?form'], controller: NgModelController, link: function(scope, element, attr, ctrls) { // notify others, especially parent forms var modelCtrl = ctrls[0], formCtrl = ctrls[1] || nullFormCtrl; formCtrl.$addControl(modelCtrl); scope.$on('$destroy', function() { formCtrl.$removeControl(modelCtrl); }); } }; }; 可以看出ngModel依赖于ngModelController以及可选的formController。ngModel在link方法中只做了2件事:
如果声明了ngModel的元素出现在一个form中,那么就向上层form注册自身 注册了$destroy事件的监听器,该事件触发时告知上层form移除对自身的引用 link方法如此简单以至于我们还没看到关于数据绑定的任何信息。看来问题的答案都藏在ngModelController中了。接下来看看ngModelController中都发生了什么。
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', function($scope, $exceptionHandler, $attr, $element, $parse) { this.
注: 文中所有涉及到的AngularJS源码均来自angular-1.2.8版本。
注2: 本文结构为一段代码配一段说明,代码在上,说明在下。
从3月3号到现在,进入新公司也有1个月了。过去这一个月里主要负责公司内部某个DevOps工具开发过程中的前端部分。由于涉及到较多的网页交互,该项目的前端部分使用了AngularJS这个框架。本人这正好借助这个机会进一步了解了AngularJS的相关知识。
这两天项目主体完成,准备上线,算是有了一些自由时间,正好借此机会学习一下AngularJS内部的实现机制。
本篇来说说最常见的directive之一 ngRepeat
该文件的整体结构如下:
var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { var NG_REMOVED = '$$NG_REMOVED'; var ngRepeatMinErr = minErr('ngRepeat'); return { transclude: 'element', priority: 1000, terminal: true, $$tlb: true, link: function($scope, $element, $attr, ctrl, $transclude){ ..... } }; function getBlockStart(block) { return block.clone[0]; } function getBlockEnd(block) { return block.clone[block.clone.length - 1]; } }]; ngRepeatDirective是一个数组,它会被传递给directive方法用来生成ngRepeat这个directive。可以看到ngRepeat依赖于$parse和$animate两个服务。$parse用来将字符串解析成javascript函数。$animate用来给DOM改变附加上动画效果。文件最后声明了两个helper方法,会在稍后的分析中介绍到。
接下来把最大的篇幅交给这个directive的核心——link方法。
var expression = $attr.ngRepeat; var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, lhs, rhs, valueIdentifier, keyIdentifier, hashFnLocals = {$id: hashKey}; if (!