JS文章迁移

This commit is contained in:
结发受长生 2018-05-20 20:36:45 +08:00
parent 10e4564c71
commit ee2b5a2da4
15 changed files with 1142 additions and 0 deletions

View File

@ -0,0 +1,143 @@
---
title: ES6的重要新特性
date: 2018-4-22 19:50:19
tags:
- JavaScript
- ECMAScript6
categories:
- JavaScript
---
1. **函数的默认参数**
```javascript
var link = function(height=50,color="red"){
//some code...
}
```
<!-- more -->
在没有这种写法的时候
我们通常需要通过下面的方式来设置函数参数的默认值
```javascript
var link = function(height,color) {
height = height || 50;
color = color || "red";
//some code...
}
```
但是这种方式存在缺陷 , 就是当实参为0 或者 ""等这种会被判定为false的值 , 他们就会被默认值所替代
而包含参数默认值的函数则不会存在这个问题
2. **模板表达式**
以往如果需要在字符串中嵌入某个变量值 , 需要使用加号进行拼接
```javascript
var port = 7001;
console.log("the server is listening at " + port + "!");
```
在ES6当中 , 使用`反引号`包裹的字符串 , 可以使用`${varName}`的形式来表示模板字符
```javascript
var port = 7001;
console.log(`the server is listening at ${port}!`);
```
用法类似JSP当中的 EL表达式
3. **多行字符串**
以往要在代码中换行显示字符串 , 需要使用加好进行拼接 , 如果要使字符串当中实际包含换行 , 则需要使用转义字符`\n`
反引号当中的字符串可以直接进行换行 , 而不需要使用`\n`符号
4. **拆包表达式**
如果要把一个JS对象当中的值赋给局部变量 , 以往通常需要这么做
```javascript
var data = {
name : "Sookie",
age : 18
};
var name = data.name;
var age = data.age;
console.log(`name : ${name}, age : ${age}`);
```
在ES6当中 , 使用以下语句可以达到相同的效果
```javascript
var {name,age} = data;
```
对于数组的支持
```javascript
var data = {
name : "Sookie",
age : 18,
skill : ["JavaScript","Java","MongoDB"]
};
//这里相当于产生了3个局部变量
var [sk1, sk2, sk3] = data.skill;
console.log(sk1, sk2, sk3);
```
如果要跳过数组中某个位置的元素也是可以的
```javascript
var [sk1, , sk3] = data.skill;
```
5. **块级作用域的`let``const`**
let可以认为是一个更新的var , 使用它定义的局部变量 , 作用域仅存在与当前的代码块( 包括复合语句 )
而由于变量的定义提升问题 , var是不能完全做到这一点的
例如 , 从习惯了写Java的程序员 , 通常习惯于下面这种写法
```javascript
var sum = 0;
for(var i=0 ; i<10 ; i++){
sum += i;
}
console.log(sum);//45
console.log(i);//10
```
但是变量 i 的作用域并不仅限于for循环的子句
如果使用如下方式
```javascript
var sum = 0;
for(let i=0 ; i<10 ; i++){
sum += i;
}
console.log(sum);//45
console.log(i);
//ReferenceError: i is not defined
```
就把 i 的作用域限制在了代码块当中
对于使用`const`定义的变量 , 可以参照C语言中的const关键字 , 和Java当中的final关键字
作用仅仅是该引用的指向不可变 , 但是指向的对象本身是可变的
6. **类与面向对象**
在ES5当中 , 要定义一个类, 只能是编写一个构造函数 , 这个函数与普通的函数没有本质区别 , 只是使用new关键字进行调用 , 它就可以构造一个对象
`class`关键字 , 在ES6之前 , 只是作为一个没有意义的保留字存在的
现在 , 我们可以使用与Java十分相像的方式去写一个类
```javascript
class BaseModel {
constructor(options = {}, data = []) {
this.name = "Sookie";
this.options = options;
this.data = data;
}
showName() {
console.log(`the name is ${this.name}`);
}
}
var model = new BaseModel();
model.showName();
```
constructor表示这个类的构造函数
需要注意的是 : 对于对象当中属性的赋值 , 必须要在构造函数当中完成 , 而不能像Java那样直接对类中的属性设定初始值
#####继承
继承的实现方式与Java也十分类似
```javascript
class MyModel extends BaseModel {
constructor(options = {}, data = []) {
//如果要显式调用父类的构造方法, super必须放在第一条语句
super(options, data);
this.age = 18;
}
showAge() {
console.log(`the name is ${this.age}`);
}
}
var model = new MyModel();
model.showName();
model.showAge();
```

View File

@ -0,0 +1,142 @@
---
title: Promise(1)
date: 2018-4-20 19:43:20
tags:
- JavaScript
- Promise
categories:
- JavaScript
---
在JavaScript当中 , 不存在真正的多线程 , 从而导致所有需要异步执行的操作都需要使用回调函数实现
<!-- more -->
例如 : 使用定时器去执行一个回调函数
```javascript
function callback(){
console.debug("Time Out!");
}
console.log("before");
setTimeout(callback,1000);
console.log("after");
```
定时器会在1秒之后去执行callback这个回调函数
这就实现了一个简单的异步操作
异步操作会在将来的某个时间点触发一个函数调用
Ajax的异步请求也是一种异步操作
类似这种**承诺将来会执行**的对象 , 在ES6当中被统一规范为`Promise对象`
这个对象的最大作用就是将执行代码和处理结果的回调函数进行了分离 , 从而在存在多重回调的情境当中 , 代码的可读性更强
一个简单的例子
```javascript
var status = true;
function test(resolve, reject) {
if (status) {
resolve('success');
} else {
reject('failed');
}
}
var p1 = new Promise(test);
//then传入的是resolve的实现
//catch传入的是reject的实现
p1.then(console.log).catch(console.error);
/*
也可以用如下的方式, 实际效果同上
p1.then(console.log, console.log);
*/
```
Promise对象在创建的时候 , 传入的这个函数当中并不关心成功与失败具体的回调是什么 , 而只是关心何时去执行该回调函数
Promise对象有3种状态
1. `Fulfilled` 成功的状态
2. `Rejected` 失败的状态
3. `Pending` Promise 对象实例创建时候的初始状态
可以由初始状态转换到成功状态 , 或者由初始状态转换到失败状态 , 并且不可逆
我们可以在初始化函数里面自己编写逻辑来确定何种情况下执行成功的回调函数( resolve ) 或者失败的函数( reject )
如果抛出错误而且没有进行捕获 , 则一定会执行失败的函数( reject )
如果对于某种状态 , 要执行多个回调函数
可以进行链式调用 , 也就是连缀多个`then`方法
前一个then方法中的返回值会作为下一个回调函数的参数
```javascript
var status = true;
function test(resolve, reject) {
if (status) {
resolve('success');
} else {
reject('failed');
}
}
var p1 = new Promise(test);
p1
.then(function(msg){
console.log("我是第一个回调函数:" + msg);
return msg;
})
.then(function(msg){
console.log("我是第二个回调函数:" + msg);
})
.catch(console.error);
```
当然上面这种方式只能适用于多重回调函数中只有一个参数的情况
如果要传多个参数
必须使用数组的形式传递
比如参数中是`function([msg1, msg2])`
前一个回调函数中`return ["AA","BB"]`
---
#### Promise.all 和 Promise.race
`Promise.all`方法可以接收一个Promise对象构成的数组作为参数 , 返回一个Promise对象
当数组中的这些Promise对象**全部**转换为成功状态的时候 , 这个对象才转换为成功状态 , 否则就转换为失败状态
```javascript
var status = [true, true];
function test1(resolve, reject){
if(status[0]) {
resolve("success");
} else {
reject("failed");
}
}
function test2(resolve, reject){
if(status[1]) {
resolve("success");
} else {
reject("failed");
}
}
var p1 = new Promise(test1);
var p2 = new Promise(test2);
p1.then((msg)=>console.log("成功" + msg))
.catch((msg)=>console.error("失败" + msg));
p2.then((msg)=>console.log("成功" + msg))
.catch((msg)=>console.error("失败" + msg));
Promise.all([p1,p2])
.then(function(msgs){
console.log(msgs instanceof Array);//true
console.log(msgs);//[ 'success', 'success' ]
});
//注意 : 这里的回调函数的参数就是所有Promise对象成功回调函数传入参数的数组
//(如果只有一个参数,则不构成数组)
```
`Promise.race`方法的用法与all类似 , 它返回的Promise对象是当数组中**存在**Promise对象转为成功状态的时候 , 它就转为成功状态 , 如果全是失败状态 , 它才是失败状态
```javascript
status[1] = false;
var p3 = new Promise(test1);
var p4 = new Promise(test2);
p3.then((msg)=>console.log("成功" + msg))
.catch((msg)=>console.error("失败" + msg));
p4.then((msg)=>console.log("成功" + msg))
.catch((msg)=>console.error("失败" + msg));
Promise.race([p3,p4])
.then(function(msgs){
//参数规则同Promise.all
console.log(msgs);
});
```

View File

@ -0,0 +1,66 @@
---
title: Promise(2)
date: 2018-4-20 19:45:09
tags:
- JavaScript
- Promise
categories:
- JavaScript
---
如果只是执行一次单步回调 , 那么传统的回调函数执行的方式其实并没有什么问题
Promise的主要意义是在于解决多重回调的多层嵌套问题
如果回调的嵌套层级太多 , 会造成代码可读性很差 , 难以维护
<!-- more -->
例如
```javascript
var test = function(num, func) {
console.log(num);
func(num);
};
test(1, function(a){
test(2, function(b){
test(3, function(c){
console.log("回调成功");
});
});
});
```
使用Promise可以将上述的代码改造成线性的模式 , 可读性增强 , 同时也更便于调试
```javascript
var pro = new Promise(function(resolve, reject){
resolve(1);
}).then(function(val){
console.log(val);
return new Promise(function(resolve, reject){
resolve(2);
});
}).then(function(val){
console.log(val);
return new Promise(function(resolve, reject){
resolve(3);
});
}).then(function(val){
console.log(val);
});
```
让then方法的函数每次都是返回不同的Promise实例 , 再去指定这个实例的回调函数即可
并且对于Promise对象 , 它的回调执行是异步的
例如 :
```javascript
new Promise(function(resolve, reject){
resolve("AA");
}).then(function(msg){
console.log(msg);
});
console.log("BB");
/* output:
BB
AA
*/
```
对于异步操作实现的原理 , 可以参考JS的事件驱动模型
JS引擎会将回调函数的执行加入到事件队列当中
从而实现在单线程的条件下 , 代码的异步执行

View File

@ -0,0 +1,77 @@
---
title: 生成器函数
date: 2018-5-6 20:09:40
tags:
- JavaScript
- ECMAScript6
categories:
- JavaScript
---
在ES6标准当中 , 新引入了一种基本数据类型 , 就是`Symbol`
<!-- more -->
在此之前的基本数据类型有
+ undefined
+ null
+ object
+ number
+ boolean
+ string
通常我们可以使用`typeof`来判断一个对象的类型
但是存在一些特例
1. 对于一个函数对象 , 对其执行typeof操作 , 获得的是`"function"`
2. 对于null , 对其执行typeof操作 , 获得的是`"object"`
---
Symbol对象可以认为是一个**唯一标识**
我们可以采用如下的方式来构造出一个Symbol对象
+ Symbol() - 这种方式每次调用 , 返回的对象都不同
+ Symbol.for(string) - 这种方式会试图访问已经存在的Symbol对象 , 如果不存在则创建一个新的
Symbol.for("aa") == Symbol.for("aa") 会获得**true**
+ 使用预定义的Symbol对象 , 比如Symbol.iterator
+ 使用typeof Symbol对象 , 返回"symbol"
### 应用场景
#### 构造一个对象当中的私有属性
JavaScript的对象属性默认是可以被外界访问和修改的
通常我们对于不希望被访问的变量 , 以下划线开头明明
但是这只是一种约定 , 并不具有强制作用
如果我们开发的第三方模块当中 , 需要有只提供给模块内方法访问的私有属性
那么就可以借助Symbol来实现
```javascript
const p = (function(){
const [_x, _y] = [Symbol(), Symbol()];
function Point(x,y) {
this[_x] = x;
this[_y] = y;
}
Point.prototype.length = function() {
const x = this[_x],
y = this[_y];
return Math.sqrt(x * x + y * y);
}
return new Point(3, 4);
})();
console.log(p.length()); //5
```
在上面提到过 , 每次调用Symbol()返回的对象都是不同的
所以在自动执行的函数内部 , _x 和 _y其实是不同的对象
或者我们也可以用`Symbol("x")``Symbol("y")`来进行区分 , 这不是必须的
这种情况下 , 内部的两个属性就真正成为了私有属性 , 无法在外部访问到它的值
因为在外部再次调用Symbol()产生的是不同的对象
#### 避免命名冲突
对于相对复杂的模块 , 暴露出的对象内部的私有变量可能需要很多
如果这些私有变量没有被完全私有化
那么这个模块的兼容性和可扩展性就会很差
因为在扩展时十分容易出现命名的冲突问题
或者可以用一个十分冗长的字符串作为属性名 , 那么源代码的可读性就基本不存在了
使用Symbol可以解决这个问题 , 与上面的类似的
变量私有化 , 则不会产生同名的冲突

View File

@ -0,0 +1,119 @@
---
title: new关键字做了什么
date: 2018-5-2 20:05:33
tags:
- JavaScript
categories:
- JavaScript
---
假设说现在要构造若干个"士兵"对象 , 每个士兵都有类型 攻击力 生命值 这些属性
同时有奔跑 攻击 防御 这些可以执行的动作 , 在代码中表现为方法
<!-- more -->
简单粗暴的方式可以这样做
```javascript
var soldiers = [];
for(let i=0 ; i<100 ; i++) {
let soldier = {
type : "步兵",
id : i,
health : 100,
run : function(){console.log("奔跑");},
attack : function(){console.log("攻击");},
defense : function(){console.log("防御");}
};
soldiers.push(soldier);
}
```
这种方式显然存在一个问题 , 就是浪费了大量的内存
因为每个士兵可以执行的行为都是一样的 , 这几个函数完全可以共用
这种方式却给每个对象创建了独立的函数
兵种也是一样的 , 只有id和生命值 , 每个士兵都要具备自己的值
可以给每个对象指定各自的原型对象 , 只要这些共用的方法写在原型对象当中
```javascript
var soldiers = [];
var soldierProto = {
type : "步兵",
run : function(){console.log("奔跑")},
attack : function(){console.log("攻击");},
defense : function(){console.log("防御");}
}
for(let i=0 ; i<100 ; i++) {
let soldier = {
id : i,
health : 100
};
solider.__proto__ = soldierProto;
soldiers.push(soldier);
}
```
现在把创建士兵的代码放在了两个地方 , 很不优雅
所以可以用一个函数把两者联系起来
```javascript
function createSoldier(id) {
var _soldier = {}; //临时对象
_soldier.__proto__ = createSoldier["原型"];
_soldier.id = id;
_soldier.health = 100;
return _soldier;
}
createSoldier["原型"]= {
type : "士兵",
attackNum : 5, //攻击力
run : function(){console.log("奔跑");},
attack : function(){console.log("攻击");},
defense : function(){console.log("防御");}
}
```
上面的代码在函数中添加了一个属性 , 叫做"原型"
主要做的就是把需要作为原型的对象保存在函数对象的一个属性当中 , 使调用这个函数的时候可以获取到这个原型对象
new关键字所做的事情 , 其实就相当于是上面代码里面我们自己手动实现的事情
+ 自动创建临时对象 ( 在函数内部使用this可以访问到这个临时对象 )
+ 自动绑定该Function对象的原型 ( 统一叫做`prototype` )
+ 自动return这个临时对象
现在写一个可以用new去调用的纯粹的构造函数
```javascript
function soldier(id) {
this.id = id;
this.health = 100;
}
soldier.prototype = {
type : "士兵",
attackNum : 5, //攻击力
run : function(){console.log("奔跑");},
attack : function(){console.log("攻击");},
defense : function(){console.log("防御");}
}
```
除此之外 , 为了记录 **临时对象是由哪个函数创建的** , 会在定义这个函数的时候 , 在函数的prototype属性 ( 是Object ) 上面自动添加一个`constructor`属性 , 比如 :
```javascript
function func(){
this.name = "Sookie";
}
func.prototype.constructor === func; //true
```
所以如果像上面那样直接给函数的prototype属性赋值一个对象的话 , 这个constructor就没了
所以可以采取下面两种做法
```javascript
//1. 手工把这个属性加上
soldier.prototype = {
constructor : soldier,
type : "士兵",
attackNum : 5, //攻击力
run : function(){console.log("奔跑");},
attack : function(){console.log("攻击");},
defense : function(){console.log("防御");}
}
```
或者
```javascript
//2. 不去覆盖原本的prototype属性对象, 而是直接在上面添加属性
soldier.prototype.type = "士兵";
soldier.prototype.attackNum = 5;
soldier.prototype.run = function(){console.log("奔跑");};
soldier.prototype.attack = function(){console.log("攻击");};
soldier.prototype.defense = function(){console.log("防御");};
```

View File

@ -0,0 +1,130 @@
---
title: 扩展运算符的用法
date: 2018-4-28 19:53:47
tags:
- JavaScript
- ECMAScript6
categories:
- JavaScript
---
ES6标准新加入的`扩展运算符(Spread Operator)` , 可以使JS代码变得更加简洁 , 编写起来更加灵活
<!-- more -->
#### 不使用apply去调用函数
如果现在有一个数组 , 需要将它当中的每个元素逐个对应到一个函数的参数进行传入的话 , 在以往需要这样写
```javascript
function func(a,b,c) {
console.log(a+b+c);
}
var args = [10,2,60];
func.apply(null, args);
```
如果使用扩展运算符 , 只需按照如下方式调用即可
```javascript
func(...args);
```
#### 合并数组
以往合并数组的方式通常是使用`concat`方法
或者也可以遍历数组 , 逐个push或者unshift到另一个数组当中
现在有了扩展运算符 , 就可以运用更加灵活简洁的方式了
```javascript
var arr1 = [10,30,"ab"];
var arr2 = [true,"pp"];
arr1.unshift(...arr2);
arr1.push(...arr2);
```
或者也可以在数组内部进行合并
```javascript
var arr1 = [10,20];
var arr2 = [1, ...arr1, 100];
```
语法更简洁了 , 而且可以灵活控制位置
#### 复制数组
其实与上面的数组内合并是类似的
```javascript
var arr1 = [1,2,3];
var arr2 = [...arr1];
```
#### 将类数组转化为数组
在以前我们需要用`Array.prototype.slice`来讲类数组( 比如arguments )来转化为真正的数组对象
现在可以直接使用扩展运算符了
```javascript
var divArr = [...document.querySelectorAll("div")];
function func(...args) {
//等同于[...arguments]
console.log(args);
}
```
---
#### 解构赋值
所谓解构 , 其实就是分解数组或对象的结构 , 将其中的元素直接赋值给变量
##### 数组解构
比如通过不定参数来获取数组后面的尾随元素
```javascript
let [a,...b] = ['a2','b2','c2','d2'];
console.log(a); //a2
console.log(b); //['b2','c2','d2']
```
也可以在某些位置留空 , 跳过数组当中对应位置的元素
```javascript
let [a,,b] = [1,2,3];
console.log(a);// 1
console.log(b);// 3
```
对于多维数组 , 也是同样适用的
```javascript
var [one,[[two],three]] = [1, [[2],3]];
```
数组结构同样可以适用于**生成器**
```javascript
function* fibs() {
var a = 0;
var b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth);
// 5
```
##### 对象解构
```
var obj = {name:"Sookie", age:18};
var {name : nameA} = obj;
console.log(nameA); //Sookie
```
当属性名与变量名一致的时候 , 也可以简写如下
```javascript
var {name} = obj;
```
与数组类似 , 多层嵌套的对象结构同样可以使用类似的结构分别赋值给对应的变量
```javascript
var complicatedObj = {
arrayProp: [
"Zapp",
{ second: "Brannigan" }
]
};
var { arrayProp: [first, { second }] } = complicatedObj;
```
> 当解构对象时 , 如果没有变量声明的标识符 ( var const let ) , 直接这样写会报错
> `{a} = {a:10}`
> 因为JS解析器会把 { 开始的语句解析为一个块语句
> `({a} = {a:10})`这样写就是正确的
我们可以给解构的结果添加一个默认值
如果解构的目标数组或者对象当中没有获取到对应的值 , 那么将使用这个默认值
( 不指定默认值将得到undefined )
```javascript
var [missing=true] = [];
var {msg="success"} = {};
```

View File

@ -0,0 +1,123 @@
---
title: 模块化编程(1)
date: 2018-5-6 20:15:26
tags:
- JavaScript
- 模块化
categories:
- JavaScript
---
#### 准备知识
> 网页中`<script>`标签
> + 如果是src引入一个文件的形式 , 加载这个文件的过程默认是同步的 , 如果引入了多个文件 , 则按照声明的顺序进行加载运行 , 前面的文件尚未加载运行完毕 , 不会加载后面的文件
<!-- more -->
> + 从src所指定的地址获取文件的动作是异步的 , 如果前面有未加载完成的脚本 , 那么该脚本即使已经获取到 , 也不会先加载运行
> + 给script标签添加`async="async"`属性 , 可以指定这个脚本的加载运行以异步方式执行
> + 在IE当中 , 使用`defer`指定异步方式执行脚本
例如 :
```xml
<script src="http://localhost:8080/test/demo1.js" ></script>
<script src="http://localhost:8080/test/demo2.js" ></script>
```
如果不加 defer async="async" 的情况下 , demo1.js 和 demo2.js 会顺序加载运行 , 假如demo2.js当中有对demo1.js的依赖 , 那么这种情况下不会出现问题
```xml
<script src="http://localhost:8080/test/demo1.js" defer async="async"></script>
<script src="http://localhost:8080/test/demo2.js" ></script>
```
如果demo1.js由于文件庞大 网络不畅等原因加载缓慢 , 我们可以给demo1.js添加异步加载标识
这种情况下 , demo2.js将**有可能**先于demo1.js加载运行 , 如果存在依赖 , 将**有可能**报错
---
### 模块化的实现方式
JS在很长一段时间内不是一种支持模块化编程的语言
虽然ES6正式支持了类和模块 , 在nodejs环境下的模块化编程没有问题
但是能够在浏览器环境中普遍使用还需要很长时间
所以需要采用其他的方法去模拟实现模块化
#### 原始写法
```javascript
function func1() {
//...
}
function func2() {
//...
}
```
将若干个函数简单堆积在一起 , 可以认为是一个模块
但是这种模式是将这些函数对象作为全局变量
如果函数很多 , 会造成全局变量的污染
与其他模块也可能发生变量的命名冲突问题
#### 对象写法
```javascript
var module1 = {
_count : 0,
up : function(){
_count ++;
},
down : function(){
_count --;
}
};
```
这种写法只讲一个对象作为全局变量 , 一定程度上避免了全局变量的污染
但是这种写法会暴露所有的模块成员 , 无法做到只有模块内部方法能够访问的局部变量
#### 立即执行函数写法
```javascript
var module1 = (function(){
var _count = 0;
return {
up : function(){
_count ++;
},
down : function(){
_count --;
}
};
})();
```
这种写法 , 把模块内需要保护的变量作为函数的局部变量 , 在外部无法直接去访问
可以算是相对科学的写法了 , 之后的几种都是对这种写法的改造和扩展
#### 放大模式
如果一个模块需要分成几个部分写在不同的地方 , 或者说一个模块需要去继承另一个模块 , 就需要使用放大模式
```javascript
var module1 = (function(mod){
mod.biz = function(){
console.log("我是另外加入的方法");
}
return mod;
})(module1);
```
#### 宽放大模式
上面的放大模式显然需要保证模块加载的先后顺序
如果传入的module1是undefined , 肯定会报错
然而部署在外网的项目 , 如果严格限制了模块的加载顺序 , 很容易造成页面加载缓慢
为了适应这种情况 , 可以采用宽放大模式
```javascript
var module1 = (function(mod){
mod.biz = function(){
console.log("我是另外加入的方法");
}
return mod;
})(module1 || {});
```
module1模块的两部分都采取这种写法 , 加载的先后就无所谓了
先加载的会在空对象上添加方法
#### 输入全局变量
一个模块最好能够是独立的 , 不与其他模块中的部分直接进行交互
但是如果在模块内需要其他模块产生的对象
可以显式地将这些对象输入到模块内部
比如需要用到jQuery的对象
```javascript
var module1 = (function($){
//...
})(jQuery);
```

View File

@ -0,0 +1,173 @@
---
title: 模块化编程(2)
date: 2018-5-6 20:19:42
tags:
- JavaScript
- 模块化
categories:
- JavaScript
---
模块存在的价值是为了能够更方便地复用代码 , 更加有利于功能的封装
但是如果要实现的话 , 就必须要求每个人都按照同样的方式去编写模块
目前通行的JavaScript模块规范有两种 , 分别是`CommonJS``AMD`
<!-- more -->
### CommonJS
从node.js出现之后 , 将JavaScript用于服务器编程
同时也标志着JS的模块化编程正式诞生
在网页环境下 , 没有模块也不是特别大的问题 , 因为网页程序的复杂性和规模都相对有限 , 但是在服务器端就必须要有模块 , 否则服务器端程序就会难以扩展和维护
nodejs的模块系统 , 就是按照CommonJS规范实现的
这个规范当中 , 有一个全局方法 `require` , 用于加载模块
```javascript
var mod1 = require("module1");
mod1.biz();
```
### AMD
有了在服务端可用的模块化编程方式 , 大家很自然就想要在客户端也能实现的方式 , 并且最好能够兼容
这样同样的一个模块的代码 , 不用修改任何内容就可以同时应用于服务端和客户端
但是如果直接套用在nodejs当中的实现方式 , 就会存在线程阻塞的问题
也就是必须要等require方法执行完毕 , 加载运行这个模块的代码之后 , 后面的代码才会被执行
对于服务端来说 , 这并不是什么问题 , 因为代码都在本地 , 不可能出现长期阻塞的问题
但是对于客户端来说 , js文件需要发送http请求去获取 , 所以同步加载的方式就十分影响性能了
AMD ( Asynchronous Module Definition `异步模块定义` ) , 这种规范的要求是采用异步方式加载模块
形式如下 :
```javascript
require(["module1"], function(mod) {
mod.biz();
});
```
规范只是约定一种形式 , 具体要应用的话需要有对应的库来实现
这里通过`require.js`来介绍
### require.js
[require.js官网](http://www.requirejs.cn/)
require.js主要解决两个问题
1. 实现js文件的异步加载 , 避免网页失去响应
2. 管理模块之间的依赖性 , 便于代码的编写和维护
使用require.js需要指定一个主模块 , 以及在主模块中可以去引入若干个子模块 , 主模块相当于是程序执行的入口
( 如果没有子模块的话就不需要require.js了 )
目录结构
![require demo](/images/JavaScript/modules.png)
#### data-main入口点
```xml
<script src="js/require.js" data-main="js/main"></script>
```
js目录下的main.js就是作为主模块 , 后面的.js可以省略
#### 子模块的编写方式
require.js加载的模块 , 采用AMD规范 , 也就是说模块必须按照AMD的规范来写
模块必须采用特定的define函数来定义
```javascript
//module1.js
define(function(){
return {
biz : function(){
console.log("子模块的方法");
}
}
})
```
这个函数返回的对象就是这个模块需要暴露出的对象
如果该模块需要依赖其他模块
```javascript
//module2.js
define(["module1"],function(mod1){
function biz2() {
return mod1.biz() + "ok";
}
return {
biz : biz2
}
});
```
#### 主模块的编写方式
```javascript
//main.js
require(["modules/module1","modules/module2"],
function(mod1,mod2){
//mod1和mod2分别是在子模块中暴露出的对象
});
```
require函数接受两个参数 , 第一个参数是子模块的相对路径和名称 ( 如果在同一个路径下可以不加路径 ) , **必须是一个数组**
第二个参数是子模块加载完成之后执行的回调函数
<font color='blueviolet'>更加灵活的自定义加载</font>
模块的引入采用的是子模块的js文件与主模块文件的相对位置
如果要加在的子模块较多 , 这么相对路径就需要加在每个子模块的前面
为了更清晰一些 , 我们可以采用下面的方式
```javascript
require.config({
//注意这里的baseUrl是相对于引入主模块的页面的路径
//页面是 /require_demo/test.html
//子模块位于 /require_demo/other/module3.js
baseUrl : "./other/",
paths : {
mod3 : "module3",
mod4 : "module4"
}
});
//上面的代码相当于对子模块的路径创建了映射
//下面才是真正引入模块
require(["mod3","mod4"], function(mod3, mod4){
mod3.biz3();
mod4.biz4();
});
```
上面的写法其实就等价于
```javascript
require(["../other/module3","../other/module4"],
function(mod3, mod4){
mod3.biz3();
mod4.biz4();
});
```
> paths里面也可以直接使用完整的网络URL地址
#### 加载非规范的模块
采用上面的方式去加载的子模块 , 模块当中必须按照AMD的规范去写
如果子模块本身并不符合这个要求
在不方便修改子模块的情况下 , 我们可以采用如下的方式去加载
```javascript
require.config({
shim : {
underscore : {
exports : "_"
}
},
paths : { //这部分并没有什么差别
underscore : "./plugins/underscore"
}
});
require(["underscore"], function(_) {
//测试代码
_.each([1,10,20],console.log);
});
```
未按照AMD规范编写的模块 , 通常采用的是暴露出一个变量放入到window当中作为全局变量 , 比如underscore这个库 , 暴露出的就是`_`
##### 模块依赖性声明
```javascript
shim : {
"jquery.scroll" : {
deps : ["jquery"],
exports : "jQuery.fn.scroll"
}
}
```

View File

@ -0,0 +1,78 @@
---
title: 生成器函数
date: 2018-4-29 19:57:44
tags:
- JavaScript
- ECMAScript6
categories:
- JavaScript
---
`function *`声明可以用于定义一个生成器函数 , 它返回一个Generator对象
<!-- more -->
语法
<pre>
function * name([param[,param[,...param]]])
{ statement }
</pre>
生成器是一种可以从中退出后重新进入的函数
函数内部的局部变量会在每次执行后被保存 , 下次进入可以继续使用
调用生成器函数并不会执行它的主体 , 而是返回对应的一个Generator对象
当这个对象的`next()`方法被调用时 , 生成器函数的主体会被执行至第一个`yield`表达式 , 该表达式定义了生成器本次生成的值
next()方法返回一个对象
包含**value**属性 , 是本次生成的值
以及**done**属性 , 表示生成器是否已经产出了最后一个值 ( 产出最后的值之后 , 调用next返回的对象当中value都是undefined )
> Generator对象中的方法
> + Generator.prototype.next()
> 返回一个由yield表达式生成的值
> + Generator.prototype.return( [val] )
> 返回给定的值并结束生成器
> + Generator.prototype.throw( [msg] )
> 向生成器抛出一个错误
```javascript
function * num() {
var a = 0;
while(a < 3) {
yield a;
a++;
}
}
var gen = num();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); //undefined
```
---
#### yield *
可以将生成器中需要生成的值委派至另一个生成器
```javascript
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i){
yield i;
yield* anotherGenerator(i);
yield i + 10;
}
var gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
```
> 生成器函数在浏览器当中的兼容性不佳 , 更推荐在nodejs当中使用 , 而不是在页面脚本当中使用
> ![generate function](/images/JavaScript/generate_function1.png)
> ![generate function](/images/JavaScript/generate_function2.png)

View File

@ -0,0 +1,91 @@
---
title: 面向对象编程
date: 2017-12-20 20:26:42
tags:
- JavaScript
categories:
- JavaScript
---
面向对象的两个基本概念
1. **类** : 是对象的类型模板 , 是一种抽象 , 并不表示实体
2. **实例** : 是根据类创建的对象 , 表示某个具体的事物
<!-- more -->
类和实例是大多数面向对象编程语言的基本概念。
不过在JavaScript中这个概念需要改一改。JavaScript不区分类和实例的概念而是通过`原型prototype`来实现面向对象编程。
既然没有类的概念 , 我们就需要用某个对象来模拟一个类了
```javascript
var person = {
name : "Unnamed",
run : function(){
console.log(this.name + "is running...");
}
};
//现在我们可以把person来当做一个原型对象, 来创建一个具体的"人"
var xiaoming = {
name : "小明"
};
xiaoming.__proto__ = person;
```
JavaScript当中的原型链 , 既可以模拟由类创建实例的关系
也可以模拟继承的关系
原型对象本身也可以有原型对象 , 从而构成`原型链`
默认原型对象是`Object`
当然在运行中也可以随时改变某个对象的原型对象
> 以上代码主要用于表示原型链的实际关系 , 实际编程当中最好不要直接去修改对象的`__proto__`
> `Object.create()`方法可以传入一个原型对象 , 用来构造出一个新对象
```javascript
var xiaoming = Object.create(person);
xiaoming.name = "小明";
//验证原型对象
xiaoming.__proto__ === person; //true
```
当我们访问某个对象的属性时 , 例如`obj.xxx`
js引擎会先在该对象上查找该属性 , 如果没找到 , 就会到原型对象上去找 , 也就是顺着原型链一直向上回溯 , 直到`Object.prototype`对象 , 如果还没有 , 那么就是undefined
除此之外 , 也可以使用`构造函数`来创建对象
构造函数本身和普通的函数没什么区别 , 只是用**new**关键字来调用 , 就会返回一个需要构建的对象
```javascript
function Student(){
this.name = "sookie";
this.age = 18;
this.say = function(){
alert("Hello");
}
}
var stu = new Student();
stu.say();
```
如果按照普通函数的调用方式执行调用 , 其中的this就表示window对象
那么其中代码的效果就是定义了几个全局属性
如果使用 new 来调用该函数 , 那么它绑定的this就指向新创建的对象
并默认返回this ( 不需要写return )
使用这种方式创建的对象还从原型上获得了一个 constructor 属性
指向这个构造函数本身
![constructor](/images/JavaScript/OOP1.png)
—> 另外这个对象中有一个 say 属性 , 它是一个函数
在这种情况下 , 这个函数是属于stu对象的 , 并不属于该对象的原型
如果使用原型创建了多个对象 , 其中就会有很多重复的函数
所以可以把这个函数放入到Student的prototype当中
```javascript
function Student(){
this.name = "sookie";
this.age = 18;
}
Student.prototype.say = function(){
alert("Hello");
}
var stu1 = new Student();
var stu2 = new Student();
console.log(stu1.say == stu2.say)
//true
```
![prototype](/images/JavaScript/OOP2.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB