Closure
闭包:有权访问另一个函数作用域中变量的函数。
一个作用域可以访问另外一个函数内部的局部变量,就产生闭包,局部变量在函数执行完后不会被立即销毁,而是等所有函数调用完该变量后再销毁。
闭包的主要作用:延伸变量的作用范围。
过度使用闭包会造成内存泄漏。
应用
模拟类私有属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function getGeneratorFunc() { var _name = "John"; var _age = 22;
return function () { return { getName: function () { return _name; }, getAge: function () { return _age; }, }; }; }
var obj = getGeneratorFunc()(); obj.getName(); obj.getAge(); obj._age;
|
柯里化(currying)
柯里化(currying),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化的优势之一就是参数的复用,它可以在传入参数的基础上生成另一个全新的函数,如函数bind
方法的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| Function.prototype.myBind = function (context = window) { if (typeof this !== "function") throw new Error("Error"); let selfFunc = this; let args = [...arguments].slice(1);
return function F() { if (this instanceof F) { return new selfFunc(...args, arguments); } else { return selfFunc.apply(context, args.concat(arguments)); } }; };
function typeOf(value) { return function (obj) { const toString = Object.prototype.toString; const map = { "[object Boolean]": "boolean", "[object Number]": "number", "[object String]": "string", "[object Function]": "function", "[object Array]": "array", "[object Date]": "date", "[object RegExp]": "regExp", "[object Undefined]": "undefined", "[object Null]": "null", "[object Object]": "object", }; return map[toString.call(obj)] === value; }; }
var isNumber = typeOf("number"); var isFunction = typeOf("function"); var isRegExp = typeOf("regExp");
isNumber(0); isFunction(function () {}); isRegExp({});
|
作用域
JS 中使用的是词法作用域(lexical scopes),也即静态作用域,函数的作用域在定义的时候已经确认,和执行的位置无关。动态作用域的语言如bash
,在下例中会输出 2。
1 2 3 4 5 6 7 8 9 10 11 12
| var value = 1;
function foo() { console.log(value); }
function bar() { var value = 2; foo(); }
bar();
|
JS 中创建作用域:
- 创建函数作用域
let
、const
创建块级作用域
try...catch
中,err
仅存在于catch
子句中
eval("var b = 3")
欺骗语法作用域
with(obj)
临时扩展作用域
作用域和闭包
模块化
模块化
- 解决变量间相互污染的问题,以及变量命名冲突的问题
- 提高代码的可维护性、可拓展性和复用性
自执行函数实现模块化
自执行函数本质上是通过函数作用域解决了命名冲突、污染全局作用域的问题。
AMD、CMD、UMD
AMD (Asynchronous Module Definition)
RequireJS
AMD 采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖项都加载完成之后,这个回调函数才会运行。
1 2 3 4 5 6
| define(["./a", "./b"], function (a, b) { a.do(); b.do(); });
|
CMD (Common Module Definition)
SeaJS
CMD 可以使用 require
同步加载依赖,也可以使用 require.async
来异步加载依赖。
1 2 3 4 5 6 7 8 9 10 11 12
| define(function (require, exports, module) { var a = require("./a"); a.doSomething();
require.async("./b", function (b) { b.doSomething(); }); });
|
AMD 和 CMD 相比,很大的一个区别就是引入模块的时机,AMD 是前置依赖,也就是说,目标模块代码执行前,必须保证所有的依赖都被引入并且执行。CMD 是后置依赖,也就是说,只有在目标代码中手动执行 require(..)
的时候,相关依赖才会被加载并执行。
还有一个区别就是引入模块的方式,AMD 的定位是浏览器环境,所以是异步引入;而 CMD 的定位是浏览器环境和 Node
环境,它可以使用 require
进行同步引入,也可以使用 require.async
的方式进行异步引入。
UMD
jquery
对 AMD 和 CMD 以及全局注册的方式做了整合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| (function (root, factory) { if (typeof define === "function" && define.amd) { define(["jquery"], factory); } else if (typeof exports === "object") { module.exports = factory(require("jquery")); } else { root.returnExports = factory(root.jQuery); } })(this, function ($) { function myFunc() {}
return myFunc; });
|
CommonJS
CommonJS 是的 NodeJS 所使用的一种服务端的模块化规范,它将每一个文件定义为一个 module ,模块必须通过 module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。
如果require
的路径不以/
或./
开头,会依次搜索Node
的核心模块,各级目录下的node_modules
目录,若未找到,则会自动添加文件后缀.js
、.json
、.node
,再次寻找。
ES6 ESModule
- export 导出的必须是接口,
export default
例外
- 模块继承:
export { vueComponent as newVueComponent } from './app.vue';
,或default
版本,export { default } from './app.vue';
CommonJs vs. ESModule
Node.js module system
- CommonJS 模块输出的是一个值的拷贝,ESModule 输出的是值的引用。CommonJS 输出的是值的拷贝,也就是说一旦输出,模块内部的变化就影响不到这个值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
let counter = 3; function incCounter() { counter++; } module.exports = { getCounter: () => counter, counter, incCounter, };
let mod = require("./mod");
console.log(mod.counter); mod.incCounter(); console.log(mod.counter); console.log(mod.getCounter());
let counter = 3; function incCounter() { counter++; } function getCounter() { return counter; } export { counter, incCounter, getCounter };
import { counter, getCounter, incCounter } from "./mod.js";
console.log(counter); incCounter(); console.log(counter); console.log(getCounter());
|
ESModule 的模块化是静态的,和 CommonJS 不同,ESModule 模块不是对象,而是通过 export 命令显示输出的指定代码的片段,再通过 import 命令将代码命令输入。也就是说在编译阶段就需要确定模块之间的依赖关系,这一点不同于 AMD / CMD / CommonJS ,这三者都是在运行时确定模块间的依赖关系的。
- ES6 的模块自动采用严格模式
- ESModule 导出的模块是只读的,不能变更,否则报错,如修改
counter
,会报:Uncaught TypeError: Assignment to constant variable. at main.js