【 js 基础 】【 源码学习 】源码设计 (持续更新)

学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构。(推荐对源码有一定熟悉了之后,再看这篇文章)

目录结构:
第一部分:zepto 设计分析
第二部分:underscore 设计分析

第一部分: 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 实例 实际上一个 对象数组,即可以模拟数组操作的对象。

咱们再用个图来梳理一下思路:

学习并感谢:

https://www.kancloud.cn/wangfupeng/zepto-design-srouce/173681

https://segmentfault.com/a/1190000007515865#articleHeader4



第二部分 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 ,一个以类 jq 和 underscore 为基础的 mvc 框架。欢迎大家  star 我的 github 阅读源码项目(https://github.com/JiayiLi/source-code-study),进行关注。

时间: 2024-08-11 09:53:25

【 js 基础 】【 源码学习 】源码设计 (持续更新)的相关文章

MINA2 源码学习--源码结构梳理

一.mina的整体框架结构及案例: 1.整体结构图: 简述:以上是一张来自网上比较经典的图,整体上揭示了mina的结构,其中IoService包含客户端IoConnector和服务端IoAcceptor两部分.即无论是客户端还是服务端都是这个结构.IoService封装了网络传输层(TCP和UDP),而IoFilterChain中mina自带的filter做了一些基本的操作之外,支持扩展.经过FilterChain之后最终调用IoHandler,IoHandler是具体实现业务逻辑的处理接口,具

cocos2d-x 学习资源整理(持续更新...)

生活像一把无情刻刀,改变了我们模样,曾经我以为会亘古不变的东西,突然在一瞬间失去了信念... 如果你改变不了生活中患得患失的心情,那就试着让自己变得强大一点,因为能做到不以物喜不以己悲都是建立在强大的基础上面的. so,加油吧,少年. ================================================================ 一不小心又废话了. 学习cocos2d-x也有一段时间了,直到现在仍然认为自己是个新手(呵呵,说的好像我不是新手一样.) 现在把自己这

[转帖]linux常用命令大全(linux基础命令入门到精通+实例讲解+持续更新+命令备忘录+面试复习)

https://www.cnblogs.com/caozy/p/9261224.html 总结的挺好的 我之前想总结 一直懒 这次国庆加班 也本来计划来着 感冒了 作罢 .. 作者:蓝藻(罗蓝国度) 创建时间:2018.7.3编辑时间:2019.7.16 前言 本文特点 授之以渔:了解命令学习方法.用途:不再死记硬背,拒绝漫无目的: 准确无误:所有命令执行通过(环境为centos7),拒绝复制粘贴: 实用性高:命令多为实际工作中用到的,实例讲解,拒绝纯理论: 条理清晰:分类归纳,快速找到想要的命

通过Tomcat的Http11NioProtocol源码学习Java NIO设计

Tomcat的Http11NioProtocol协议使用Java NIO技术实现高性能Web服务器.本文通过分析Http11NioProtocol源码来学习Java NIO的使用.从中可以了解到阻塞IO和非阻塞IO的配合,NIO的读写操作以及Selector.wakeup的使用. 1. 初始化阶段 Java NIO服务器端实现的第一步是开启一个新的ServerSocketChannel对象.Http11NioProtocol的实现也不例外, 在NioEndPoint类的init方法可以看到这段代

javascript(js)基础之dom学习

dom学习 <img id='xx'.. onclick='aa()'> functon aa(){ xx1=document.getElementById("xx") //下面对xx1进行操作 } bom介绍:浏览器对象模型 因为浏览器企业太多,w3c定义了一个做浏览器的规范 规定 ----------- dom介绍/学习:文档对象模型 dom树 例子 <script language="JavaScript"> function text

网上公开源码的一些链接(持续更新)

灌水动画,网易新闻等 360度全景图 一些库的封装,还不错

深度学习算法索引(持续更新)

https://zhuanlan.zhihu.com/p/26004118 机器学习最近几年发展如同野兽出笼,网上的资料铺天盖地.学习之余建立一个索引,把最有潜力(不求最全)的机器学习算法.最好的教程.贴近工业界最前沿的开源代码收录其中.个人能力有限,希望知友指正和补充. Model篇 1. Reinforcement Learning 领军人物:david silver 教程 2015年david silver的UCL Course on RL:Teaching david silver的Tu

cocos2d-x学习资源汇总(持续更新。。。)

引用地址:http://www.cnblogs.com/zilongshanren/archive/2012/02/17/2356516.html 我之前一直学习c++的,第一次接触cocos2d是oc版的,虽然很好用,但是,我还是对c++情有独钟,再加之cocos2d-x由自己人主导,更应该拥抱她,热爱她,把她发扬光大.之后,我将会全面投入cocos2d-x的怀抱,欢迎大家和我交流,一起学习. 补充一点:我选择cocos2d-x绝不是简单地支持国产这么简单,cocos2d-x真的很强大.关于游

学习golang的历程——持续更新

学习一门语言首先hello world 1,首先建立一个目录,新建一个hello.go的文件 package main import ( "fmt" ) func main() { fmt.Println("hello world") } 2,go run hello.go [email protected] study % go run hello.go hello world 上面我们就完成了我们的基础使命.下面我们正式开始入门 在正式进入写代码之前,推荐大家使

iOS 开发学习资料整理(持续更新)

"如果说我看得比别人远些,那是因为我站在巨人们的肩膀上." ---牛顿 iOS及Mac开源项目和学习资料[超级全面] http://www.kancloud.cn/digest/ios-mac-study/84557 iOS 学习资料整理 https://segmentfault.com/a/1190000002473595#articleHeader16 iOS.mac开源项目及库 https://github.com/Tim9Liu9/TimLiu-iOS Swift语言 http