1. 作用域变量
作用域就是变量的作用范围。也就是你声明一个变量以后,这个变量可以在什么场合下使用。以前的JavaScript只有全局作用域,和函数作用域。
1.1 var的问题
1.var 没有块级作用域,定义后在当前包中都都可以访问,如果变量名重复,就会覆盖前面定义的变量,并且很可以被他人修改。
if(true){
var a = "a"; //期望a是某一个值
}
console.log(a);
<br />2.var 在for循环标记变量共享,一般在循环中会使用的i会被共享,其本质也是由于没有块级作用域造成的<br />
for (var i = 0; i < 3; i++) {
setTimeout(function () {
alert(i);
}, 0);
}
// 结果就是 弹窗三次 3
for ( i = 0; i < 3; i++) {
setTimeout(function () {
alert(i);
}, 0);
}
// 结果就是 弹窗三次 0-2
1.2 块级作用域
在用var定义变量的时候,变量通过闭包进行隔离的,现在用了let,不仅仅可以通过闭包隔离,还可以增加了一些块级作用域隔离。块级作用用一组大括号定义一个快,使用let定义的变量字啊大括号的外面是访问不到的。
1.2.1 实现会计作用域
if(ture){
let name = 'wjh'
}
consloe.log('name'); // ReferenceError: name is not defined
1.2.2 不会污染全局对象
if(ture){
let name = 'wjh'
}
console.log(window.name); // undefined
1.2.3 for 循环中也可以使用i
// 嵌套循环不会相互影响
for (let i = 0; i < 3; i++) {
console.log("out", i);
for (let i = 0; i < 2; i++) {
console.log("in", i);
}
}
// 结果 out 0 in 0 in 1 out 1 in 0 in 1 out 2 in 0 in 1
1.2.4 重复定义会报错
if(ture){
let a = 1;
let a = 2; //Identifier 'a'
}
1.2.5 不存在变量的预解释
for(let i = 0;i<2;i++){
console.log('inner',i);
let i =100;
}
// 结果 i is not defined
1.2.6 闭包的新写法
;(function(){
})();
现在
{
}
2. 常量
使用 const 我们可以声明一个常量,一旦声明之后,就不可以更改。
2.1 常量不能重新赋值
const MY_NAME = 'wjw';
MY_NAME = 'wjw2';//Assignment to constant variable
2.2 变量可以改变
注意const限制的是不能给变量重新赋值,而变量的值本身是可以改变的,下面的操作是可以的
const names = ['wjw1'];
names.push('wjw2');
console.log(names);
2.3 不同的块级作用域可以多次定义
const A = "0";
{
const A = "A";
console.log(A)
}
{
const A = "B";
console.log(A)
}
console.log(A)
// 结果 A B 0
3. 解构
3.1 解构数组
解构意思就是分解一个东西的结构,可以用一种类似数组的方式定义N个变量,可以将一个数组中的值按照规则赋值过去。<br />
var [name,age]=['wjh',8];
console.log(name,age);
3.2 嵌套赋值
let [x,[y],z]=[1,[2.1]];
console.log(x,y,z);
let [x,[y,z]] = [1,[2.1,2.2]];
console.log(x,y,z);
let [json,arr,num] = [{name:'wjw'},[1,2],3];
console.log(json,arr,num);
// 1 2.1 undefined 1 2.1 2.2 { name: 'wjw' } [ 1, 2 ] 3
3.3 省略赋值
let [,,x]=[1,2,3]
console.log(x);
3.4 解构对象
对象也可以被解构
var obj = {name:'wjw',age:8};
//对象里的name属性的值会交给name这个变量,age的值会交给age这个变量
var {name,age} = obj
//对象里的name属性的值会交给myname这个变量,age的值会交给myage这个变量
let {name: myname, age: myage} = obj;
console.log(name,age,myname,myage);
3.5 默认值
在赋值和传参的时候可以使用默认值
let [a='a',b='b',c=new Error('C必须指定')] = [1, , 3];
console.log(a,b,c);
function ajax(options){
var method = options.method || "get";
var data = options.data || {};
}
function ajax(method='get',data){
console.log(arguments);
}
ajax({
method:'post',
data:{'name':'wjh'}
})
4. 字符串
4.1 模板字符串
模板字符串用反应号(数字1左边的那个建)包含,用${}
括起来<br /><br />
var name = 'wjw',age = 8;
let desc = `${name} is ${age} old!`;
console.log(desc);
//所有模板字符串的空格和换行,都是被保留的
var str = `<ul>
<li>a</li>
<li>b</li>
</ul>`;
console.log(str);
其中的变量会用变量的值替换掉
function replace(desc){
return desc.replace(/\$\{([^}]+)\}/g,function(matched,key){
return eval(key);
});
}
4.2 带标签的模板字符串
可以在模板字符串的前面添加一个标签,这个标签可以去处理模板字符串 标签其实就是一个函数,函数可以接收两个参数,一个是 strings 就是模板字符串里的每个部分的字符 还有一个参数可以使用rest的形式values,这个参数里面是模板字符串里的值。
var name = 'wjh',age = 8;
function desc(strings,...values){
console.log(strings,values);
}
desc`${name} is ${age} old!`;
字符串新方法
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
var s = 'wjh';
s.startsWith('w') // true
s.endsWith('h') // true
s.includes('j') // true
第二个参数,表示开始搜索的位置
var s = 'wjh';
console.log(s.startsWith('j',2)); // true
console.log(s.endsWith('j',2)); // true
console.log(s.includes('j',2)); // false
endsWith的行为与其他其他方法有所不同。它针对前n个字符,而其他方法是从第几位开始到字符串结束
4.4 repeat
repeat 方法返回一个新字符串,表示将原字符串重复n次。<br /><br />
'x'.repeat(3);
'x'.repeat(0);
5. 函数
5.1 默认参数
可以给定义的函数接收的参数设置默认的值 在执行这个函数的时候,如果不指定函数的参数的值,就会使用参数的这些默认的值。
function ajax(url,method='GET',dataType="json"){
console.log(url);
console.log(method);
console.log(dataType);
}
5.2 展开操作符
把...放在数组前面可以把一个数组进行展开,可以把一个函数而不需要使用apply
<br /><br />
//传入参数
let print = function(a,b,c){
console.log(a,b,c);
}
print([1,2,3]);
print(...[1,2,3]);
// 可以替代apply
var m1 = Math.max.apply(null, [8, 9, 4, 1]);
var m2 = Math.max(...[8, 9, 4, 1]);
// 可以替代concat
var arr1 = [1, 3];
var arr2 = [3, 5];
var arr3 = arr1.concat(arr2);
var arr4 = [...arr1, ...arr2];
console.log(arr3,arr4);
//类数组的转数组
function max(a,b,c) {
console.log(Math.max(...arguments));
}
max(1, 3, 4);
5.3 剩余操作符
剩余操作符可以把其余参数的值放在一个叫做b的数组里
let rest = function(a,...rest){
console.log(a,rest);
}
rest(1,2,3);
5.4 解构参数
let destruct = function({name,age}){
console.log(name,age);
}
destruct({name:'wjh',age:10})
5.6 箭头函数
箭头函数简化了函数的定义方式
[1,2,3].forEach(val=>console.log(val));
输入参数如果多于一个要用()包含,函数体如果有多条语句需要用{}包起来
箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。 正是因为它没有this,从而避免了this指向的问题。
var person = {
name:'wjh',
getName:function(){
- setTimeout(function(){console.log(this);},1000); //在浏览器执行的话this指向window
+ setTimeout(() => console.log(this),1000);//在浏览器执行的话this指向person
}
}
person.getName();
6. 数组的新方法
// 相同的阵列
var people = [
{
name : 'Casper' ,
like : '锅烧意面' ,
age : 18
},
{
name : 'Wang' ,
like : '炒面' ,
age : 24
},
{
name : 'Bobo' ,
like : '萝卜泥' ,
age : 1
},
{
name : '卤蛋' ,
like : '萝卜泥' ,
age : 3
}
];
6.1 Array.prototype.filter()
filter() 会回传一个阵列,其条件是return 后方为true 的物件,很适合用在搜寻符合条件的资料。
var filterEmpty = people.filter( function ( item, index, array ) {
});
console.log(filterEmpty); //没有条件,会是一个空阵列
var filterAgeThan5 = people.filter( function ( item, index, array ) {
return item.age > 5 ; //取得大于五岁的 如果这边符合条件 只要为ture即可
});
console .log(filterAgeThan5); // Casper, Wang这两个物件
6.2 Array.prototype.find()
find()与filter()很像,但find() 只会回传一次值,且是第一次为true的值。
var findEmpty = people.find( function ( item, index, array ) {
});
console .log(findEmpty); //没有条件,会是undefined
var findAgeThan5 = people.find( function ( item, index, array ) {
return item.age > 5 ; //取得大于五岁的
});
console .log(findAgeThan5); //虽然答案有两个,但只会回传Casper这一个物件
var findLike = people.find( function ( item, index, array ) {
return item.like === '萝卜泥' ; //取得阵列like === '萝卜泥'
});
console .log(findLike); //虽然答案有两个,但只会回传第一个Bobo物件
6.3 Array.prototype.forEach()
forEach 是这几个阵列函式最单纯的一个,不会额外回传值,只单纯执行每个阵列内的物件或值。
var forEachIt = people.forEach( function ( item, index, array ) {
console .log(item, index, array); //物件,索引,全部阵列
return item; // forEach没在return的,所以这边写了也没用
});
console .log(forEachIt); // undefined
people.forEach( function ( item, index, array ) {
item.age = item.age + 1 ; // forEach就如同for,不过写法更容易
});
console .log(people); //全部age + 1
6.4 Array.prototype.map()
使用map() 时他需要回传一个值,他会透过函式内所回传的值组合成一个阵列。
如果不回传则是 undefined
回传数量等于原始阵列的长度
这很适合将原始的变数运算后重新组合一个新的阵列。
var mapEmpty = people.map( function ( item, index, array ) {
});
console .log(mapEmpty); // [undefined, undefined, undefined, undefined]
var mapAgeThan5 = people.map( function ( item, index, array ) {
return item.age > 5 ; //比较大于五岁的
});
console .log(mapAgeThan5); // [true, true, false, false]
var mapAgeThan5_2 = people.map( function ( item, index, array ) {
// 错误示范
if (item.age > 5 ) {
return item; //回传大于五岁的
}
return false ; //别以为空的或是false就不会回传
});
console .log(mapAgeThan5_2); // [{name: 'Casper'...}, {name: 'Wang'...}, false, false]
var mapEat = people.map( function ( item, index, array ) {
if (item.like !== '萝卜泥' ) {
return ` ${item.like}好吃` ;
} else {
return ` ${item.like}不好吃` ;
}
});
console .log(mapEat); // ["锅烧意面好吃", "炒面好吃", "萝卜泥不好吃", "萝卜泥不好吃"]
6.5 Array.prototype.every()
every()可以检查所有的阵列是否符合条件,这仅会回传一个值trueor false,可以用来检查阵列中的内容是否符合特定条件。
var ans = array.every( function ( item, index, array ) {
console .log(item, index, array); //物件,索引,全部阵列
return item.age > 10 //当全部age大于10才能回传true
});
console .log(ans); // false:只要有部分不符合,则为false
var ans2 = array.every( function ( item, index, array ) {
return item.age < 25
});
console .log(ans2); // true:全部age都小于25
6.6 Array.prototype.some()
some() 与every() 非常接近,都是回传true or false,差异仅在every() 需完全符合,some() 仅需要部分符合。
var ans = people.some( function ( item, index, array ) {
return item.age > 10 //当全部age大于10才能回传true
});
console .log(ans); // true:只要有部分符合,则为true
var ans2 = people.some( function ( item, index, array ) {
return item.age < 25
});
console .log(ans2); // true:只要有部分符合,则为true
var ans2 = people.some( function ( item, index, array ) {
return item.age > 25
});
console .log(ans2); // false:全部都不符合则为false
6.7 Array.prototype.reduce()
reduce() 和其他几个差异就很大了,他可以与前一个回传的值再次作运算,参数包含以下:
accumulator: 前一个参数,如果是第一个阵列的话,值是以另外传入或初始化的值
currentValue: 当前变数
currentIndex: 当前索引
array: 全部阵列
var reduceEmpty = people.reduce( function ( accumulator, currentValue, currentIndex, array ) {
});
console .log(reduceEmpty); //没有条件,会是undefined
var reducePlus = people.reduce( function ( accumulator, currentValue, currentIndex, array ) {
// 分别为前一个回传值, 目前值, 当前索引值
console .log(accumulator, currentValue, currentIndex);
return accumulator + currentValue.age; //与前一个值相加
}, 0 ); //传入初始化值为0
console .log(reducePlus); //总和为46
var reducePlus = people.reduce( function ( accumulator, currentValue, currentIndex, array ) {
console .log( 'reduce' , accumulator, currentValue, currentIndex)
return Math .max( accumulator, currentValue.age ); //与前一个值比较哪个大
}, 0 );
console .log(reducePlus); //最大值为24
7. 对象
7.1 对象字面量
如果你想在对象里添加跟变量名一样的属性,并且属性的值就是变量表示的值就可以直接在对象里加上这些属性
let name = 'wjh';
let age = 8;
let getName = function(){
console.log(this.name)
}
let person ={
name,
age,
getName
}
person.getName();
7.2 Object.is
对比两个值是否相等
console.log(Object.is(NaN,NaN));
7.3 Object.assign
把多个对象的属性复制到一个对象中,第一个参数是复制的对象,从第二个参数开始往后,都是复制的源对象
var nameObj = {name:'wjh'}
var ageObj = {age:8};
var obj = {};
Object.assign(obj,nameObj,ageObj);
console.log(obj);
//克隆对象
function clone(obj){
return Object.assgin({},obj);
}
7.4 Object.setPrototypeOf
将一个指定的对象原型设置为另一个对象或者null
var obj1 = {name:'wjh1'};
var obj2 = {name:'wjh2'};
var obj = {};
Object.setPrototypeOf(obj,obj1);
console.log(obj.name);
console.log(Object.getPrototypeOf(obj));
Object.setProtoypeOF(obj,obj2);
console.log(obj.name);
console.log(Object.getPrototypeOf(obj));
7.5 proto
直接对象表达式中设置prototype
var obj1 = {name:'wjh'};
var obj3 = {
_proto_:obj1
}
console.log(obj3.name);
console.log(Object.getPrototypeOf(obj3));
7.6 super
通过super可以调用protype上的属性或方法
let person ={
eat(){
return 'milk';
}
}
let student = {
_proto_:person,
eat(){
return super.eat()+'bead'
}
}
console.log(student.eat());
8. 类
8.1 class
使用 class 这个关键词定义一个类,基于这个创建实例以后就会创建 constructor 方法,此方法可以用来初始化
class Person{
constructor(name){
this.name = name;
}
getName(){
console.log(this.name)
}
}
let person = new Person('wjh');
person.getName();
8.2 get与set
getter 可以用来获取属性,setter 可以去设置属性
class Person {
constructor(){
this.hobbies = [];
}
set hobby(hobby){
this.hobbies.push(hobby);
}
get hobby(){
return this.hobbies;
}
}
let person = new Person();
person.hobby = 'aa';
person.hobby = 'bb';
console.log(person.hobby)
8.3 静态方法-static
在类里面添加静态的方法可以使用static 这个关键词,静态方法就是不需要实例化类就能使用的方法
class Person{
static add(a,b){
return a+b;
}
}
console.log(Person.add(1,x));
8.4 继承extends
一个类可以继承其他的类里的东西
class Person{
constructor(name){
this.name = name;
}
}
class Teacher extends Person{
constructor(name,age){
super(name);
this.age = age;
}
}
var teacher = Teacher('wjh',8);
console.log(teacher.name,teacher.age)
9. 生成器(Generator)与迭代器(Iterator)
Generator 是一个特殊的函数,执行它会返回一个Iterator对象。通过遍历迭代器,Generator函数运行后悔返回遍历器对象,而不是函数的返回值。
9.1 Iterators模拟
迭代器有一个next方法,每次执行的时候会返回一个对象 对象里面有两个函数,一个是value表示返回的值,还有就是布尔值done,表示是迭代完成
function buy(books){
let i = 0;
return{
next(){
let done = i ===books.length;
let value = !done ? books[i++]:undefined;
return {
value:value,
done:done
}
}
}
}
let iterators = buy(['js','html']);
var curr;
do{
curr = iterators.next();
console.log(curr);
}while(!curr.done);
9.3 Generators
生成器用于创建迭代器
function* buy(boos){
for(var i=0;i<boos.length;i++){
yield books[i];
}
}
let buying = buy(['js','html]);
var curr;
do {
curr = buying.next();
console.log(curr);
}while(!curr.done);
10. 集合
10.1 Set
一个Set是一堆东西的集合,Set 有点像数组,不过跟数组不一样的是,Set里面不能有重复的内容
var books = new Set();
books.add('js');
books.add('js');//添加重复元素的集合元素个数不会变化
books.add('html');
books.forEach(function(book){ // 循环集合
console.log(book);
})
console.log(book.size);//集合中元数的个数
console.log(books.has('js'));//判断集合是否有此元素
books.delete('js');
console.log(books.size);
console.log(books.has('js'));
books.clear();//清空set
console.log(books.size);
10.2 Map
可以使用Map来组织这个名值对的数据
var books = new Map();
books.set('js',{name:'js'});//向map中添加元素
books.set('html',{name:'html'});
console.log(books.size);//查看集合中的元素
console.log(books.get('js'));//通过key获取值
books.delete('js');//执行key删除元素
console.log(books.has('js'));//判断map中有没有key
book.forEach((value,key)=>{
console.log(key+'='+value);
})
books.clear();//清空map
11. 模块
可以根据应用的需求吧代码分成不同的模块,每个模块里可以导出它需要让其他模块使用的东西,在其他模块里面可以导入这些模块,导出的东西。
11.1 模块
在浏览器中使用模块需要借助 导出
export var name = 'wjh';
export var age = 8;
导入
//import {name,age} from './school.js';
import * as school from './school.js';
console.log(school.name,school.age);
在页面中引用
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script type="module" src="index.js"></script>
11.2 重命名
导出时重命名
function say(){
console.log('say');
}
export {say as say2};
导入时重命名
import {say2 as say3} from './school.js'
11.3 默认导出
每个模块都可以有一个默认要导出的东西
export default function say(){
console.log('say')
}
导入
import say from './school.js'
11.4 深度克隆
var parent = {
age: 5,
hobby: [1, 2, 3],
home: {city: '北京'},
};
var child = extendDeep(parent);
child.age = 6;
child.hobby.push('4');
child.home.city = '广东';
console.log('child ', child); //[1, 2, 3, 4]
console.log('parent ', parent);
function extend(parent) {
let child;
if (Object.prototype.toString.call(parent) == '[object Object]') {
child = {};
for (let key in parent) {
child[key] = extend(parent[key])
}
} else if (Object.prototype.toString.call(parent) == '[object Array]') {
child = parent.map(item => extend(item));
} else {
return parent;
}
return child;
}
function extendDeep(parent, child) {
child = child || {};
for (var key in parent) {
if (typeof parent[key] === "object") {
child[key] = (Object.prototype.toString.call(parent[key]) === "[object Array]") ? [] : {};
extendDeep(parent[key], child[key]);
} else {
child[key] = parent[key];
}
}
return child;
}
12.JavaScript(ES6) 中条件语句
12.1 使用 Array.includes 来处理多个条件
function test(fruit) {
if (fruit == 'apple' || fruit == 'strawberry') {
console.log('red');
}
}
优化变成 ->>
function test(fruit) {
// 条件提取到数组中
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
if (redFruits.includes(fruit)) {
console.log('red');
}
}
12.2 减少嵌套,提前使用 return 语句
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// 条件 1:fruit 必须有值
if (fruit) {
// 条件 2:必须为红色
if (redFruits.includes(fruit)) {
console.log('red');
// 条件 3:数量必须大于 10
if (quantity > 10) {
console.log('big quantity');
}
}
} else {
throw new Error('No fruit!');
}
}
// 测试结果
test(null); // 抛出错误:No fruits
test('apple'); // 打印:red
test('apple', 20); // 打印:red,big quantity
优化
/* 在发现无效条件时提前 return */
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// 条件 1:提前抛出错误
if (!fruit) throw new Error('No fruit!');
// 条件2:必须为红色
if (redFruits.includes(fruit)) {
console.log('red');
// 条件 3:数量必须大于 10
if (quantity > 10) {
console.log('big quantity');
}
}
}
为了减少一个嵌套层级,优化编码风格
/* 在发现无效条件时提前 return */
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
if (!fruit) throw new Error('No fruit!'); // 条件 1:提前抛出错误
if (!redFruits.includes(fruit)) return; // 条件 2:当 fruit 不是红色的时候,提前 return
console.log('red');
// 条件 3:必须是大量存在
if (quantity > 10) {
console.log('big quantity');
}
}
12.3.使用函数的默认参数 和 解构
function test(fruit, quantity) {
if (!fruit) return;
const q = quantity || 1; // 如果没有提供 quantity 参数,则默认为 1
console.log(`We have ${q} ${fruit}!`);
}
// 测试结果
test('banana'); // We have 1 banana!
test('apple', 2); // We have 2 apple!
但是q在这边不直观所有优化
function test(fruit, quantity = 1) { // i如果没有提供 quantity 参数,则默认为 1
if (!fruit) return;
console.log(`We have ${quantity} ${fruit}!`);
}
// 测试结果
test('banana'); // We have 1 banana!
test('apple', 2); // We have 2 apple!
但是这边 也可能是个对象
// 解构 —— 只获得 name 属性
// 参数默认分配空对象 {}
function test({name} = {}) {
console.log (name || 'unknown');
}
//测试结果
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple
12.4 选择 Map / Object 字面量,而不是Switch语句
function test(color) {
// 使用 switch case 语句,根据颜色找出对应的水果
switch (color) {
case 'red':
return ['apple', 'strawberry'];
case 'yellow':
return ['banana', 'pineapple'];
case 'purple':
return ['grape', 'plum'];
default:
return [];
}
}
//测试结果
test(null); // []
test('yellow'); // ['banana', 'pineapple']
这边建议使用对象,更加清晰
// 使用对象字面量,根据颜色找出对应的水果
const fruitColor = {
red: ['apple', 'strawberry'],
yellow: ['banana', 'pineapple'],
purple: ['grape', 'plum']
};
function test(color) {
return fruitColor[color] || [];
}
但是这边是很有可能为网络数据,无法判断red这样的变量,那么就用arry.filter 来过滤
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'strawberry', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'pineapple', color: 'yellow' },
{ name: 'grape', color: 'purple' },
{ name: 'plum', color: 'purple' }
];
function test(color) {
// 使用 Array filter ,根据颜色找出对应的水果
return fruits.filter(f => f.color == color);
}
12.5 使用 Array.every 和 Array.some 来处理全部/部分满足条件
我们想检查所有水果是否都是红色的
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
let isAllRed = true;
// 条件:所有的水果都必须是红色
for (let f of fruits) {
if (!isAllRed) break;
isAllRed = (f.color == 'red');
}
console.log(isAllRed); // false
}
使用 arry.every来过滤
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
// 条件:简短方式,所有的水果都必须是红色
const isAllRed = fruits.every(f => f.color == 'red');
console.log(isAllRed); // false
}
如果我们想要检查是否有至少一个水果是红色的,我们可以使用 Array.some 仅用一行代码就实现出来
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
// 条件:是否存在红色的水果
const isAnyRed = fruits.some(f => f.color == 'red');
console.log(isAnyRed); // true
}
13 promise
13.1 异步回调
13.1.1 回调地狱
在需要多个操作的时间,会导致多个回调函数嵌套,导致代码不够直观,就常说的回调地狱
13.1.2 并行结果
如果几个异步操作之间并没有前后顺序之分,但需要等多个异步完成操作完成后才能执行后续的任务,无法实现并行节约时间
13.2 Promise
promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。什么时间会用到过一段时间?答案是异步操作,异步是指可能比较长时间才有结果的才做,例如网络请求、读取本地文件等
13.3 Promise的三种状态
- Pending Promise对象势力创建时候的初始化状态
- Fulfilled 可以理解为成功的状态
- Rejected 可以理解为失败的状态
then方法就是用来指定Promise 对象的状态改变时确定执行的操作,resolve时执行第一个函数(onFulfilled),reject时执行第二函数(onRejected)
13.4 构造一个Promise
13.4.1 使用Promise
let promise = new Promise((resolve,reject)=>{
setTimeout(()=>{
if(Math.random()>0.5)
resolve('This is resolve!')
else
reject('This is reject')
},1000);
});
promise.then(Fulfilled,Rejected)
- 构造一个Promise实例需要给Promise构造函数传入一个函数
- 传入的函数需要有两个形参,两个形参都是function类型的参数。
- 第一个形参运行后会让Promise实例处于resolve状态,所以我们一般给第一个形参命名为resolve,使 Promise对象的状态改变成成功,同时传递一个参数用于后续成功后的操作
- 第一个形参运行后悔让Promise实例处于reject状态,所以我们一般给第一个形参命名为reject,将Promise对象的状态改变为失败,同事将错误的信息传递到后续错误处理的操作
13.4.2 es5模拟Promise
function Promise(fn){
this.success(data);
},(error)=>{
this.error();
}
Promise.prtotype.resolve = function (data){
this.success(data);
}
Promise.prototype.then = function (success,error){
this.success = success;
this.error = error;
}
13.4.3 es5模拟Promise
class Promise{
constructor(fn){
fn((data)=>{
this.success(data);
},(error)=>{
this.error();
})
}
resolve(data){
this.success(data);
}
reject(error){
this.error(error);
}
then(success,error){
this.success = success;
this.error = error;
console.log(this);
}
}
13.5 promise 做为函数的返回值
function ajaxPromise(queryUrl){
return new Promise((resolve,reject)=>{
xhr.open('GET',queryUrl,ture);
xhr.send(null);
xhr.onreadystatechange = () =>{
if(xhr.readyState === 4 ){
if(xhr.status === 200){
resolve(xhr.responseText);
}else{
reject(xhr.responseText);
}
}
}
})
}
ajaxPromise('http://www.baidu.com')
.then((value)=>{
console.log(value);
})
.catch((err)=>{
console.error(err);
});
13.6 promise的链式调用
- 每次调用返回的都是一个新的Promise实例
- 链式调用的参数通过返回值传递
then 可以使用链式调用的写法原因在于,每一次执行该方法时总是会返回一个 Promise 对象
readFile('1.txt').then(function(data){
console.log(data);
}).then(function (data){
console.log(data);
return readFile(data);
}).then(function (data){
console.log(data);
}).catch(function (err){
console.log(err);
})
13.7 promise API
13.7.1 Promise.all
- 参数:接受一个数组,数组内都是Promise实例
- 返回值: 返回一个 promise 实例,这个promise 实例的状态转移取决于参数的 promise实例的状态变化。当参数处于resolve状态时,返回resolve状态。如果参数中任意一个实例处于reject状态,返回的promise实例变为reject状态。
Promise.all([p1,p2]).then(function(result){
console.log(result); //[ '2.txt', '2' ]
})
不管两个promise谁先完成,Promise.all 方法会按照数组里面的顺序将结果返回
13.7.2 Promise.race
- 参数:接受一个数组,数组内都是Promise实例
- 返回值: 返回一个 promise 实例,这个promise 实例的状态转移取决于参数的 promise实例的状态变化。当参数处于resolve状态时,返回resolve状态。如果参数中任意一个实例处于reject状态,返回的promise实例变为reject状态。
Promise.race([p1,p2]).then(function(result){
console.log(result); //[ '2.txt', '2' ]
})
13.7.3 Promise.resolve
返回一个Promise 实例,这个实例处于resolve状态。<br />根据传入的参数不同有不同的功能:
- 值(对象、数组、字符串等):作为resolve传递出去的值
- Promise 实例 : 原封不动返回
Promise.reject
返回一个Promise实例,这个实例处于reject状态
- 参数一般就是抛出的错误信息。
13.8 q
Q是一个在Javascrip中实现promise的模块
13.8.1 q的基本用法
var Q = require('q');
var fs = require('fs');
function read(filename){
var deferred = Q.defer();
fs.readFile(filename,'utf8',function)(err,data){
if(err){
deferred.reject(err);
}else{
deferred.resolve(data);
}
});
}
read('1.txt1').then(function(data){
console.log(data);
},funtcion(error){
console.error(error);
})
13.8.2 q的简单实现
module.exports = {
defer(){
var _success,_error;
return {
resolve(data){
_success(data);
},
reject(err){
_error(err);
},
promise:{
then(success,error){
_success = success;
_error = error;
}
}
}
}
}
13.8.3 q的实现
var defer = function () {
var pending = [], value;
return {
resolve: function (_value) {
if (pending) {
value = _value;
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
callback(value);
}
pending = undefined;
}
},
promise: {
then: function (callback) {
if (pending) {
pending.push(callback);
} else {
callback(value);
}
}
}
};
};
13.9 bluebird
实现 promise 标准的库是功能最全,速度最快的一个库
13.9.1 bluebird经典使用
var Promise = require('./bluebird');
var readFile = Promise.promisify(require("fs").readFile);
readFile("1.txt", "utf8").then(function(contents) {
console.log(contents);
})
var fs = Promise.promisifyAll(require("fs"));
fs.readFileAsync("1.txt", "utf8").then(function (contents) {
console.log(contents);
})
13.9.2 bluebird简单实现
module.exports = {
promisify(fn){
return function () {
var args = Array.from(arguments);
return new Promise(function (resolve, reject) {
fn.apply(null, args.concat(function (err) {
if (err) {
reject(err);
} else {
resolve(arguments[1])
}
}));
})
}
},
promisifyAll(obj){
for(var attr in obj){
if(obj.hasOwnProperty(attr) && typeof obj[attr] =='function'){
obj[attr+'Async'] = this.promisify(obj[attr]);
}
}
return obj;
}
}
13.10 动画
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>move</title>
<style>
.square{
width:40px;
height:40px;
border-radius: 50%;
}
.square1{
background-color: red;
}
.square2{
background-color: yellow;
}
.square3{
background-color: blue;
}
</style>
</head>
<body>
<div class="square square1" style="margin-left: 0"></div>
<div class="square square2" style="margin-left: 0"></div>
<div class="square square3" style="margin-left: 0"></div>
</body>
<script>
var square1 = document.querySelector('.square1');
var square2 = document.querySelector('.square2');
var square3 = document.querySelector('.square3');
/*function move(element,target,resolve){
let timer = setInterval(function(){
var marginLeft = parseInt(element.style.marginLeft, 10);
if(marginLeft == target){
resolve();
}else{
element.style.marginLeft = ++marginLeft+'px';
}
},13);
}*/
function move(element,target,resolve){
let current = 0;
let timer = setInterval(function(){
element.style.transform=`translateX(${++current}px)`;
if(current>target){
clearInterval(timer);
resolve();
};
},13);
}
function animate(element,target){
return new Promise(function(resolve,reject){
move(element,target,resolve);
});
}
animate(square1,100)
.then(function(){
return animate(square2,100);
})
.then(function(){
return animate(square3,100);
});
</script>
</html>
13.11. co
13.11.1 co初体验
let fs = require('fs');
function getNumber(){
return new Promise(function (resolve,reject) {
setTimeout(function(){
let number = Math.random();
if(number >.5){
resolve(number);
}else{
reject('数字太小');
}
},1000);
});
}
function *read(){
let a = yield getNumber();
console.log(a);
let b = yield 'b';
console.log(b);
let c = yield getNumber();
console.log(c);
}
function co(gen){
return new Promise(function(resolve,reject){
let g = gen();
function next(lastValue){
let {done,value} = g.next(lastValue);
if(done){
resolve(lastValue);
}else{
if(value instanceof Promise){
value.then(next,function(val){
reject(val);
});
}else{
next(value);
}
}
}
next();
});
}
co(read).then(function(data){
console.log(data);
},function(reason){
console.log(reason);
});
13.11.2 co连续读文件
let fs = require('fs');
function readFile(filename){
return new Promise(function (resolve,reject) {
fs.readFile(filename,'utf8',function(err,data){
if(err)
reject(err);
else
resolve(data);
})
});
}
function *read(){
let a = yield readFile('./1.txt');
console.log(a);
let b = yield readFile('./2.txt');
console.log(b);
}
function co(gen){
let g = gen();
function next(val){
let {done,value} = g.next(val);
if(!done){
value.then(next);
}
}
next();
}
13.12 Promise/A+完整实现
function Promise(executor) {
let self = this;
self.status = "pending";
self.value = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(function () { // 异步执行所有的回调函数
if (self.status == 'pending') {
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(item => item(value));
}
});
}
function reject(value) {
setTimeout(function () {
if (self.status == 'pending') {
self.value = value;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item => item(value));
}
});
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('循环引用'));
}
let then, called;
if (x != null && ((typeof x == 'object' || typeof x == 'function'))) {
try {
then = x.then;
if (typeof then == 'function') {
then.call(x, function (y) {
if (called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, function (r) {
if (called)return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
if (called)return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : function (value) {
return value
};
onRejected = typeof onRejected == 'function' ? onRejected : function (value) {
throw value
};
let promise2;
if (self.status == 'resolved') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status == 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status == 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function (value) {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push(function (value) {
try {
let x = onRejected(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
return promise2;
}
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
}
Promise.all = function (promises) {
return new Promise(function (resolve, reject) {
let result = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (data) {
result[i] = data;
if (++count == promises.length) {
resolve(result);
}
}, function (err) {
reject(err);
});
}
});
}
Promise.deferred = Promise.defer = function () {
var defer = {};
defer.promise = new Promise(function (resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
})
return defer;
}
/**
* npm i -g promises-aplus-tests
* promises-aplus-tests Promise.js
*/
try {
module.exports = Promise
} catch (e) {
}