- JS基础-1.01 复习
- JS基础-1.02 作业
- JS基础-1.03 聚合查询
- JS基础-1.04 分组查询
- JS基础-1.05 子查询多表查询
- JS基础-1.06 多表查询
- JS基础-2.01 学习语言基本步骤
- JS基础-2.02 js概述1
- JS基础-3.01 开发环境
- JS基础-3.02 语法规范
- JS基础-4.01 变量常量1
- JS基础-4.02 变量常量2
- JS基础-5.01 复习
- JS基础-5.02 数据类型1
- JS基础-5.03 数据类型2
- JS基础-5.04 类型转换1
- JS基础-5.05 类型转换2
- JS基础-5.06 算数运算符
- JS基础-5.07 比较运算符
- JS基础-5.08 逻辑运算符
- JS基础-5.09 短路逻辑
- JS基础-5.10 位运算符
- JS基础-6.01 复习
- JS基础-6.02 赋值运算符
- JS基础-6.03 三目运算
- JS基础-6.04 浏览器端函数
- JS基础-6.05 if语句1
- JS基础-6.06 if语句2
- JS基础-6.07 练习
- JS基础-6.08 if语句3
- JS基础-6.09 switch case语句1
- JS基础-6.10 switch case语句2、循环
- JS基础-7.01 复习
- JS基础-7.02 while循环
- JS基础-7.03 while循环练习
- JS基础-7.04 break练习
- JS基础-7.05 复习、do while循环
- JS基础-7.06 do while循环练习
- JS基础-7.07 for循环_1
- JS基础-7.08 for循环练习
- JS基础-7.09 for循环_2
- JS基础-7.10 循环嵌套
- JS基础-8.1 复习
- JS基础-8.2 作业+函数
- JS基础-8.3 创建普通函数
- JS基础-8.4 函数练习
- JS基础-8.5 复习
- JS基础-8.6 break和return对比
- JS基础-8.7 练习
- JS基础-8.8 阶乘+作用域-1
- JS基础-8.9 作用域-2
- JS基础-8.10 复习
- JS基础-8.11 复习+作业
- JS基础-8.12 递归
- JS基础-8.13 递归练习+匿名函数
- JS基础-8.14 回调函数1
- JS基础-8.15 回调函数2
- JS基础-9.1 对象1
- JS基础-9.2 对象2
- JS基础-9.3 对象3
- JS基础-9.4 复习
- JS基础-9.5 查找属性
- JS基础-9.6 练习
- JS基础-10.1 数组
- JS基础-10.2 数组练习
- JS基础-10.3 数组练习+遍历数组
- JS基础-10.4 数组for-in遍历
- JS基础-11.1 数组-遍历练习+数组API
- JS基础-11.2 数组API练习
- JS基础-12.1 复习+作业
- JS基础-12.2 冒泡作业+数组API
- JS基础-12.3 数组API+二维数组
- JS基础-13.1 字符串
- JS基础-13.2 字符串API-1
- JS基础-13.3 字符串API练习-1
- JS基础-13.4 字符串API练习-2
- JS基础-13.5 字符串API-2+匹配模式1
- JS基础-13.6 匹配模式-2+Math
- JS基础-14.1 复习+作业
- JS基础-14.2 作业2
- JS基础-14.3 date对象-1
- JS基础-14.4 date练习
- JS基础-14.5 date对象-2
- JS基础-14.6 date练习2
- JS基础-14.7 date对象-3
- JS基础-14.8 number、Boolean对象
- JS基础-15.1 错误处理
- JS核心-1.1 正则表达式-01
- JS核心-1.2 正则表达式-02
- JS核心-1.3 正则表达式-03
- JS核心-1.4 正则表达式-04
- JS核心-1.5 正则表达式-05
- JS核心-1.6 正则表达式-06
- JS核心-1.7 正则表达式-indexof
- JS核心-1.8 正则表达式-search-01
- JS核心-2.1 复习
- JS核心-2.2 正则表达式-search
- JS核心-2.3 正则表达式-match
- JS核心-2.4 正则表达式-match-g
- JS核心-2.5 正则表达式-replace-01
- JS核心-2.6 正则表达式-replace-02
- JS核心-2.7 正则表达式-delete
- JS核心-2.8 正则表达式-spilt
- JS核心-2.9 正则表达式-RegExp对象-01
- JS核心-2.10 正则表达式-test
- JS核心-3.1 复习
- JS核心-3.2 正则表达式-RegExp对象-02
- JS核心-3.3 function
- JS核心-3.4 function-赋值创建
- JS核心-3.5 function-全局作用域
- JS核心-3.6 overload
- JS核心-3.7 参数
- JS核心-3.8 匿名函数
- JS核心-3.9 作用域
- JS核心-3.10 程序和函数执行过程
- JS核心-3.11 闭包
- JS核心-3.12 闭包原理
- JS核心-4.1 复习-01
- JS核心-4.2 复习-02
- JS核心-4.3 面向对象
- JS核心-4.4 面向对象-创建对象
- JS核心-4.5 克隆
- JS核心-4.6 构造函数
- JS核心-4.7 继承
- JS核心-4.8 继承-添加共有成员
- JS核心-4.9 属性
- JS核心-4.10 内置对象
- JS核心-5.1 复习
- JS核心-5.2 原型链
- JS核心-5.3 重写
- JS核心-5.4 继承
- JS核心-5.5 严格模式1
- JS核心-5.6 严格模式2
- JS核心-5.7 递归
- JS核心-5.8 保护对象
- JS核心-5.9 数据保护
- JS核心-5.10 访问器属性
- JS核心-5.11 复习
- JS核心-5.12 锁定
- JS核心-5.13 create函数
- JS核心-5.14 call-apply-bind
- JS核心-5.15 双色球
- JS核心-5.16 every
- JS核心-5.17 every2
- JS核心-5.18 foreach
- JS核心-5.19 map
- JS核心-5.20 reduce
- JS核心-6.1 复习
- JS核心-6.2 面试题
- JS核心-6.3 es6
- JS核心-6.4 es6
- JS核心-6.5 箭头函数
- JS核心-6.6 forof-01
- JS核心-6.7 forof-02
- JS核心-6.8 rest语法
- JS核心-6.9 解构
- JS核心-6.10 对象解构
- JS核心-6.11 参数解构
- JS核心-6.12 复习
- JS核心-6.13 简化
- JS核心-6.14 super
- JS核心-6.15 promise
- JS核心-6.16 promise
- JS核心-6.17 promise
- JS核心-6.18 promise
- JS核心-6.19 ES7
JavaScript是横亘在所有学习web前端开发人员面前的一座大山。很多童鞋一直在要求我能不能讲解一季js的零基础视频教程,推脱了许久,终于得空开始讲解。喜欢的话请努力转发出去,让更多的人看到,JavaScript其实一点都不难!
javascript!是一门非常强大的脚本语言,应用的范围非常广泛,每一个web开发者学好javascript也是必须的,本套视频教程详细的讲解了javascript各个知识点、关键点,其中涉及到高深的函数概念、原型概念、接口概念、单体概念、更是详细的讲解了javascript设计模式。
Part 1:你能再次解释模块是什么吗?
优秀的作者会将他的书分为章和节。同理,优秀的程序员能将他的程序划分为各个模块。
就像书的章节,模块就是词(或代码,视情况而定)的集群。
好的模块拥有以下特点:不同功能是高度独立的,并且它们允许被打乱、移除或在必要时进行补充,而不会扰乱系统作为一个整体。
为什么使用模块?
使用模块有诸多好处,如利于建立一个扩展性强的、互相依赖的代码库。而在我看来,其最重要是:
1)可维护性:根据定义,模块是独立的。一个设计良好的模块意在尽可能减少对代码库的依赖,所以它才能单独地扩展与完善。更新一个从其它代码段解耦出来的独立模块显然来得更简单。
回到书的案例,如果书的某个章节需要进行小改动,而该改动会牵涉到其它所有章节,这无疑是个梦魇。相反,如果每章节都以某种良好方式进行编写,那么改动某章节,则不会影响其它章节。
2)命名空间:在 JavaScript 中,如果变量声明在顶级函数的作用域外,那么这些变量都是全局的(意味着任何地方都能读写它)。因此,造成了常见的“命名空间污染”,从而导致完全无关的代码却共享着全局变量。
无关的代码间共享着全局变量是一个严重的 编程禁忌。
我们将在本文后面看到,模块通过为变量创建一个私有空间,从而避免了命名空间的污染。
3)可重用性:坦诚地讲:我们都试过复制旧项目的代码到新项目上。例如,我们复制以前项目的某些功能方法到当前项目中。
该做法看似可行,但如果发现那段代码有更好的实现方式(即需要改动),那么你就不得不去追溯并更新任何你所粘贴到的任何地方。
这无疑会浪费大量的时间。因此可复用的模块显然让你编码轻松。
如何整合为模块?
整合为模块的方式有很多。下面就看看其中的一些方法:
模块模式(Module pattern)
模块模式用于模仿类(由于 JavaScript 并不支持原生的类),以致我们能在单个对象中存储公有和私有变量与方法——类似于其它编程语言(如 Java 或 Python )中的类的用法。模块模式不仅允许我们创建公用接口 API(如果我们需要暴露方法时),而且也能在闭包作用域中封装私有变量和方法。
下面有几种方式能实现模块模式(module pattern)。第一个案例中,我将会使用匿名闭包。只需将所有代码放进匿名函数中,就能帮助我们实现目标(记住:在 JavaScript 中,函数是唯一创建新作用域的方式)。
Example 1:匿名闭包(Anonymous closure)
(function () {
// We keep these variables private inside this closure scope
// 让这些变量在闭包作用域内变为私有(外界访问不到这些变量)。
var myGrades = [93, 95, 88, 0, 55, 91];
var average = function() {
var total = myGrades.reduce(function(accumulator, item) {
return accumulator + item}, 0);
return 'Your average grade is ' + total / myGrades.length + '.';
}
var failing = function(){
var failingGrades = myGrades.filter(function(item) {
return item < 70;});
return 'You failed ' + failingGrades.length + ' times.';
}
console.log(failing());
}());
// ‘You failed 2 times.’
通过这种结构,匿名函数拥有自身的求值环境或”闭包“,并立即执行它。这就实现了对上级(全局)命名空间的隐藏。
这种方法的好处是:能在函数内使用本地变量,而不会意外地重写已存在的全局变量。当然,你也能获取全局变量,如:
var global = 'Hello, I am a global variable :)';
(function () {
// We keep these variables private inside this closure scope
var myGrades = [93, 95, 88, 0, 55, 91];
var average = function() {
var total = myGrades.reduce(function(accumulator, item) {
return accumulator + item}, 0);
return 'Your average grade is ' + total / myGrades.length + '.';
}
var failing = function(){
var failingGrades = myGrades.filter(function(item) {
return item < 70;});
return 'You failed ' + failingGrades.length + ' times.';
}
console.log(failing());
console.log(global);
}());
// 'You failed 2 times.'
// 'Hello, I am a global variable :)'
这里需要注意的是,匿名函数必须被小括号包裹住,这是因为当语句以关键字 function 开头时,它会被认为是一个函数的声明语句(记住,JavaScript 中不能拥有未命名的函数声明语句)。因此,该括号会创建一个函数表达式代替它。欲知详情,可点击 这里。
Example 2:全局导入(Global import )
另一个常见的方式是类似于 jQuery 的全局导入(global import)。该方式与上述的匿名闭包相似,特别之处是传入了一个全局变量作为参数:
(function (globalVariable) {
// Keep this variables private inside this closure scope
var privateFunction = function() {
console.log('Shhhh, this is private!');
}
// Expose the below methods via the globalVariable interface while
// hiding the implementation of the method within the
// function() block
// 通过 globalVariable 接口暴露下面的方法。当然,这些方法的实现则隐藏在 function() 块内
globalVariable.each = function(collection, iterator) {
if (Array.isArray(collection)) {
for (var i = 0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
} else {
for (var key in collection) {
iterator(collection[key], key, collection);
}
}
};
globalVariable.filter = function(collection, test) {
var filtered = [];
globalVariable.each(collection, function(item) {
if (test(item)) {
filtered.push(item);
}
});
return filtered;
};
globalVariable.map = function(collection, iterator) {
var mapped = [];
globalUtils.each(collection, function(value, key, collection) {
mapped.push(iterator(value));
});
return mapped;
};
globalVariable.reduce = function(collection, iterator, accumulator) {
var startingValueMissing = accumulator === undefined;
globalVariable.each(collection, function(item) {
if(startingValueMissing) {
accumulator = item;
startingValueMissing = false;
} else {
accumulator = iterator(accumulator, item);
}
});
return accumulator;
};
}(globalVariable));
在该案例中,globalVariable 是唯一的全局变量。这个相对于匿名闭包的优势是:提前声明了全局变量,能让别人更清晰地阅读你的代码。
var myGradesCalculate = (function () {
// Keep this variable private inside this closure scope
var myGrades = [93, 95, 88, 0, 55, 91];
// Expose these functions via an interface while hiding
// the implementation of the module within the function() block
return {
average: function() {
var total = myGrades.reduce(function(accumulator, item) {
return accumulator + item;
}, 0);
return'Your average grade is ' + total / myGrades.length + '.';
},
failing: function() {
var failingGrades = myGrades.filter(function(item) {
return item < 70;
});
return 'You failed ' + failingGrades.length + ' times.';
}
}
})();
myGradesCalculate.failing(); // 'You failed 2 times.'
myGradesCalculate.average(); // 'Your average grade is 70.33333333333333.'
正如你所看到的,该方式让你决定哪个变量/方法是私有的(如 myGrades),哪个变量/方法是需要暴露出来的(通过将需要暴露出来的变量/方法放在 return 语句中,如 average & failing)。
Example 4: 暴露模块模式(Revealing module pattern)
这与上一个方法非常类似,只不过该方法确保所有变量和方法都是私有的,除非显式暴露它们:
var myGradesCalculate = (function () {
// Keep this variable private inside this closure scope
var myGrades = [93, 95, 88, 0, 55, 91];
var average = function() {
var total = myGrades.reduce(function(accumulator, item) {
return accumulator + item;
}, 0);
return'Your average grade is ' + total / myGrades.length + '.';
};
var failing = function() {
var failingGrades = myGrades.filter(function(item) {
return item < 70;
});
return 'You failed ' + failingGrades.length + ' times.';
};
// Explicitly reveal public pointers to the private functions
// that we want to reveal publicly
return {
average: average,
failing: failing
}
})();
myGradesCalculate.failing(); // 'You failed 2 times.'
myGradesCalculate.average(); // 'Your average grade is 70.33333333333333.'
看似有许多知识需要我们吸收,但这只是模块模式(module patterns)的冰山一角。在我学习这方面知识时,发现了下面这些有用的资源:
Learning JavaScript Design Patterns: 出自 Addy Osmani,他以极其简洁的方式对模块模式进行详细分析。
Adequately Good by Ben Cherry:一篇通过案例对模块模式的高级用法进行概述的文章。
Blog of Carl Danley:一篇对模块模式进行概述并拥有其它 JavaScript 模式资源的文章。
CommonJS and AMD
上述所有方法都有一个共同点:使用一个全局变量将其代码封装在一个函数中,从而利用闭包作用域为自身创建一个私有的命名空间。
虽每种方式都有效,但他们也有消极的一面。
举个例子说,作为一名开发者,需要以正确的依赖顺序去加载你的文件。更直接地说,假如你在项目中使用 Backbone,那么你需要在文件中用 script 标签引入 Backbone 的源代码。
然而,由于 Backbone 重度依赖于 Underscore.js,因此 Backbone 的 script 标签不能放在 Underscore 的 script 标签前。
作为一名开发者,有时会为了正确处理并管理好这种依赖关系而感到头痛。
另一个消极部分是:他们仍会导致命名空间污染。例如,两个模块拥有同样的名字,或者一个模块拥有两个版本,而且你同时需要他们俩。
所以,你可能会想到:我们能不能设计一种方法,无须通过全局作用域去请求一个模块接口呢?
答案是能!
有两种流行且实现良好的方法:CommonJS 和 AMD。
CommonJS
CommonJS 是一个志愿工作组设计并实现的 JavaScript 声明模块 APIs。
CommonJS 模块本质上是一片可重用的 JavaScript 代码段,将其以特定对象导出后,其它模块即可引用它。如果你接触过 Node.js,那么你应该非常熟悉这种格式。
通过 CommonJS,每个 JavaScript 文件保存的模块都拥有其独一无二的模块上下文(就像封装在闭包内)。在此作用域中,我们使用 module.exports 对象导出模块,然后通过 require 导入它们。
当你定义一个 CommonJS 模块时,代码类似:
function myModule() {
this.hello = function() {
return 'hello!';
}
this.goodbye = function() {
return 'goodbye!';
}
}
module.exports = myModule;
我们使用特定对象模块,并将 module.exports 指向我们的函数。这让 CommonJS 模块系统知道我们想导出什么,并让其它文件能访问到它。
然后,当有人想使用 myModule 时,他们可在文件内将其 require 进来,如:
var myModule = require('myModule');
var myModuleInstance = new myModule();
myModuleInstance.hello(); // 'hello!'
myModuleInstance.goodbye(); // 'goodbye!'
该方法相对于我们先前讨论的模块模式有两个显而易见的好处:
避免了全局命名空间的污染
让依赖关系更明确
此外,该语法非常紧凑简单,我个人非常喜欢。
另外需要注意的一点是:CommonJS 采用服务器优先的方式,并采用同步的方式加载模块。这点很重要,因为如果我们有其它三个模块需要 require 进来的话,这些模块会被一个接一个地加载。
这种工作方式很适合应用在服务器上。但不幸的是,当你将这种方式应用在浏览器端时,就会出现问题。因为相对于硬盘,从 web 上读取模块更耗时(网络传输等因素)。而且,只要模块正在加载,就会阻塞浏览器运行其它任务。这是由于 JavaScript 线程会在代码加载完成前被停止。(在 Part 2 的模块打包部分,我会告诉你如何解决此问题。而现在,只需了解到这)。
AMD
CommonJS 非常不错,但如果我们想异步加载模块呢?答案是异步模块定义(Asynchronous Module Definition),或简称 AMD。
使用 AMD 加载模块的代码类似:
define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
console.log(myModule.hello());
});
define 函数的第一个参数是一个包含本模块所依赖的模块数组。这些依赖都在后台加载(以不阻塞的方式)。加载完成后,define 会调用其指定的回调函数。
接着,回调函数会将加载完成后的依赖作为其参数(一一对应)——在该案例中,是 myModule 和 myOtherModule。因此,回调函数就能使用这些依赖。当然,这些依赖本身也需要通过 define 关键字定义。
例如,myModule 类似:
define([], function() {
return {
hello: function() {
console.log('hello');
},
goodbye: function() {
console.log('goodbye');
}
};
});
与 CommonJS相反,AMD 采取浏览器优先的方式,通过异步加载的方式完成任务。(注意,有很多人并不赞成此方式,因为他们坚信在代码开始运行时动态且逐个地加载文件是不好的。我将会在下一节的模块构建(module-building)中探讨更多相关信息)。
除了异步外,AMD 的另一个好处是:模块可以是一个对象、函数、构造函数、字符串、JSON 或其它各种类型,而 CommonJS 仅支持对象作为模块。
话虽如此,AMD 不兼容 io、文件系统(filesystem)和其它通过 CommonJS 实现的面向服务器的功能,而且其通过函数封装的语法与简单的 require 语句相比显得有点啰嗦。