zoukankan      html  css  js  c++  java
  • 【读书笔记】-- JavaScript模块

    在JavaScript编程中我们用的很多的一个场景就是写模块。可以看成一个简单的封装或者是一个类库的开始,有哪些形式呢,先来一个简单的模块。

    简单模块

           var foo = (function() {
                    var name = "foo";
                    function hello() {
                        console.log("hello "+name);
                    }
                    function doWork() {
                        console.log("do work");
                    }
                    return {
                        hello: hello,
                        doWork: doWork
                    };
                })();
                foo.hello(); // hello foo
                foo.doWork(); // do work

    用IIFE创建一个闭包,隔离作用域,避免变量相互干扰。得到foo对象可以直接用了。这种适合小的模块,比如在ag中的写Service。

    (function () {
        angular
            .module('readApp')
            .service('authentication', authentication);
        
        authentication.$inject = ['$window','$http'];
        function authentication($window, $http) {
            var saveToken = function (token) {
                $window.localStorage['read-token'] = token;
            };
            var getToken = function () {
                return $window.localStorage['read-token'];
            };
            var register = function(user) {
                return $http.post('/api/register', user).success(function(data) {
                    saveToken(data.token);
                });
            };
            var login = function(user) {
                return $http.post('/api/login', user).success(function(data) {
                    saveToken(data.token);
                });
            };
            var logout = function() {
                $window.localStorage.removeItem('read-token');
            };
    
            var isLoggedIn = function() {
                var token = getToken();
                if (token) {
                    var payload = JSON.parse($window.atob(token.split('.')[1]));
                    return payload.exp > Date.now() / 1000;
                } else {
                    return false;
                }
            };
            var currentUser = function() {
                if (isLoggedIn()) {
                    var token = getToken();
                    var payload = JSON.parse($window.atob(token.split('.')[1]));
                    return {
                        email: payload.email,
                        name: payload.name,
                    };
                }
            };
            
    
            return {
                saveToken: saveToken,
                getToken: getToken,
                register: register,
                login: login,
                logout: logout,
                isLoggedIn: isLoggedIn,
                currentUser: currentUser,
            };
        }
    })();
    View Code

    也可以直接一个大括号的:

       var foo = {
                    other: function () {
                        console.log("do other");
                    },
                    hello:function() {
                        console.log("working");
                    },
                    doWork:function() {
                        console.log("working");
                        foo.other();
                    }
                }

    这个形式我们在jquery内部或者一些工具类js中见过。简单直接。如果有内部相互调用,建议直接用对象名。这样不必在每一个方法里面写一个 var self=this 。缺点就是太长了不好维护,多增加一个变量都要加个key:value的形式。只适合简单场景。但如果模块内部方法比较多,还是建议在内部创建对象。

    扩展模块

      var foo = (function () {
                    var self = {},name = "foo";
                    function _wroking() {
                        console.log("working");
                    }
                    self.hello = function () {
                        console.log("hello " + name);
                    }
                    self.doWork = function () {
                        _wroking();
                    }
                    return self;
                })();

    这样内部方法和外部方法就有明确的区分,可以看下Zepto的大体结构:

    var Zepto = (function() {
            var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter,
                    zepto = {}, camelize, uniq;
                    //...........
                    function isObject(obj) { return type(obj) == "object" }
                    //..........
                    zepto.matches = function (element, selector) {
                    //...
                    }
                    zepto.init = function(selector, context) {
                    //......
                    }
                    $ = function (selector, context) {
                        return zepto.init(selector, context)
                    }
                    function extend(target, source, deep) {
                     //...
                    }
                    $.extend = function (target) {
                    }
                })()
    
                window.Zepto = Zepto
                window.$ === undefined && (window.$ = Zepto)

    内部定义了zepot对象,并注意到有一个extend方法,便于后面扩展zepot的这个模块,当然也可以扩展其他的模块。target是指定的。

      function extend(target, source, deep) {
        for (key in source)
          if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
            if (isPlainObject(source[key]) && !isPlainObject(target[key]))
              target[key] = {}
            if (isArray(source[key]) && !isArray(target[key]))
              target[key] = []
            extend(target[key], source[key], deep)
          }
          else if (source[key] !== undefined) target[key] = source[key]
      }
    
      // Copy all but undefined properties from one or more
      // objects to the `target` object.
      $.extend = function(target){
        var deep, args = slice.call(arguments, 1)
        if (typeof target == 'boolean') {
          deep = target
          target = args.shift()
        }
        args.forEach(function(arg){ extend(target, arg, deep) })
        return target
      }
    View Code

    再扩展子模块的时候就方便了:

                ;(function ($) {
                    //...
                    $.event = { add: add, remove: remove }
                    $.proxy = function(fn, context) {
                      //...
                    }
                })(Zepto);

    现代的模块机制

    AMDCMD都是将模块的定义封装进了一个友好的API。就是require.js和sea.js中的define方法。先看一个简单的现代模块实现:

    var MyModules = (function () {
                    var modules = {};
    
                    function define(name, deps, impl) {
                        for (var i = 0; i < deps.length; i++) {
                            deps[i] = modules[deps[i]];
                        }
                        modules[name] = impl.apply(impl, deps);
                    }
    
                    function get(name) {
                        return modules[name];
                    }
    
                    return {
                        define: define,
                        get: get
                    }
                })();
                MyModules.define("bar", [], function () {
                    function hello(who) {
                        return "Let me introduce: " + who;
                    }
                    return {
                        hello: hello
                    };
                });
                MyModules.define("foo", ["bar"], function (bar) {
                    var hungry = "jazz";
    
                    function awsome() {
                        console.log(bar.hello(hungry));
                    }
    
                    return {
                        awsome: awsome
                    }
                });
                var bar = MyModules.get("bar");
                var foo = MyModules.get("foo");
    
                console.log(bar.hello("hippo"));//Let me introduce: hippo
                foo.awsome();//Let me introduce: jazz

    MyModules的define方法包含name,deps,impl三个参数,name表示是模块名称,deps表示是依赖项,impl表示实现。关键是modules[name] = impl.apply( impl, deps );这一句,上面的for循环将一个模块名称数组先转成一个包含具体模块的数组,然后apply给具体的实现方法。相当于是注入了依赖项。注意到定义foo模块的时候,依赖了bar模块,只需要在deps这个参数加入[“bar”]即可。

    在sea.js中,模块的状态做了区分:

    var STATUS = Module.STATUS = {
      // 1 - The `module.uri` is being fetched 相当于初始化
      FETCHING: 1,
      // 2 - The meta data has been saved to cachedMods 缓存在cacheMods
      SAVED: 2,
      // 3 - The `module.dependencies` are being loaded
      LOADING: 3,
      // 4 - The module are ready to execute
      LOADED: 4,
      // 5 - The module is being executed
      EXECUTING: 5,
      // 6 - The `module.exports` is available
      EXECUTED: 6
    }

    看下define方法:

    Module.define = function (id, deps, factory) {
      var argsLen = arguments.length
    
      // define(factory)
      if (argsLen === 1) {
        factory = id
        id = undefined
      }
      else if (argsLen === 2) {
        factory = deps
    
        // define(deps, factory)
        if (isArray(id)) {
          deps = id
          id = undefined
        }
        // define(id, factory)
        else {
          deps = undefined
        }
      }
    
      // Parse dependencies according to the module factory code
      if (!isArray(deps) && isFunction(factory)) {
        deps = parseDependencies(factory.toString())
      }
    
      var meta = {
        id: id,
        uri: resolve(id),
        deps: deps,
        factory: factory
      }
    
      // Try to derive uri in IE6-9 for anonymous modules
      if (!meta.uri && doc.attachEvent) {
        var script = getCurrentScript()
    
        if (script) {
          meta.uri = script.src
        }
    
        // NOTE: If the id-deriving methods above is failed, then falls back
        // to use onload event to get the uri
      }
    
      // Emit `define` event, used in nocache plugin, seajs node version etc
      emit("define", meta)
    
      meta.uri ? save(meta.uri, meta) :
          // Save information for "saving" work in the script onload event
          anonymousMeta = meta
    }
    View Code

    save的内部是通过Module.get 缓存起uri和deps(依赖项)构建的Module对象。

    Module.get = function(uri, deps) {
      return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
    }

    require也是通Module.get来获取模块:

    seajs.require = function(id) {
      return (cachedMods[resolve(id)] || {}).exports
    }

    不像例子中是一次性加载模块,sea.js可以在需要的地方再加载对应的模块。因为我们在很多js框架中看到下面这一块:

    (function (root, factory) { 
        if (typeof define === 'function' && define.amd) {
            define(factory);
        } else if (typeof exports === 'object') {
            module.exports = factory();
        } else {
            root.xxx= factory();
        }
    })(this, function () {
    //。。。
    }

     支持amd规范和node模块。

    ES6中的模块

    以上的模块都是基于函数的,API在运行时都可以被修改。ES6中为模块增加了一级语法支持,通过模块系统进行加载时,ES6会将文件独立的模块来处理。使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。

    // ES6模块
    import { stat, exists, readFile } from 'fs';

    上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。

    下面是一个circle.js文件,它输出两个方法areacircumference

    // circle.js
    
    export function area(radius) {
      return Math.PI * radius * radius;
    }
    
    export function circumference(radius) {
      return 2 * Math.PI * radius;
    }

    现在,加载这个模块。

    // main.js
    
    import { area, circumference } from './circle';
    
    console.log('圆面积:' + area(4));
    console.log('圆周长:' + circumference(14));

    上面写法是逐一指定要加载的方法,整体加载的写法如下。

    import * as circle from './circle';

    这语法让人有点激动。而且是不允许修改的,这样就很大的确保了接口的稳定性。

    import * as circle from './circle';
    
    // 下面两行都是不允许的 
    circle.foo = 'hello';
    circle.area = function () {};

    小结:模块在JavaScript运用很广泛,好的模块构建能确定一个清晰的结构,有助于分工和维护。

    参考文档:

    ES6 Module: http://es6.ruanyifeng.com/#docs/module

    CMD规范:https://github.com/seajs/seajs/issues/242

    阅读书籍:

    资源在读书群中。

  • 相关阅读:
    机器学习--决策树
    插入排序、选择排序的实现与性能比较
    【笔记】如何实现属性可修改的函数装饰器
    【笔记】如何为被装饰的函数保存元数据
    【笔记】对文件的一些操作
    【笔记】对字符串的一些操作
    USB鼠标按键驱动
    LCD驱动 15-3
    LCD驱动 15 -2
    LCD驱动 15-1
  • 原文地址:https://www.cnblogs.com/stoneniqiu/p/6396526.html
Copyright © 2011-2022 走看看