【读书笔记】-- JavaScript模块

来源:互联网 发布:js日期时间选择器 编辑:程序博客网 时间:2024/05/13 11:10

在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);

现代的模块机制

AMD和CMD都是将模块的定义封装进了一个友好的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.jsexport function area(radius) {  return Math.PI * radius * radius;}export function circumference(radius) {  return 2 * Math.PI * radius;}

现在,加载这个模块。

// main.jsimport { 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

阅读书籍:

资源在读书群中。

 

0 0
原创粉丝点击