JS 杂记
-
'use strict'
JS文件行首声明,防止变量未声明成为全局变量// 对象内部定义的函数称为方法,其中this关键字代指对象本身 // 如果不通过对象调用方法,那么this代指window,如果在'strict'模式下,this代指'undefined'
-
ES6 模板字符串
// 需要放到 反引号 中才有效哦;另外 反引号 可以支持多行文本 var name = '小明'; var age = 20; var message = `你好, ${name}, 你今年${age}岁了!`;
-
in
判断对象是否拥有某属性。注意:JS对象的所有属性都是字符串!var xiaoming = { name: '小明', birth: 1990 }; "name" in xiaoming; // true
-
hasOwnProperty()
判断一个对象是否拥有某属性(非继承属性)var xiaoming = { name: '小明', birth: 1990 }; xiaoming.hasOwnProperty('name'); // true xiaoming.hasOwnProperty('toString'); // false toString 继承自object
-
条件分支语句块只包含一条语句时,可以省略
{ }
花括号var balance = 99; if (balance >= 100) alert('rich'); else alert('poor'); // 但是建议每次都加上,因为一点语句块包含两条语句,那么第二条就不在else控制范围内
-
JS中的
false
和true
:null; undefined; 0; NaN; ''; // 空字符串 // 除此之外都为true // 因此判断数组为空,需要如下: var arr = []; if (arr) console.log('true'); // 打印true if (arr.length) console.log('true'); // 不打印 // 通过lodash进行是否为空判断 if (_.isEmpty(arr)) console.log('true'); // 打印true
-
浮点数/整数解析
parseFloat('123.45') // 123.45 解析为浮点数 parseInt(123.45) // 123 解析为整数
-
for ... in
循环,遍历属性// 遍历对象的属性 var obj = { name: 'Jack', age: 20, city: 'Beijing' }; for (var i in obj) console.log(i); // name age city // 遍历arrary的索引 (array也是对象,其属性是索引) var arr = ['a', 'b', 'c']; for (var i in arr) { console.log(i); // 0 1 2 console.log(arr[i]) // a b c }
-
do {...} while(...)
循环,先执行再判断,因此循环体至少执行一次。 -
Map 对象,ES6新特性
// JS对象的属性只能是字符串。而map对象类似于python的字典,key可以为字符串或数字,查找速度极快 // 初始化一个Map对象,接收二维数组 var m = new Map([['ayhan', 'male'], ['lena', 'female']]); // {"ayhan" => "male", "lena" => "female"} // 初始一个空Map对象 var m = new Map(); //添加元素及操作 m.set('ayhan', 'male'); // 添加元素 m.has('ayhan'); // true m.get('ayhan'); // 'male' m.delete('ayhan'); // 删除, key存在返回true, 否则false
-
Set 集合对象,ES6新特性;类似python, 其中元素不能重复,可用于去重
// 初始化一个Set对象 var s = new Set([1, 2, 3, 3]); // {1, 2, 3} var s = new Set() // 常用操作 s.add(5); // {1, 2, 3, 5} s.delete(3); // {1,2, 5} s.has(3); // false
-
iterable
可迭代对象,ES6新特性,统一了集合类型的遍历,包括:Array, Map, Set// 通过 for ... of 遍历 var a = [1, 2, 3, 4]; var m = new Map([['a', 1], ['b',2], ['c', 3]]); var s = new Set(['x', 'y', 'z']); for (var i of a) { console.log(i); // 1,2,3,4 }; for (var i of m) { console.log(i[0] + '=' + i[1]); // a=1, b=2, c=3 }; for (var i of s) { console.log(i); // x, y, z } // 通过 forEach 遍历 // 遍历Array a.forEach(function(ele, index, array) { // ele 当前元素 // index 当前索引 // array 可迭代对象本身 console.log(ele + ', index= ' + index); // 1, index= 0 }); // 遍历Map m.forEach(function(ele, key, map) { console.log(val); }); // 遍历Set: 集合没有索引,因此前两个参数就是当前元素 s.forEach(function(ele, sameEle, set) { console.log(sameEle); }) // 虽然forEach可以接收3个参数,如果只需要第一个,下面这样也是可以的 a.forEach(function(ele) { console.log(ele); // 1 });
-
定义函数的两种方式:
// function 声明 function bar(x) { console.log(x); } // 通过匿名函数赋值 var bar = function(x) { console.log(x); };
-
JS函数可以传入任意个参数不报错
-
函数参数检查和异常抛出
function abs(x) { if (typeof x !== 'number') { throw 'Not a number'; } if (x >= 0) { return x; } else { return -x; } } abs('aaa') // Uncaught Not a number
-
arguments
关键字,只在函数内部起作用,指向函数调用时传入的所有参数,类似于Array,可以进行遍历 -
rest
参数 ES6 新特性,用于传给函数的多余的参数function bar(x, y, ...rest) { // ...rest 固定写法 console.log(rest) }; bar(1, 2, 3, 4); // [3, 4]
-
变量提升:扫描整个函数体的语句,把所有声明的变量“提升”到函数顶部,但是不提提升赋值:
function foo() { var a = 'hello, ' + b; console.log(a); var b = 'ayhan'; } foo(); // hello, undefined // 执行时相当于: function foo() { var b; // 提升b的声明,但是不赋值,因此是undefined var a = 'hello, ' + b; console.log(a); var b = 'ayhan'; }
-
鉴于JS的变量提升特性,建议在函数体中首先通过
var
声明所有的变量:function foo() { var // 通过 var 声明函数内所有变量 b = 'ayhan'; a = 'hello, ' + b; console.log(a); }
-
JS有一个默认的全局对象
window
,因此任何全局作用域变量,都相当于绑定到window
的一个属性var a = 1; a == window.a; // true function foo() { console.log(foo); } foo == window.foo; //true
-
JS 中变量作用域查找是层层网上找,直到唯一的全局作用域
window
,如果还是找不到,就报ReferenceError
错误。 -
名字空间:由于JS中任何全局变量都会绑定到
window
上,容易发生命名冲突。解决方案:声明一个全局变量,把所有的变量和函数都绑定到这个变量上。var myspace = {}; // 声明一个全局变量 myspace.x = 123; myspace.foo = function() {...} // jQuery等库其实就是这么干的
-
块级作用域:传统的JS作用域是以函数划分。ES6中引入的
let
可以声明一个块级作用域变量function foo() { for (var i=0; i<10; i++) { ; } console.log(i); } foo(); // 10 // 通过 let 声明具有块级作用域的变量 function foo() { for (let i=0; i<10; i++) { ; } console.log(i); } foo(); // undefined
-
const
常量。传统的JS中用大写字母声明常量,ES6中引入了const
,与let一样,具有块级作用域 -
解构赋值,类似于python中的解压序列,ES6
// 将一个数组中的元素分别赋值给几个变量 var [x, y, z] = [1, 2, 3]; // x=1, y=2, z=3 // 也支持其他集合对象,比如集合 var s = new Set([1,2,3]); var [x, y, z] = s; // 嵌套 var [x, [y, z]] = ['hello', ['ayhan', 'papa']]; // 嵌套层级必须一致才行。 // 忽略多余元素 var [, y, ] = ['hello', 'ayhan', 'papa']; // 只赋值 var y = 'ayhan' // 对象,如果有嵌套,层级需要保持一致 var handsome = { name: 'ayahn', age: '18', phone: '110111110', addr: { city: 'beijing', strict: '海淀', } } var {name, addr: {city, strict}} = handsome; strict // 海淀 // 对象属性不存在时,变量将被赋值为undefined。对于不存在的属性,可以设置默认值 var {name, gender='male'} = handsome; // 将某个属性赋给另一个变量 var {phone: mobile} = handsome; // 将handsome对象的phone属性赋值给mobile变量 phone // Uncaught ReferenceError: phone is not defined mobile // "110111110"
-
解构赋值的应用场景:
// 交换变量的值 var x=1, y=2; [x, y] = [y, x]; // 获取当前页面的域名和路径 var {hostname:domain, pathname:path} = location; // 如果函数接收对象作为参数,通过解构赋值,可以直接该对象的属性绑定给变量 function buildDate({year, month, day, hour=0, minute=0, second=0}) { return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second); } buildDate({year: 2017, month:12, day:12} ); // Tue Dec 12 2017 00:00:00 GMT+0800 (中国标准时间)
-
函数apply方法:1. 指定this代指谁 2. 装饰器
// 格式:func.apply(调用者, [arg1, arg2,...]) 不同的调用者,this指代不一样
// 利用apply实现装饰器,统计parseInt调用了几次。
‘use strict’var count = 0;
var oldParseInt = parseInt;
window.parseInt = function() {
count += 1
return oldParseInt.apply(null, arguments);
} -
call 方法,同apply,只是传参按顺序传入,而不是打包成数组传入
-
map, reduce 映射/聚合计算,返回新的结果
// 将数组[1,2,3,4] 转为整数1234 var arr = [1,2,3,4]; arr.reduce(function(x, y) { return x*10 + y; }) // 将数组[1,2,3,4] 的每个元素转为字符 arr.map(String);
-
parseInt
和map的坑// 因为parseInt其实还有第二个参数:radix,该参数为进行转换的基数,需要是2~36的一个值,默认为0是使用10,map函数实际会为它传入第二个参数:【当前数的索引值】。所以parseInt实际拿到的radix值在传入1时为0,所以无误,传入2时为1,返回NaN,传入3时为2,返回NaN // 把字符串变为整数 var arr = ['1', '2', '3']; arr.map(parseInt); // 1,NaN,NaN // map将当前元素的索引传入作为第二个参数 //正确做法 arr.map(Number);
-
深度克隆JS对象
JSON.parse(JSON.stringify(obj)) // 以上克隆方式仅在对象内部没有定义方法才可行。 // 但是通过lodash库可以: var objB = _.cloneDeep(objA)
-
JS产生随机数:
function getRandomNumber(min, max){ return Math.floor(Math.random() * (max - min)) + min; } console.log(getRandomNumber(15, 20)); // lodash做法: console.log(_.random(15, 20));
-
扩展对象,类似python的update
// 为对象扩展原型方法extend Object.prototype.extend = function(obj) { for (let i in obj) { if (obj.hasOwnProperty(i)) { //判断被扩展的对象有没有某个属性, this[i] = obj[i]; } } }; var objA = {"name": "戈德斯文", "car": "宝马"}; var objB = {"name": "柴硕", "loveEat": true}; objA.extend(objB); console.log(objA); // 通过lodash来做 console.log(_.assign(objA, objB));
-
var let const
辨析// var function temp() { if (1 !== 1) { var a = 1; } console.log(a); } temp(); // 打印结果 "undefined",解析: // JS中作用域默认以函数划分,并对所有以var声明的变量进行提升,这意味着 var 声明在编译阶段被处理,JS引擎知道函数体内存在变量 a,但是其值只有在赋值语句执行时才真的存在(而以上赋值语句不可能执行),因此默认值为undefined。 // let 'use strict'; function temp() { if (1 !== 1) { let a = 1; } console.log(a); } temp(); // 结果抛出ReferenceError异常:Uncaught ReferenceError: a is not defined,解析: // let 确保变量a只在if语句块中可见。所以在外部并不知道有变量a的存在,这与C,Java等语言的处理方式是一样的。 // const 'use strict'; const a = 1; a = 2; // 结果抛出TypeError异常:Uncaught TypeError: Assignment to constant variable. 解析: // const 于C语言中的const相同,一旦为const声明的变量赋值,那么久无法再为该变量进行其他赋值 // 但是不要将const 和 可变对象混淆,虽然你不可以进行其他赋值,但你可以修改它的值,如下: 'use strict'; const a = {}; a.a = 1;
-
window.history
属性指向 History 对象,它表示当前窗口的浏览历史。window.history.length // 3 一共访问了3个网址
由于安全原因,浏览器不允许脚本读取这些地址,但是允许在地址之间导航。
History.back()
:移动到上一个网址,等同于点击浏览器的后退键。对于第一个访问的网址,该方法无效果。History.forward()
:移动到下一个网址,等同于点击浏览器的前进键。对于最后一个访问的网址,该方法无效果。History.go()
:接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址,比如go(1)
相当于forward()
,go(-1)
相当于back()
。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为0
,相当于刷新当前页面。
-
filter 将函数作用于数组的每个元素上,并根据返回值决定保留(true)还是丢弃元素(false)
var arr = ['A', '', 'B', null, undefined, 'C', ' ']; var r = arr.filter(function(s) { return s && s.trim(); }) // filter 接收三个参数 ele, index, self。利用indexOf()总是返回找到的第一个元素的位置,后续重复元素的位置于indexOf()返回的不相等,可以去重 arr.filter(function(ele, index, self) { return self.indexOf(ele) === index; })
-
sort 排序:默认将数组的所有元素转换为字符串,然后字符串最高位ASCII码进行排序。默认排序没卵用,还好sort可以接收函数。另外,sort会对数组就地进行修改。
比较大小的逻辑:对于两个元素x
和y
,如果认为x < y
,则返回-1
,如果认为x == y
,则返回0
,如果认为x > y
,则返回1
// 对数组进行排序 var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; });
-
闭包
function lazySum(arr) { var sum = function() { return arr.reduce(function(x, y) { return x + y; }); }; return sum } var sum = lazySum([1,2,3,4,5]); sum(); // 15 // lazySum中定义了函数sum,内部函数sum引用外部函数lazySum的参数和局部变量。 // 作用域在定义阶段就确定了。因此当lazySum返回sum函数时,sum仍然保留之前参数和局部变量的引用
返回闭包时牢记的一点就是:
返回函数不要引用任何循环变量,或者后续会发生变化的变量。function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push(function () { return i * i; }); } return arr; } var res = count(); var f1 = res[0]; var f2 = res[1]; var f3 = res[2]; f1(); // 16 f2(); // 16 f3(); // 16 // 解析,执行函数,循环结束后,arr中推入了3个函数,这个3个函数时闭包函数(定义在内部的函数)。函数引用其定义阶段的外部变量 i, 循环结束后,i的值是4,因此出现以上结果。 // 解决办法查看:https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/00143449934543461c9d5dfeeb848f5b72bd012e1113d15000
-
箭头函数
相当于匿名函数,并简化了函数定义。
// 简单箭头函数,只包含一个表达式 x => x * x // 包含多条语句的,不能省略 {...} 和 return x => { if (x > 0) { return x * x; } return -x * x; } // 如果参数不止一个,就要用 () 括起来 (x, y) => x * x + y * y; // 无参数 () => 3.14 // 可变参数 (x, y, ...rest) => { var i, sum = x + y; for (i=0; i<rest.length; i++) { sum += rest[i]; } return sum; } // 如果返回对象,需要用()括起来,以免和{}函数体语法冲突 x => ({ foo: x }) // this问题:在匿名函数中this指代window或者undefined,箭头函数修复了这个问题,总实指向词法作用域,也就是外层调用者obj 箭头函数 阅读: 131908 ES6标准新增了一种新的函数:Arrow Function(箭头函数)。 为什么叫Arrow Function?因为它的定义用的就是一个箭头: x => x * x 上面的箭头函数相当于: function (x) { return x * x; } 在继续学习箭头函数之前,请测试你的浏览器是否支持ES6的Arrow Function: 'use strict'; var fn = x => x * x; console.log('你的浏览器支持ES6的Arrow Function!'); Run 箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }和return: x => { if (x > 0) { return x * x; } else { return - x * x; } } 如果参数不是一个,就需要用括号()括起来: // 两个参数: (x, y) => x * x + y * y // 无参数: () => 3.14 // 可变参数: (x, y, ...rest) => { var i, sum = x + y; for (i=0; i<rest.length; i++) { sum += rest[i]; } return sum; } 如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错: // SyntaxError: x => { foo: x } 因为和函数体的{ ... }有语法冲突,所以要改为: // ok: x => ({ foo: x }) this 箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。 回顾前面的例子,由于JavaScript函数对this绑定的错误处理,下面的例子无法得到预期结果: var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = function () { return new Date().getFullYear() - this.birth; // this指向window或undefined }; return fn(); } }; var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象 return fn(); } }; obj.getAge(); // 28 // 由于this在箭头函数中已经按照吃法作用域绑定了,所以call()/apply()调用箭头函数时,无法对this及进行绑定 var obj = { birth: 1990, getAge: function(year) { var b = this.birth; var fn = (y) => y - this.birth; return fn.call({birth:2000}, year); } };
-
generator 生成器函数,同python, ES6特性
由function*
声明,并且函数体内使用yield关键字// 定义生成器函数 function* foo(x) { yield x + 1; yield x + 2; return x + 3; } var g = foo(1); // 拿到生成器对象 g.next(); // {value: 2, done: false} g.next(); // {value: 3, done: false} g.next(); // {value: 4, done: true} 迭代完了 g.next(); // {value: undefined, done: false} // 通过 for ... of 进行迭代: for (var i of g) { console.log(i); } // generator还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。没有generator之前的黑暗时代,用AJAX时需要这么写代码: ajax('http://url-1', data1, function (err, result) { if (err) { return handle(err); } ajax('http://url-2', data2, function (err, result) { if (err) { return handle(err); } ajax('http://url-3', data3, function (err, result) { if (err) { return handle(err); } return success(result); }); }); }); // 用生成器来写: try { r1 = yield ajax('http://url-1', data1); r2 = yield ajax('http://url-2', data2); r3 = yield ajax('http://url-3', data3); success(r3); } catch (err) { handle(err); } // 看上去是同步的代码,实际执行是异步的。
-
JS 时间对象
var now = new Date(); // 当前时间对象,从本机操作系统获取,不一定准确 var timer = new Date('2000-12-12') // 指定时间对象 var ts = Date.parse('2018-06-07 12:00:00'); // 时间戳毫秒1528344000000 var d = new Date(ts); // 时间戳转时间对象(浏览器会自动把时间戳转化为本地时间) var t = Date.now(); // 获取当前时间戳 d.toLocaleString(); // 转字符串 d.toUTCString(); // 转化为UTC时间,与UTC8差8个小时
-
JS 基于原型创建对象
// 原型对象: var Student = { name: '', height: 1.2, run: function () { console.log(this.name + ' is running...'); } }; // 通过Object.create(原型对象)创建新对象 function createStudent(name) { // 基于Student原型创建一个新对象: var s = Object.create(Student); // 初始化新对象: s.name = name; return s; } var xiaoming = createStudent('小明'); xiaoming.run(); // 小明 is running... xiaoming.__proto__ === Student; // 小明的原型时Student true
-
JS基于构造函数创造对象
// 构造函数,首字符大写!区分普通函数 function Student(name) { this.name = name; this.hello = function() { alert('hello, ' + this.name + '!'); } } // new 实例化对象 var s = new Student('二狗子') // 但是所有对象都会拥有一个自己的hello函数,浪费内存 // 正确做法如下: function Student(name) { this.name = name; } Student.prototype.hello = function () { alert('hello, ' + this.name + '!'); }
-
JS 属性查找顺序:当前对象 =》 原型对象 =》Object
var arr = [1,2,3] arr => Array.prototype => Object.prototype // 函数的原型链 function foo() { console.log('foo'); } foo => Function.prototype => Object.prototype // apply() 就是Function.prototype上的方法
-
class 关键字 定义类 ;ES6特性
// 之前用函数实现Student function Student(name) { this.name = name; } Student.prototye.hello = function () { alert('hello, ' + this.name + '!'); } // class 关键字实现 class Student { constructor(name) { //构造函数 constructor this.name = name; } hello() { // 没有function 关键字!,所有对象共享hellO方法,不需要如上单独定义 alert('hello, ' + this.name + '!'); } } // 创建新的的对象时,也是通过new关键字
-
class 类的继承
没有class关键字之前,JS要实现继承很繁琐(借助prototype),现在通过extends即可class PrimaryStudent extends Student { constructor(name, grade) { super(name); // super调用父类的构造方法 this.grade = grade; } myGrade() { alert('My grade is ' + this.grade); } } // 利用babel可以将class 转为 prototype 语法
-
|| 短路运算符。在JS逻辑运算中,0、""、null、false、undefined、NaN都会判为false,其他都为true.
- 只要第一个值的布尔值为false,那么永远返回第二个值。
- 逻辑或属于短路操作,第一个值为true时,不再操作第二个值,且返回第一个值
ar attr = attr || ""; // 判断一个变量是否已定义,如果没有定义就给他一个初始值 console.log(0 || '我是string,boolean值为true'); // 返回字符串 console.log(NaN || '我是string,boolean值为true'); // 返回字符串 console.log('' || '我是string,boolean值为true'); // 返回字符串 console.log(null || '我是string,boolean值为true'); // 返回字符串 console.log(undefined || '我是string,boolean值为true'); // 返回字符串 // 我们知道 0、NaN、' '、null、undefined转换为boolean值都为flase,若第一个值为false,第二个值为true,则返回第二个值。 console.log(0 || 'NaN'); // 返回 NaN console.log(NaN || ''); // 返回 '' 空字符串 console.log('' || null); // 返回 null console.log(null || 'undefined'); // 返回 undefined console.log(undefined || 0); // 返回 0 // 只要第一个值的布尔值为false,那么永远返回第二个值,**不管第二个值的布尔值是true还是false。
&&
- 只要第一个值的布尔值为true,那么永远返回第二个值。
- 逻辑与属于短路操作,第一个值为false时,不再操作第二个值,且返回第一个值。
&&,它先计算第一个表达式,若为假,就不会去处理第二个表达;否则继续处理后继表达式。从左到右选取表达式的第一个为非true的表达式的值,如果一直未找到则返回最后一个表达式的值。 例:(其中的味道还需要细心琢磨) 2 && 's1' && '123' && 'sss' 表达式的值等于 'sss' if(a >=5){ alert("你好"); } 可以简成: a >= 5 && alert("你好");
-
浏览器对象
-
window 对象
window.innerWidth
浏览器窗口的内部宽度和高度window.innerHeight
-
navigator 对象 表示浏览器的信息。请注意,
navigator
的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的- navigator.appName:浏览器名称;
- navigator.appVersion:浏览器版本;
- navigator.language:浏览器设置的语言;
- navigator.platform:操作系统类型;
- navigator.userAgent:浏览器设定的
User-Agent
字符串。
-
screen 对象
- screen.width:屏幕宽度,以像素为单位;
- screen.height:屏幕高度,以像素为单位;
- screen.colorDepth:返回颜色位数,如8、16、24。
-
location 当前页面的URL信息,比如
http://www.example.com:8080/path/index.html?a=1&b=2#TOP
-
location.href 获取当前URL
-
location.protocol; // ‘http’
location.host; // ‘www.example.com’
location.port; // ‘8080’
location.pathname; // ‘/path/index.html’
location.search; // ‘?a=1&b=2’
location.hash; // ‘TOP’ -
location.assign(path) 加载新页面
-
location.reload() 刷新页面
if (confirm('重新加载当前页' + location.href + '?')) { location.reload(); // 刷新页面 } else { location.assign('/'); // 跳回首页 }
-
-
-
Promise 对象
JS代码都是单线程的,这就要求所有的网络请求,浏览器事件,都必须时异步操作。异步操作通常用回调函数实现。
通过setTimeout模拟网络请求
https://segmentfault.com/a/1190000011652907
function callback() {
console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');
// 执行结果
before setTimeout()
VM5314:6 after setTimeout()
undefined 1秒后...
VM5314:2 Done
异步操作就是会在将来某个时间点触发一个函数调用。
Promise 概念:它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。
Promise一般形式:
new Promise(
/* executor */
function(resolve, reject) {
if (/* success */) {
// ...执行代码
resolve();
} else { /* fail */
// ...执行代码
reject();
}
}
);
Promise对象有3个状态:
- pending:初始化状态
- fulfilled: 完成状态
- rejected: 失败状态
Promise 的参数是一个执行器函数,它有两个参数:resolve, 和 reject。执行器函数内部执行一些异步操作(比如网络请求):
- 如果异步操作成功,则调用resolve(),将Promise实例的状态设置为fulfilled
- 如果异步操作失败,则调用reject(),将Promise实例的状态设置为reject
Promise状态的转化(单向,不可逆)
- 操作成功:pending -> fulfilled
- 操作失败:pending -> rejected
示例:
function test(resolve, reject) {
var timeOut = Math.random()*2;
console.log('set timeout to: '+timeOut+' seconds');
setTimeout(function() {
if (timeOut < 1) {
console.log('call resolve()...');
resolve('200 ok');
}
else {
console.log('call reject()...');
reject('timeout in ' + timeOut + ' seconds');
}
}, timeOut*1000)
}
// Promise 对象来执行函数,并在某个时刻获得成功或失败的结果。Promise对象串联,可以简化为
new Promise(test).then(function (result) {
console.log('成功: ' + result);
}).catch(function (reason) {
console.log('失败: ' + reason);
})
Promise的方法:
then() 方法:调用后返回一个Promise对象,因此可以实现链式调用。then() 接收两个函数,一个是处理成功后的函数,一个是处理错误结果的函数。
var p1 = new Promise(function(resolve, reject) {
// 模拟网络请求成功
setTimeout(function () {
resolve('success response')
}, 2000);
});
p1
.then(function(data){
console.log(data); // 成功,执行
}, function (err) {
console.log(err); // 不执行
})
.then(function(data) {
// 上一步then()没有返回值,data undefined
console.log('链式调用: ' + data);
return NaN
}, function(err) {
console.log(err)
})
.then(function (data) {
console.log(data)
}, function (err) {
console.log(err) // 失败执行
})
/* 返回的这个Promise对象的状态主要是根据promise1.then()方法返回的值,大致分为以下几种情况:
如果then()方法中返回了一个参数值,那么返回的Promise将会变成接收状态。
如果then()方法中抛出了一个异常,那么返回的Promise将会变成拒绝状态。
如果then()方法调用resolve()方法,那么返回的Promise将会变成接收状态。
如果then()方法调用reject()方法,那么返回的Promise将会变成拒绝状态。
如果then()方法返回了一个未知状态(pending)的Promise新实例,那么返回的新Promise就是未知状态。
如果then()方法没有明确指定的resolve(data)/reject(data)/return data时,那么返回的新Promise就是接收状态,可以一层一层地往下传递。
*/
var promise2 = new Promise(function(resolve, reject) {
// 2秒后置为接收状态
setTimeout(function() {
resolve('success');
}, 2000);
});
promise2
.then(function(data) {
// 上一个then()调用了resolve,置为fulfilled态
console.log('第一个then');
console.log(data);
return '2';
})
.then(function(data) {
// 此时这里的状态也是fulfilled, 因为上一步返回了2
console.log('第二个then');
console.log(data); // 2
return new Promise(function(resolve, reject) {
reject('把状态置为rejected error'); // 返回一个rejected的Promise实例
});
}, function(err) {
// error
})
.then(function(data) {
/* 这里不运行 */
console.log('第三个then');
console.log(data);
// ....
}, function(err) {
// error回调
// 此时这里的状态也是fulfilled, 因为上一步使用了reject()来返回值
console.log('出错:' + err); // 出错:把状态置为rejected error
})
.then(function(data) {
// 没有明确指定返回值,默认返回fulfilled
console.log('这里是fulfilled态');
});
catch() 方法:同then() ,返回新的Promise对象,主要用于捕获处理异常,因此可以省略then()方法的第二个参数。
var p3 = new Promise(function (resolve, reject) {
setTimeout(function () {
reject('reject')
}, 2000);
});
p3
.then(function(data) {
console.log('fulfilled status'); // 不会执行
})
// 最后的catch()方法可以捕获在这一条Promise链上的异常
.catch(function (err) {
console.log(err)
})
all() 方法,接收一个promise对象构成数组。处理一些并发的异步操作,其结果互不干扰,只是需要异步执行。它最终只有两种状态:成功或者失败。
它的状态受参数内各个值的状态影响,即里面状态全部为fulfilled
时,它才会变成fulfilled
,否则变成rejected
。成功调用后返回一个数组,数组的值是有序的,即按照传入参数的数组的值操作后返回的结果
var arr = [1,2,3];
var ps = arr.map(function(ele){
return new Promise(function (resolve, reject) {
resolve(ele * 5);
});
});
Promise.all(ps).then(function (data) {
console.log(data); // [ 5, 10, 15 ]
})
race() 方法:类似all(),但是谁跑得快,就取谁的值。有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()
实现:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
resolve()
Promise.resolve()接受一个参数值,可以是普通的值
,具有then()方法的对象
和Promise实例
。正常情况下,它返回一个Promise对象,状态为fulfilled
。但是,当解析时发生错误时,返回的Promise对象将会置为rejected
态。如下:
// 参数为普通值
var p4 = Promise.resolve(5);
p4.then(function(data) {
console.log(data); // 5
});
// 参数为含有then()方法的对象
var obj = {
then: function() {
console.log('obj 里面的then()方法');
}
};
var p5 = Promise.resolve(obj);
p5.then(function(data) {
// 这里的值时obj方法里面返回的值
console.log(data); // obj 里面的then()方法
});
// 参数为Promise实例
var p6 = Promise.resolve(7);
var p7 = Promise.resolve(p6);
p7.then(function(data) {
// 这里的值时Promise实例返回的值
console.log(data); // 7
});
// 参数为Promise实例,但参数是rejected态
var p8 = Promise.reject(8);
var p9 = Promise.resolve(p8);
p9.then(function(data) {
// 这里的值时Promise实例返回的值
console.log('fulfilled:'+ data); // 不执行
}).catch(function(err) {
console.log('rejected:' + err); // rejected: 8
});
reject() 方法Promise.reject()和Promise.resolve()正好相反,它接收一个参数值reason
,即发生异常的原因。此时返回的Promise对象将会置为rejected
态。如下:
var p10 = Promise.reject('手动拒绝');
p10.then(function(data) {
console.log(data); // 这里不会执行,因为是rejected态
}).catch(function(err) {
console.log(err); // 手动拒绝
}).then(function(data) {
// 不受上一级影响
console.log('状态:fulfilled'); // 状态:fulfilled
});
除非Promise.then()方法内部抛出异常或者是明确置为rejected态,否则它返回的Promise的状态都是fulfilled态,即完成态,并且它的状态不受它的上一级的状态的影响。
-
模块
Node环境中,一个.js文件就称之为一个模块(module) ,模块的导入和导出:
// 导出模块 'use strict'; var s = 'Hello'; function greet(name) { console.log(s + ', ' + name + '!'); } module.exports = greet; // 导出模块 // 导入模块 'use strict'; // 引入hello模块: var greet = require('./hello'); // 导入模块 var s = 'Michael'; greet(s); // Hello, Michael! // 导出模块:输出对象/函数 module.exports = { foo: function () { return 'foo'; } }; module.exports = function () { return 'foo'; };
-
Node 模块之 fs 文件读写模块
// 回调函数function 接收两个参数: // 如果读取异常时,err为一个错误对象,否则为null // data 为读到的字符串,如果异常时,undefined 'user strict' var fs = require('fs') fs.readFile('sample.txt', 'utf-8', function(err, data) { if (err) { console.log(err); } else { console.log(data); } }) // 读取二进制文件时,不传入文件编码时,data返回一个Buffer对象(直接构成的数组) // Buffer和String 转换: var text = data.toString('utf-8') bar buf = Buffer.from(text, 'utf-8') // writeFile() 写文件 var data = 'Hello, Node.js'; fs.writeFile('output.txt', data, function (err) { if (err) { console.log(err); } else { console.log('ok.'); } }); fs.stat('sample.txt', function(err, stat) { stat.isFile(); 判断是否是文件 stat.isDirectory(); 是否是目录 // 文件时的属性 stat.size; stat.birthtime; // 创建时间 stat.mtime; // 修改时间 }) // readFile 和 WriteFile 都是异步执行的;也有同步方法:readFileSync / writeFileSync,基本没啥用。 /* 由于Node环境执行的JavaScript代码是服务器端代码,所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,否则,同步代码在执行时期,服务器将停止响应,因为JavaScript只有一个执行线程。 */