js 基础 源码学习 源码设计 (持续更新)
Posted TristaLee
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了 js 基础 源码学习 源码设计 (持续更新)相关的知识,希望对你有一定的参考价值。
学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构。(推荐对源码有一定熟悉了之后,再看这篇文章)
目录结构:
第一部分:zepto 设计分析
第二部分:underscore 设计分析
第三部分:backbone 设计分析
第一部分: zepto 设计分析
zepto 是一个轻量级的 javascript 库。相对于 jquery 来说在 size 上更加小,主要是定位于移动设备。它是非常好的学习源码的入门级 javascript 库。这里重点说一下,这个库的设计,而对于详细的源码学习大家可以 star 我的 github 源码学习项目(https://github.com/JiayiLi/source-code-study) 进行关注。
让我们先看看把所有代码删除只剩下这几行的 zepto :
1 var Zepto = (function() { 2 return $ 3 })() 4 window.Zepto = Zepto 5 window.$ === undefined && (window.$ = Zepto)
一个匿名自执行函数返回 $ 传递给了 Zepto。然后把 Zepto 挂到 window 上,使其成为全局 window 的一个属性,同时,如果 window.$ 符号没有被占用,那么 $ 会被赋值为 Zepto,故可以全局范围内使用 $。
然后咱们再来看看赋值给 zepto 的匿名自执行函数的核心代码具体干了什么:
1 var Zepto = (function() { 2 //zepto和$是Zepto中的两个命名空间,用来挂载静态函数 3 var $,zepto = {}; 4 5 function Z(dom, selector) { 6 var i, len = dom ? dom.length: 0 7 for (i = 0; i < len; i++) this[i] = dom[i] this.length = len this.selector = selector || \'\' 8 } 9 10 zepto.Z = function(dom, selector) { 11 return new Z(dom, selector) 12 } 13 zepto.init = function(selector, context) {.... 14 return zepto.Z(dom, selector) 15 } 16 17 //$()的返回值能够调用$.fn中的方法 18 $ = function(selector, context) { 19 return zepto.init(selector, context); 20 } 21 22 $.fn = { 23 // 里面有若干个工具函数 24 }; 25 26 zepto.Z.prototype = $.fn; 27 $.zepto = zepto 28 29 return $ //返回$,赋值给Zepto 30 })() 31 32 window.Zepto = Zepto 33 //当$未被占用时就把Zepto赋值给$ 34 window.$ === undefined && (window.$ = Zepto)
首先定义了 两个变量 zepto 和 $ ,还有一个 构造函数 Z 。
对于变量 zepto ,给其定义了两个方法 Z 和 init。init 方法中调用了 zepto 的 Z 方法,而在 zepto 的 Z 方法中 则 实例化了 构造函数 Z。
对于 变量 $ ,则是个函数,内部调用了 zepto 的 init 方法 ,也就是最后返回了 构造函数 Z 的 新实例 。同时也给 $ 上定义了一个属性 fn,fn 是一个 对象,这个对象里面实现了多个工具函数,比如 concat、slice、each、filter 等。
然后将 刚才定义的 变量 zepto 中的 Z 方法的原型 即构造函数的原型 指向了 $.fn ,这样 调用 $ 函数所返回的 Z 实例 ,就继承了 $.fn 中的方法。
最后通过 $.zepto = zepto 将内部 API 导出。如果有需要可以调用 zepto 上的内部方法。
Z 实例 实际上一个 对象数组,即可以模拟数组操作的对象。
咱们再用个图来梳理一下思路:
第二部分 underscore 设计分析
underscore 是一个Javascript 实用库。是函数式编程的典型代表。它是非常好的学习源码的入门级 javascript 库,尤其是学习函数式编程的好材料。这里重点说一下,这个库的设计,而对于详细的源码学习大家可以 star 我的 github 源码学习项目(https://github.com/JiayiLi/source-code-study) 进行查看。
underscore 的所有代码都包裹在匿名自执行函数中,
1 (function() { 2 ... 3 }.call(this)) // 通过传入this(浏览器环境中其实就是window对象)来改变函数的作用域
大多数的源码设计都是使用的 匿名自执行函数,这样做的好处:
1、避免全局污染:库中所定义的变量,方法都封装到了该函数的作用域中。
2、隐私保护:使用方只能获得 库 想暴露在外面的变量方法,而不能访问 不想暴露的内部变量方法。
再说设计之前,这里还要说一个知识点:
underscore 采用的是典型的函数式编程风格,这与面向对象的编程风格并不相同。
函数式编程(fp)风格,设计的函数方法并不会属于任何一个对象,对象只是 这些函数方法的参数。
而面向对象的编程(oop)风格则是 设计的函数方法都隶属于一个对象。作为对象的一个属性。
如果你还没有明白,这里看一下调用方式的不同:
函数式编程风格:
1 var arr = [1, 2, 3]; 2 _.map(arr,function(item) { 3 return item * 2; 4 });
arr 这个对象只是 map 方法的一个参数,map 并不属于 arr。
面向对象风格:
1 var arr = [1, 2, 3]; 2 arr.map(function(item) { 3 return item * 2; 4 });
map是对象arr的一个方法。
看出区别了吗?
回到匿名自执行函数内部,核心代码如下:
1 (function() { 2 var root = this; 3 4 // 核心函数 5 // `_` 其实是一个构造函数 6 var _ = function(obj) { 7 // 以下均针对 OOP 形式的调用 8 // 如果是非 OOP 形式的调用,不会进入该函数内部 9 // 如果 obj 已经是 `_` 函数的实例,则直接返回 obj 10 if (obj instanceof _) return obj; 11 12 // 如果不是 `_` 函数的实例 13 // 则调用 new 运算符,返回实例化的对象 14 if (! (this instanceof _)) return new _(obj); 15 16 // 将 obj 赋值给 this._wrapped 属性 17 this._wrapped = obj; 18 }; 19 20 // 将上面定义的 `_` 局部变量赋值给全局对象中的 `_` 属性 21 // 即客户端中 window._ = _ 22 // 服务端(node)中 exports._ = _ 23 // 同时在服务端向后兼容老的 require() API 24 // 这样暴露给全局后便可以在全局环境中使用 `_` 变量(方法) 25 if (typeof exports !== \'undefined\') { 26 if (typeof module !== \'undefined\' && module.exports) { 27 exports = module.exports = _; 28 } 29 exports._ = _; 30 } else { 31 root._ = _; 32 } 33 34 // ..... 定义工具函数 如 _.each, _.map 等 35 36 37 _.mixin = function(obj) { 38 // 遍历 obj 的 key,将方法挂载到 Underscore 上 39 // 其实是将方法浅拷贝到 _.prototype 上 40 _.each(_.functions(obj), 41 function(name) { 42 // 直接把方法挂载到 _[name] 上 43 // 调用类似 _.myFunc([1, 2, 3], ..) 44 var func = _[name] = obj[name]; 45 46 // 浅拷贝 47 // 将 name 方法挂载到 _ 对象的原型链上,使之能 OOP 调用 48 _.prototype[name] = function() { 49 // 第一个参数 50 var args = [this._wrapped]; 51 52 // arguments 为 name 方法需要的其他参数 53 push.apply(args, arguments); 54 // 执行 func 方法 55 // 支持链式操作 56 return result(this, func.apply(_, args)); 57 }; 58 }); 59 }; 60 61 // Add all of the Underscore functions to the wrapper object. 62 // 将前面定义的 underscore 方法添加给包装过的对象 63 // 即添加到 _.prototype 中 64 // 使 underscore 支持面向对象形式的调用 65 _.mixin(_); 66 67 // Add all mutator Array functions to the wrapper. 68 // 将 Array 原型链上有的方法都添加到 underscore 中 69 _.each([\'pop\', \'push\', \'reverse\', \'shift\', \'sort\', \'splice\', \'unshift\'], 70 function(name) { 71 var method = ArrayProto[name]; 72 _.prototype[name] = function() { 73 var obj = this._wrapped; 74 method.apply(obj, arguments); 75 76 if ((name === \'shift\' || name === \'splice\') && obj.length === 0) delete obj[0]; 77 78 // 支持链式操作 79 return result(this, obj); 80 }; 81 }); 82 83 // Add all accessor Array functions to the wrapper. 84 // 添加 concat、join、slice 等数组原生方法给 Underscore 85 _.each([\'concat\', \'join\', \'slice\'], 86 function(name) { 87 var method = ArrayProto[name]; 88 _.prototype[name] = function() { 89 return result(this, method.apply(this._wrapped, arguments)); 90 }; 91 }); 92 // 一个包装过(OOP)并且链式调用的对象 93 // 用 value 方法获取结果 94 _.prototype.value = function() { 95 return this._wrapped; 96 }; 97 98 _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; 99 100 _.prototype.toString = function() { 101 return \'\' + this._wrapped; 102 }; 103 104 }.call(this))
将this(浏览器环境中其实就是window对象)传入 匿名自执行 函数,并赋值给 root。
创建 _ 函数,将其挂到 root 即全局作用域下,故可以全局范围内使用 _ 。
然后在 _ 上定义了工具函数(函数即对象,可以在对象上添加方法),像 _.each, _.map 等。
这样就可以全局使用函数式编程风格的方式 调用 _ 上的方法了, _.each, _.map等。
但同时 underscore 也做了针对面向对象风格的调用方式的兼容。需要通过_()来包裹一下对象,调用例子:
1 var arr = [1, 2, 3]; 2 _(arr).map(function(item) { 3 return item * 2; 4 });
var _ = function(obj) { // 以下均针对 OOP 形式的调用 // 如果是非 OOP 形式的调用,不会进入该函数内部 // 如果 obj 已经是 `_` 函数的实例,则直接返回 obj if (obj instanceof _) return obj; // 如果不是 `_` 函数的实例 // 则调用 new 运算符,返回实例化的对象 if (! (this instanceof _)) return new _(obj); // 将 obj 赋值给 this._wrapped 属性 this._wrapped = obj; };
如果你采用的是函数式编程风格 调用的话, 不传 obj ,所以不会进入两个 if 判断而直接执行最后一句 this._wrapped = obj;。
如果你采用的是面向对象的编程风格 调用的话,如果 obj 已经是 _ 函数的实例,则直接返回 obj,如果不是 _ 函数的实例, 则调用 new 运算符,返回实例化的对象。
那么 面向对象风格的调用方式,是如何拥有所有定义的方法的呢?
从 代码中的 _.mixin 函数开始,就是为了兼容 这一种调用。
定义了一个 _.mixin 方法 ,并在之后立即执行了,传入了 _ 作为参数。
_.mixin = function(obj) { // 遍历 obj 的 key,将方法挂载到 Underscore 上 // 其实是将方法浅拷贝到 _.prototype 上 _.each(_.functions(obj), function(name) { // 直接把方法挂载到 _[name] 上 // 调用类似 _.myFunc([1, 2, 3], ..) var func = _[name] = obj[name]; // 浅拷贝 // 将 name 方法挂载到 _ 对象的原型链上,使之能 OOP 调用 _.prototype[name] = function() { // 第一个参数 var args = [this._wrapped]; // arguments 为 name 方法需要的其他参数 push.apply(args, arguments); // 执行 func 方法 // 支持链式操作 return result(this, func.apply(_, args)); }; }); }; // Add all of the Underscore functions to the wrapper object. // 将前面定义的 underscore 方法添加给包装过的对象 // 即添加到 _.prototype 中 // 使 underscore 支持面向对象形式的调用 _.mixin(_); // Add all mutator Array functions to the wrapper. // 将 Array 原型链上有的方法都添加到 underscore 中 _.each([\'pop\', \'push\', \'reverse\', \'shift\', \'sort\', \'splice\', \'unshift\'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name === \'shift\' || name === \'splice\') && obj.length === 0) delete obj[0]; // 支持链式操作 return result(this, obj); }; }); // Add all accessor Array functions to the wrapper. // 添加 concat、join、slice 等数组原生方法给 Underscore _.each([\'concat\', \'join\', \'slice\'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; }); // 一个包装过(OOP)并且链式调用的对象 // 用 value 方法获取结果 _.prototype.value = function() { return this._wrapped; }; _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; _.prototype.toString = function() { return \'\' + this._wrapped; };
_.mixin 函数中将遍历_的属性,如果某个属性的类型是function,就把该函数挂载到 _ 原型链上,这样对于 _ 函数的实例自然就可以调用 _ 原型链上的方法。
对于函数式编程,其实是另一种编程思想,它相较于大家所熟知的面向对象的编程风格来说 ,应该是各有好处。推荐大家看 underscore源码 和 书 《Javascript函数式编程》 深入了解。
学习并感谢:
http://www.jianshu.com/p/e602ce36b6f7
https://yoyoyohamapi.gitbooks.io/undersercore-analysis/content/base/%E7%BB%93%E6%9E%84.html
第三部分 backbone 设计分析
backbone 是一个以类 jq 和 underscore 为基础的 mvc 框架。它是非常好的学习 mvc 框架的入门级 javascript 库。这里主要说一下这个框架的设计,而对于细节上的知识点可以 star 我的 github 源码学习项目(https://github.com/JiayiLi/source-code-study) 进行查看。
这里是删减了许多代码的 backbone,只剩下一个外壳:
1 (function(factory) { 2 // 定义全局对象 root 变量,在浏览器环境下为 window,在 服务器 环境下为 global, self 指向window 3 var root = (typeof self == \'object\' && self.self === self && self) || (typeof global == \'object\' && global.global === global && global); 4 5 // 支持AMD规范 6 // 使用define函数定义Backbone模块, 依赖`underscore`, `jquery`, `exports`三个模块. 7 if (typeof define === \'function\' && define.amd) { 8 define([\'underscore\', \'jquery\', \'exports\'], 9 function(_, $, exports) { 10 // Export global even in AMD case in case this script is loaded with 11 // others that may still expect a global Backbone. 12 root.Backbone = factory(root, exports, _, $); 13 }); 14 15 // 支持 commonJs 规范(NodeJS使用的规范, 主要用于服务器端, 所以jQuery非必须). 16 // CommonJS规范中, exports是用于导出模块的对象. 17 } else if (typeof exports !== \'undefined\') { 18 var _ = require(\'underscore\'), 19 $; 20 try { 21 $ = require(\'jquery\'); 22 } catch(e) {} 23 factory(root, exports, _, $); 24 25 // 以上两种情况都没有,则以最简单的执行函数方式,将函数的返回值作为全局对象Backbone 26 } else { 27 root.Backbone = factory(root, {}, 28 root._, (root.jQuery || root.Zepto || root.ender || root.$)); 29 } 30 31 })(function(root, Backbone, _, $) { 32 33 // …………… 34 35 return Backbone; 36 });
1 (function(factory) { 2 3 })(function(root, Backbone, _, $) { 4 // Backbone.Events 5 // --------------- 6 // Backbone 事件部分 7 // Backbone的 Events 实际上就是一个观察者模式(发布订阅模式)的实现,并且巧妙的是,还可以作为mixin混入到自己写的object中, 8 9 // 初始化Events为一个空对象,js中的对象是按引用传递的。 10 var Events = Backbone.Events = {}; 11 12 // Bind an event to a `callback` function. Passing `"all"` will bind 13 // the callback to all events fired. 14 // 绑定事件。将一个事件绑定到 `callback` 函数上。事件触发时执行回调函数`callback`。 15 Events.on = function(name, callback, context) { 16 17 }; 18 19 // “on”的控制反转版本。 20 // 让 object 监听 另一个(other)对象上的一个特定事件。跟踪它正在监听的内容,以便以后解除绑定。 21 Events.listenTo = function(obj, name, callback) { 22 23 }; 24 25 // 此函数作用于删除一个或多个回调。 26 Events.off = function(name, callback, context) { 27 28 }; 29 30 // 解除 当前 object 监听的 其他对象上制定事件,或者说是所有当前监听的事件 31 // 如果 传入被监听对象 obj 就是解除特定对象上的事件,没有就是解除所有事件 32 Events.stopListening = function(obj, name, callback) { 33 34 }; 35 36 // 绑定事件只能触发一次。在第一次调用回调之后,它的监听器将被删除。如果使用空格分隔的语法传递多个事件,则处理程序将针对每个事件触发一次,而不是一次所有事件的组合。 37 Events.once = function(name, callback, context) { 38 39 }; 40 41 // once的反转控制版本 42 Events.listenToOnce = function(obj, name, callback) { 43 44 }; 45 46 // 触发一个或者多个事件,并触发所有的回调函数 47 Events.trigger = function(name) { 48 49 }; 50 51 // 将Events的特性全部extend到Backbone, 即Backbone也可以做Backbone.on/Backbone.trigger这样的操作. 52 // underscore:_.extend(destination, *sources) 复制source对象中的所有属性覆盖到destination对象上,并且返回 destination 对象. 复制是按顺序的, 所以后面的对象属性会把前面的对象属性覆盖掉(如果有重复). 53 _.extend(Backbone, Events); 54 55 // 至此,Events部分结束,接下来是Model部分 56 // Backbone.Model 模型Model绑定键值数据和自定义事件; 57 // -------------- 58 // 每当一个模型建立,一个 cid 便会被自动创建 59 // 实际上,Model 函数内的语句顺序也是很重要的,这个不能随便打乱顺序(初始化过程) 60 61 // 创建具有指定属性的新model。 客户端ID(`cid`)自动生成并分配给您。 62 var Model = Backbone.Model = function(attributes, options) { 63 64 // Model的唯一的id,这和自己传入的id并不一样,虽然我们也要保证id是唯一的 65 this.cid = _.uniqueId(this.cidPrefix); 66 67 // model 模型元数据都存储在`attributes`变量中. 68 this.attributes = {}; 69 70 // 如果指定`collection`则保存, model 在构造 url 时可能会用到此参数. 71 if (options.collection) this.collection = options.collection; 72 73 // 如果之后 new 的时候传入的是 JSON,我们必须在 options 选项中声明 parse 为 true 74 if (options.parse) attrs = this.parse(attrs, options) || {}; 75 76 // 存储历史变化记录,用于保存上一次`set`之后改变的数据字段. 77 this.changed = {}; 78 // 调用initialize初始化方法。这个initialize也是空的,给初始化之后调用 79 this.initialize.apply(this, arguments); 80 }; 81 82 // 使用extend方法为Model原型定义一系列属性和方法。 83 _.extend(Model.prototype, Events, { 84 // preinitialize默认为空函数。您可以使用函数或对象覆盖它。在模型中运行任何实例逻辑之前,preinitialize将运行。 85 preinitialize: function() {}, 86 87 // 默认情况下,Initialize是一个空的函数。用自己的初始化逻辑覆盖它。 88 initialize: function() {}, 89 90 // Get the value of an attribute. 91 // 从当前model中获取当前属性(attributes)值,比如: note.get("title") 92 get: function(attr) { 93 return this.attributes[attr]; 94 }, 95 96 // 检查模型中是否存在某个属性。属性值为非 null 或非 undefined 时返回 true。 97 // 当该属性的值被转换为Boolean类型后值为false, 则认为不存在。如果值为false, null, undefined, 0, NaN, 或空字符串时, 均会被转换为false。 98 has: function(attr) { 99 return this.get(attr) != null; 100 以上是关于 js 基础 源码学习 源码设计 (持续更新)的主要内容,如果未能解决你的问题,请参考以下文章