js函数与 Promise的使用

JavaScript的函数不但是“头等公民”,而且可以像变量一样使用,具有非常强大的抽象能力。

定义函数的方式如下:
function abs (x) { if (x >= 0 ) { return x; } else { return -x; }}

如果没有 return 语句,函数执行完毕后也会返回结果,只是结果为 undefined

由于JavaScript的函数 也是一个对象 ,上述定义的 abs() 函数实际上是一个函数对象,而函数名 abs 可以视为 指向该函数的变量。
因此,第二种定义函数的方式如下:
var abs = function (x) { if (x >= 0 ) { return x; } else { return -x; }};

在这种方式下, function (x) { ... } 是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量 abs ,所以, 通过变量 abs 就可以调用该函数

述两种定义 完全等价 ,注意第二种方式按照完整语法需要在函数体末尾加一个 ; ,表示赋值语句结束。


abs(); // 返回 NaN
此时 abs(x) 函数的参数 x 收到 undefined ,计算结果为 NaN
要避免收到 undefined ,可以对参数进行检查:
function abs (x) { if ( typeof x !== 'number') { throw 'Not a number' ; } if (x >= 0 ) { return x; } else { return -x; }}


请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个 var 申明函数内部用到的所有变量:
function foo () { var x = 1 , // x初始化为1 y = x + 1 , // y初始化为2 z, i; // z和i为undefined // 其他语句: for (i= 0 ; i< 100 ; i++) { ... }}


全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象 window ,全局作用域的变量实际上被绑定到 window 的一个属性:

var course = 'Learn JavaScript' ;alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript'


以变量方式 var foo = function () {} 定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到 window 对象:
function foo () { alert( 'foo' );}foo(); // 直接调用foo() window.foo(); // 通过window.foo()调用


不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突, 并且很难被发现。

减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量MYAPP: var MYAPP = {}; // 其他变量: MYAPP.name = 'myapp' ;MYAPP.version = 1.0 ; // 其他函数: MYAPP.foo = function () { return 'foo' ;};


局部作用域
由于JavaScript的变量作用域实际上是函数内部,我们在 for 循环等语句块中是无法定义具有局部作用域的变量的:
'use strict' ; function foo () { for ( var i= 0 ; i< 100 ; i++) { // } i += 100 ; // 仍然可以引用变量i }
为了解决块级作用域,ES6引入了新的关键字 let ,用 let 替代 var 可以申明一个 块级作用域 的变量:
'use strict' ; function foo () { var sum = 0 ; for ( let i= 0 ; i< 100 ; i++) { sum += i; } // SyntaxError: i += 1 ;}

关键字 const 来定义常量

解构赋值,可以同时对一组变量进行赋值。
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

注意,对数组元素进行解构赋值时,多个变量要用 [...] 括起来。
如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致:
let [x, [y, z]] = [ 'hello' , [ 'JavaScript' , 'ES6' ]];x; // 'hello' y; // 'JavaScript' z; // 'ES6'


如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:
'use strict';var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school'}; var {name, age, passport} = person;


同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的:
var person = { name: '小明' , age: 20 , gender: 'male' , passport: 'G-12345678' , school: 'No.4 middle school' , address: { city: 'Beijing' , street: 'No.1 Road' , zipcode: '100001' }}; var {name, address: {city, zip}} = person;name; // '小明' city; // 'Beijing' zip; // undefined , 因为属性名是zipcode而不是zip // 注意: address不是变量 ,而是为了让city和zip获得嵌套的address对象的属性: address; // Uncaught ReferenceError: address is not defined


解构赋值还可以使用默认值,这样就避免了不存在的属性返回 undefined 的问题:
var person = { name: '小明' , age: 20 , gender: 'male' , passport: 'G-12345678' }; // 如果person对象没有single属性,默认赋值为true: var {name, single=true} = person;name; // '小明' single; // true



有些时候,如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:
// 声明变量: var x, y; // 解构赋值: {x, y} = { name: '小明' , x: 100 , y: 200 }; // 语法错误: Uncaught SyntaxError: Unexpected token =
这是因为JavaScript引擎把 { 开头的语句当作了块处理,于是 = 不再合法。解决方法是用小括号括起来:
({x, y} = { name: '小明', x: 100, y: 200});




在一个对象中绑定函数,称为这个对象的方法。
var xiaoming = { name: '小明' , birth: 1990 , age: function () { var y = new Date().getFullYear(); return y - this .birth; }};xiaoming.age; // function xiaoming.age() xiaoming.age(); // 今年调用是25,明年调用就变成26了


让我们拆开写:
function getAge () { var y = new Date().getFullYear(); return y - this .birth;} var xiaoming = { name: '小明' , birth: 1990 , age: getAge};xiaoming.age(); // 25, 正常结果 getAge(); // NaN
单独调用函数 getAge() 怎么返回了 NaN 请注意 ,我们已经进入到了JavaScript的一个大坑里。
JavaScript的函数内部如果调用了 this ,那么这个 this 到底指向谁?
答案是,视情况而定!
如果以对象的方法形式调用,比如 xiaoming.age() ,该函数的 this 指向被调用的对象,也就是 xiaoming ,这是符合我们预期的。
如果单独调用函数,比如 getAge() ,此时,该函数的 this 指向全局对象,也就是 window



“创建一个匿名函数并立刻执行”的语法:
( function (x) { return x * x;})( 3 ); // 9
需要用括号把整个函数定义括起来


箭头函数

x => x * x
上面的箭头函数相当于:
function (x) { return x * x;}
相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连 { ... } return 都省略掉了。还有一种可以包含多条语句,这时候就不能省略 { ... } return

x => { if (x > 0 ) { return x * x; } else { return - x * x; }}

如果参数不是一个,就需要用括号 () 括起来:
// 两个参数: (x, y) => x * x + y * y
// 无参数: () => 3.14

如果要返回一个对象,需要加小括号
x => ({ foo: x })


Promise
异步执行可以用回调函数实现
AJAX就是典型的异步操作。

看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:
function test (resolve, reject) { var timeOut = Math.random() * 2 ; log( 'set timeout to: ' + timeOut + ' seconds.' ); setTimeout( function () { if (timeOut < 1 ) { log( 'call resolve()...' ); resolve( '200 OK' ); } else { log( 'call reject()...' ); reject( 'timeout in ' + timeOut + ' seconds.' ); } }, timeOut * 1000 );}
这个 test() 函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用 resolve('200 OK') ,如果执行失败,我们将调用 reject('timeout in ' + timeOut + ' seconds.') 。可以看出, test() 函数只关心自身的逻辑,并不关心具体的 resolve reject 将如何处理结果。

var p1 = new Promise(test); var p2 = p1.then( function (result) { console.log( '成功:' + result);}); var p3 = p2. catch ( function (reason) { console.log( '失败:' + reason);});

变量 p1 是一个Promise对象,它负责执行 test 函数。由于 test 函数在内部是异步执行的,当 test 函数执行成功时,我们告诉Promise对象:
// 如果成功,执行这个函数: p1.then( function (result) { console.log( '成功:' + result);});
test 函数执行失败时,我们告诉Promise对象:
p2. catch ( function (reason) { console.log( '失败:' + reason);});

Promise对象可以串联起来,所以上述代码可以简化为:
new Promise(test).then( function (result) { console.log( '成功:' + result);}). catch ( function (reason) { console.log( '失败:' + reason);});

Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。
job1. then (job2). then (job3).catch(handleError);
其中, job1 job2 job3 都是Promise对象。

// 0.5秒后返回input*input的计算结果:
function multiply(input) {
return new Promise(function (resolve, reject) {
log('calculating ' + input + ' x ' + input + '...');
setTimeout(resolve, 500, input * input);
});
}

// 0.5秒后返回input+input的计算结果:
function add(input) {
return new Promise(function (resolve, reject) {
log('calculating ' + input + ' + ' + input + '...');
setTimeout(resolve, 500, input + input);
});
}

var p = new Promise(function (resolve, reject) {
log('start new Promise...');
resolve(123);
});

p.then(multiply)
.then(add)
.then(multiply)
.then(add)
.then(function (result) {
log('Got value: ' + result);
});

把上一节的AJAX异步执行函数转换为Promise对象,看看用Promise如何简化异步处理:
'use strict';// ajax函数将返回Promise对象:function ajax(method, url, data) { var request = new XMLHttpRequest(); return new Promise(function (resolve, reject) { request.onreadystatechange = function () { if (request.readyState === 4) { if (request.status === 200) { resolve(request.responseText); } else { reject(request.status); } } }; request.open(method, url); request.send(data); });}

var log = document.getElementById('test-promise-ajax-result');
var p = ajax('GET', '/api/categories');
p.then(function (text) { // 如果AJAX成功,获得响应内容
log.innerText = text;
}).catch(function (status) { // 如果AJAX失败,获得响应代码
log.innerText = 'ERROR: ' + status;
});

Promise ,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

下面代码创造了一个 Promise 实例。
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); }});

Promise 构造函数 接受一个函数 作为参数,该函数的两个参数分别是 resolve reject 它们是两个函数 ,由 JavaScript 引擎提供,不用自己部署。

resolve 函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
reject 函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

promise.then(function(value) { // success}, function(error) { // failure});

then 方法可以接受两个回调函数作为参数。
其中,第二个函数是可选的,不一定要提供。
这两个函数都接受 Promise 对象 传出的值 作为参数。

function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); });}timeout(100).then((value) => { console.log(value);});

一般来说,最好在它们前面加上 return 语句,这样就不会有意外。
return resolve ( 1 );


可以采用链式写法,即 then 方法后面再调用另一个 then 方法。
getJSON ( "/posts.json" ). then ( function ( json ) { return json . post ; }). then ( function ( post ) { // ... });
上面的代码使用 then 方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。


getJSON ( "/post/1.json" ). then ( function ( post ) { return getJSON ( post . commentURL ); }). then ( function funcA ( comments ) { console . log ( "resolved: " , comments ); }, function funcB ( err ){ console . log ( "rejected: " , err ); });
上面代码中,第一个 then 方法指定的回调函数,返回的是另一个 Promise 对象。这时,第二个 then 方法指定的回调函数,就会等待这个新的 Promise 对象状态发生变化。如果变为 resolved ,就调用 funcA ,如果状态变为 rejected ,就调用 funcB
如果采用箭头函数,上面的代码可以写得更简洁。
getJSON ( "/post/1.json" ). then ( post => getJSON ( post . commentURL ) ). then ( comments => console . log ( "resolved: " , comments ), err => console . log ( "rejected: " , err ) );

有时需要将现有对象转为 Promise 对象, Promise.resolve 方法就起到这个作用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值