javascript
javascript
数据结构
-
数字
-
字符串
- '\x41'; // 完全等同于 'A'
- '\u4e2d\u6587'; // 完全等同于 '中文'
- `用于包含多行字段
-
布尔值
-
运算符, == 与 === 不同,==会强制转换,1='1'为true;NaN与所有值都不同,包括自己,只有通过isNaN()函数才能得到true
-
null 为空;undefine 为未定义,一般不用,只用于判断函数是否传参,访问对象空属性时返回undefine。
-
数组,
var a = new Array(); || var a = [1, 3, 'abc', null, true];
-
对象,即无序键值对,键一定为字符串,
-
obj.key=value; // 新增属性
-
delete obj.key; // 删除属性
-
'key' in obj; // 检测是否有此属性
-
xiaoming.hasOwnProperty('name'); // true 判断此属性是否是继承的
-
-
Map,键值对,键不一定为字符串(ES6)
-
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
-
-
Set (ES6)
变量
- 一个变量没有通过
var
申明,该变量自动被申明为全局变量;否则为局部变量 - strict模式:必须使用var声明变量。在js文件第一行写
'use strict'
; 不支持的将此视为一个字符串。 - iterable:Array
、
Map和
Set都属于
iterablefor ... of
循环来遍历
for ... of 与 for ... in 的区别。
for ... in
循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array
数组实际上也是一个对象,它的每个元素的索引被视为一个属性。当我们手动给
Array
对象添加了额外的属性后,for ... in
循环将带来意想不到的意外效果:var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x in a) { console.log(x); // '0', '1', '2', 'name' }
for ... in
循环将把name
包括在内,但Array
的length
属性却不包括在内。
for ... of
循环则完全修复了这些问题,它只循环集合本身的元素:var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x of a) { console.log(x); // 'A', 'B', 'C' }
a.forEach(function (element, index, array) { // ES 5.1 // element: 指向当前元素的值 // index: 指向当前索引 // array: 指向Array对象本身 console.log(element + ', index = ' + index); });
函数
-
允许传入任意个参数而不影响调用
-
arguments,
- 只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。
- 类似Array但它不是一个Array
- 即使函数没定义形参,也可通过其获得参数
-
rest 参数:
function foo(a, b, ...rest){ // 通过rest获得可变参数 }
-
js引擎自动添加引号
function foo() { return { name: 'foo' }; } // 等同于 function foo() { return; // 自动添加了分号,相当于return undefined; { name: 'foo' }; // 这行语句已经没法执行到了 }
变量提升
请严格遵守在函数内部首先申明所有变量这一规则
function foo() {
var x = 'Hello, ' + y;
console.log(x); // 得到 'Hello, undefine'
var y = 'Bob';
}
function foo() {
var y; // 提升变量y的申明,此时y为undefined
var x = 'Hello, ' + y;
console.log(x);
y = 'Bob';
}
全局作用域
JavaScript默认有一个全局对象window
,全局作用域的变量实际上被绑定到window
的一个属性
局部作用域
let
替代var
可以申明一个块级作用域的变量
function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用变量i
}
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
// SyntaxError:
i += 1;
}
解构赋值
var [x, y, z] = ['hello', 'JavaScript', 'ES6']; // ES6
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
// 对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
// 给name默认值
// 把passport属性赋值给变量id:
let {name='noname', passport:id} = person;
[a, b] = [b, a]; // a b 交换值
方法
要保证this
指向正确,必须用obj.xxx()
的形式调用
var xiaoming = {
birth: 1990,
age: function () { // 此时 this 指向xiaoming对象。在全局模式中,this指向window
var y = new Date().getFullYear();
return y - this.birth;
},
age2: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth; // 此时 this 不在对象的方法中,严格模式下为undefine,非strict模式它指向window
}
return getAgeFromBirth();
}
};
apply call
在一个独立的函数调用中,根据是否是strict模式,this
指向undefined
或window
,不过,我们还是可以控制this
的指向的!
要指定函数的this
指向哪个对象,可以用函数本身的apply
方法,它接收两个参数,第一个参数就是需要绑定的this
变量,第二个参数是Array
,表示函数本身的参数。
用apply
修复getAge()
调用:
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
另一个与apply()
类似的方法是call()
,唯一区别是:
apply()
把参数打包成Array
再传入;call()
把参数按顺序传入。
比如调用Math.max(3, 5, 4)
,分别用apply()
和call()
实现如下:
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
对普通函数调用,我们通常把this
绑定为null
。
装饰器
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3
高阶函数
接收函数变量作为参数
map
function pow(x) {
return x * x;
}
[1,2,3,4].map(pow);
reduce
[x1, x2, x3, x4].reduce(f) == f(f(f(x1, x2), x3), x4)
filter
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
return x % 2 !== 0;
});
r; // [1, 5, 9, 15]
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
console.log(element); // 依次打印'A', 'B', 'C'
console.log(index); // 依次打印0, 1, 2
console.log(self); // self就是变量arr
return true;
});
sort
arr.sort(function (x, y) { // 可接收比较函数作为排序依据
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
});
其他
- every(fun) 判断是否所有元素满足条件
- forEach() 把每个元素依次作用于传入的函数,但不会返回新的数组
- reverse()
闭包
函数作为返回值
返回的函数在其定义内部引用了局部变量
arr
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了
f()
才执行。返回函数不要引用任何循环变量,或者后续会发生变化的变量。
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
箭头函数
x => x * x
// 等同于
function (x) {
return x * x;
}
var fn = x => x * x;
fn(2); // 4
x => {
if (x > 0) {
return x * x;
}
else {
return (- x * x);
}
}
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
箭头函数和匿名函数有个明显的区别:箭头函数内部的this
是词法作用域,由上下文确定。
箭头函数完全修复了this
的指向,this
总是指向词法作用域,也就是外层调用者obj
普通函数的this作用域不确定。
由于this
在箭头函数中已经按照词法作用域绑定了,所以,用call()
或者apply()
调用箭头函数时,无法对this
进行绑定,即传入的第一个参数被忽略:
generator
与python生成器类似,有function*
定义
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
var f = fib(5);
// 每次执行函数,遇到yield返回数据,并暂停fib函数
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}
for (var x of fib(10)) {
console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}
标准对象
一切都是对象
typeof 123; // 'number'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
包装对象
不要使用包装对象!
typeof new Number(123); // 'object'
new Number(123) === 123; // false
typeof new Boolean(true); // 'object'
new Boolean(true) === true; // false
typeof new String('str'); // 'object'
new String('str') === 'str'; // false
- 不要使用
new Number()
、new Boolean()
、new String()
创建包装对象; - 用
parseInt()
或parseFloat()
来转换任意类型到number
; - 用
String()
来转换任意类型到string
,或者直接调用某个对象的toString()
方法; - 通常不必把任意类型转换为
boolean
再判断,因为可以直接写if (myVar) {...}
; typeof
操作符可以判断出number
、boolean
、string
、function
和undefined
;- 判断
Array
要使用Array.isArray(arr)
; - 判断
null
请使用myVar === null
; - 判断某个全局变量是否存在用
typeof window.myVar === 'undefined'
; - 函数内部判断某个变量是否存在用
typeof myVar === 'undefined'
。
number
对象调用toString()
报SyntaxError:
123.toString(); // SyntaxError
遇到这种情况,要特殊处理一下:
123..toString(); // '123', 注意是两个点!
(123).toString(); // '123'
JavaScript的Date对象月份值从0开始,牢记0=1月,1=2月,2=3月,……,11=12月。
正则 RegExp
var re1 = /ABC\-001/;
var re2 = new RegExp('ABC\\-001');
var re = /^\d{3}\-\d{3,8}$/;
re.test('010-12345'); // true
re.test('010-1234x'); // false
re.test('010 12345'); // false
// g 指代全局搜索
var r1 = /test/g;
var r2 = new RegExp('test', 'g');
JSON
序列化
JSON.stringify();
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
'middle-school': '\"W3C\" Middle School',
skills: ['JavaScript', 'Java', 'Python', 'Lisp']
};
JSON.stringify(xiaoming, null, ' '); // 第二个参数为指定键值(如 ['name','age']),第三个为按缩进输出。
// 还可以传入一个函数,将所有值变大写
function convert(key, value) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value;
}
JSON.stringify(xiaoming, convert, ' ');
// 还可以给定对象一个序列化方法
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
skills: ['JavaScript', 'Java', 'Python', 'Lisp'],
toJSON: function () {
return { // 只输出name和age,并且改变了key:
'Name': this.name,
'Age': this.age
};
}
};
反序列化
JSON.parse();
var obj = JSON.parse('{"name":"小明","age":14}', function (key, value) {
if (key === 'name') {
return value + '同学';
}
return value;
});
面向对象
JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。
prototype为函数所有,指向修改对象原型
proto 为对象所有,指向原型链
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
var xiaoming = {
name: '小明'
};
xiaoming.__proto__ = Student; // 此为原理,不可使用此方法创建对象
xiaoming.name; // '小明'
xiaoming.run(); // 小明 is running...
创建对象应使用 Object.create(obj)
var xiaoming = Object.create(Student); // 得到xiaoming对象,没有属性
xiaoming.__proto__ === Student; // true
var Student = {
name:'Student',
run: function(){
console.log(this.name + 'is running');
}
};
a = new Object(Student); // a 就是 Student
b = Object.create(Student); // b 是以Student为原型的对象
// 重点来了
a === Student; // true
a.__proto__ === Student.__proto__; //true
b.__proto__ === Student; //true
用obj.xxx
访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype
对象,最后,如果还没有找到,就只能返回undefined
。
var arr = [1, 2, 3];
其原型链是:
arr ----> Array.prototype ----> Object.prototype ----> null
Array.prototype定义了indexOf()、shift()等方法,因此你可以在所有的Array对象上直接调用这些方法。
function foo() {
return 0;
}
foo ----> Function.prototype ----> Object.prototype ----> null
由于Function.prototype定义了apply()等方法,因此,所有函数都可以调用apply()方法。
构造函数
function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
// 一个普通函数,用 new 调用并返回一个对象。
var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
xiaoming.__proto__ === Student.prototype // true
Student.prototype.constructor === xiaoming.constructor // true
xiaoming.__proto__ ----> Student.prototype ----> Object.prototype ----> null
如果不写new,这就是一个普通函数,它返回undefined。但是,如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;
constructor
// 用new Student()创建的对象还从原型上获得了一个constructor属性,它指向函数Student本身:
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
xiaoming instanceof Student; // true
红色箭头是原型链。注意,Student.prototype
指向的对象就是xiaoming
、xiaohong
的原型对象,这个原型对象自己还有个属性constructor
,指向Student
函数本身。
另外,函数Student
恰好有个属性prototype
指向xiaoming
、xiaohong
的原型对象,但是xiaoming
、xiaohong
这些对象可没有prototype
这个属性,不过可以用__proto__
这个非标准用法来查看。
共享方法
现在我们就认为xiaoming
、xiaohong
这些对象“继承”自Student
。
xiaoming.name; // '小明'
xiaohong.name; // '小红'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false
xiaoming
和xiaohong
各自的name
不同,这是对的,否则我们无法区分谁是谁了。
xiaoming
和xiaohong
各自的hello
是一个函数,但它们是两个不同的函数,虽然函数名称和代码都是相同的!
如果我们通过new Student()
创建了很多对象,这些对象的hello
函数实际上只需要共享同一个函数就可以了,这样可以节省很多内存。
要让创建的对象共享一个hello
函数,根据对象的属性查找原则,我们只要把hello
函数移动到xiaoming
、xiaohong
这些对象共同的原型上就可以了,也就是Student.prototype
:
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
忘记写 new
在strict模式下,this.name = name
将报错,因为this
绑定为undefined
,在非strict模式下,this.name = name
不报错,因为this
绑定为window
,于是无意间创建了全局变量name
,并且返回undefined
,这个结果更糟糕。
调用构造函数千万不要忘记写new
。为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写,这样,一些语法检查工具如 jslint 将可以帮你检测到漏写的new
。
最后,我们还可以编写一个createStudent()
函数,在内部封装所有的new
操作。一个常用的编程模式像这样:
function Student(props) {
this.name = props.name || '匿名'; // 默认值为'匿名'
this.grade = props.grade || 1; // 默认值为1
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
function createStudent(props) {
return new Student(props || {})
}
这个createStudent()
函数有几个巨大的优点:一是不需要new
来调用,二是参数非常灵活,可以不传,也可以这么传:
var xiaoming = createStudent({
name: '小明'
});
xiaoming.grade; // 1
如果创建的对象有很多属性,我们只需要传递需要的某些属性,剩下的属性可以用默认值。由于参数是一个Object,我们无需记忆参数的顺序。如果恰好从JSON
拿到了一个对象,就可以直接创建出xiaoming
。
>原型继承<
继承链:new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null
// PrimaryStudent构造函数:
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 空函数F:
function F() {
}
// 把F的原型指向Student.prototype:
F.prototype = Student.prototype;
// 把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F();
// 把PrimaryStudent原型的构造函数修复为PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;
// 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
// 创建xiaoming:
var xiaoming = new PrimaryStudent({
name: '小明',
grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2
// 验证原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true
// 验证继承关系:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true
- 定义新的构造函数,并在内部用
call()
调用希望“继承”的构造函数,并绑定this
; - 借助中间函数
F
实现原型链继承,最好通过封装的inherits
函数完成; - 继续在新的构造函数的原型上定义新方法。
class继承
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student('小明');
xiaoming.hello();
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
ES6引入的class
和原有的JavaScript原型继承没有任何区别,class
的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class
的好处就是极大地简化了原型链代码。
需要一个工具把class
代码转换为传统的prototype
代码,可以试试Babel这个工具。
Ajax
var request = new ActiveXObject('Microsoft.XMLHTTP'); // 新建Microsoft.XMLHTTP对象,用于低版本IE
var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象
request.onreadystatechange = function () {
// 状态发生变化时,函数被回调
}
request.open('GET', '/api/categories');
request.send();
jQuery
ajax(url, settings)
函数需要接收一个URL和一个可选的settings
对象,常用的选项如下:
- async:是否异步执行AJAX请求,默认为
true
,千万不要指定为false
; - method:发送的Method,缺省为
'GET'
,可指定为'POST'
、'PUT'
等; - contentType:发送POST请求的格式,默认值为
'application/x-www-form-urlencoded; charset=UTF-8'
,也可以指定为text/plain
、application/json
; - data:发送的数据,可以是字符串、数组或object。如果是GET请求,data将被转换成query附加到URL上,如果是POST请求,根据contentType把data序列化成合适的格式;
- headers:发送的额外的HTTP头,必须是一个object;
- dataType:接收的数据格式,可以指定为
'html'
、'xml'
、'json'
、'text'
等,缺省情况下根据响应的Content-Type
猜测。
var jqxhr = $.ajax('/api/categories', {
dataType: 'json'
});
var jqxhr = $.ajax('/api/categories', {
dataType: 'json'
}).done(function (data) {
ajaxLog('成功, 收到的数据: ' + JSON.stringify(data));
}).fail(function (xhr, status) {
ajaxLog('失败: ' + xhr.status + ', 原因: ' + status);
}).always(function () {
ajaxLog('请求完成: 无论成功或失败都会调用');
});
var jqxhr = $.get('/path/to/resource', {
name: 'Bob Lee',
check: 1
});
var jqxhr = $.post('/path/to/resource', {
name: 'Bob Lee',
check: 1
});
var jqxhr = $.getJSON('/path/to/resource', {
name: 'Bob Lee',
check: 1
}).done(function (data) {
// data已经被解析为JSON对象了
});
如果需要使用JSONP,可以在ajax()
中设置jsonp: 'callback'
,让jQuery实现JSONP跨域加载数据。
同源策略
默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。
-
通过在同源域名下架设一个代理服务器来转发,JavaScript负责把请求发送到代理服务器:
'/proxy?url=http://www.sina.com.cn'
-
JSONP,只能用GET请求。这种方式跨域实际上是利用了浏览器允许跨域引用JavaScript资源
<script src="http://example.com/abc.js?callback=foo"></script> 返回数据:foo('data');
CORS
HTML5 中新的跨域策略(Cross-Origin Resource Sharing)
Origin表示本域,也就是浏览器当前页面的域。当JavaScript向外域(如sina.com)发起请求后,浏览器收到响应后,首先检查Access-Control-Allow-Origin
是否包含本域,如果是,则此次跨域请求成功,如果不是,则请求失败,JavaScript将无法获取到响应的任何数据。
假设本域是my.com
,外域是sina.com
,只要响应头Access-Control-Allow-Origin
为http://my.com
,或者是*
,本次请求就可以成功。
可见,跨域能否成功,取决于对方服务器是否愿意给你设置一个正确的Access-Control-Allow-Origin
,决定权始终在对方手中。
上面这种跨域请求,称之为“简单请求”。简单请求包括GET、HEAD和POST(POST的Content-Type类型 仅限application/x-www-form-urlencoded
、multipart/form-data
和text/plain
),并且不能出现任何自定义头(例如,X-Custom: 12345
),通常能满足90%的需求。
简单请求包括GET、HEAD和POST(POST的Content-Type类型 仅限application/x-www-form-urlencoded
、multipart/form-data
和text/plain
),并且不能出现任何自定义头(例如,X-Custom: 12345
)
对于PUT、DELETE以及其他类型如application/json
的POST请求,在发送AJAX请求之前,浏览器会先发送一个OPTIONS
请求(称为preflighted请求)到这个URL上,询问目标服务器是否接受:
OPTIONS /path/to/resource HTTP/1.1
Host: bar.com
Origin: http://my.com
Access-Control-Request-Method: POST
服务器必须响应并明确指出允许的Method:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Max-Age: 86400
浏览器确认服务器响应的Access-Control-Allow-Methods
头确实包含将要发送的AJAX请求的Method,才会继续发送AJAX,否则,抛出一个错误。
由于以POST
、PUT
方式传送JSON格式的数据在REST中很常见,所以要跨域正确处理POST
和PUT
请求,服务器端必须正确响应OPTIONS
请求。
Promise
var p1 = new Promise(test);
var p2 = p1.then(function (result) { // 成功时执行
console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) { // 失败时执行
console.log('失败:' + reason);
});
// 等同于
new Promise(test).then(function (result) {
console.log('成功:' + result);
}).catch(function (reason) {
console.log('失败:' + reason);
});
job1.then(job2).then(job3).catch(handleError); // job123都是Promise对象
// 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);
});
}
// p1 p2 都是ajax函数
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
// 同时发出请求,只需要获得先返回的结果即可
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
underscore
正如jQuery统一了不同浏览器之间的DOM操作的差异,让我们可以简单地对DOM进行操作,underscore则提供了一套完善的函数式编程的接口,让我们更方便地在JavaScript中实现函数式编程。
jQuery在加载时,会把自身绑定到唯一的全局变量$
上,underscore与其类似,会把自身绑定到唯一的全局变量_
上。
_.map([1, 2, 3], (x) => x * x); // [1, 4, 9]
_.map({ a: 1, b: 2, c: 3 }, (v, k) => k + '=' + v); // ['a=1', 'b=2', 'c=3']
node.js
// 判断使用环境
if (typeof(window) === 'undefined') {
console.log('node.js');
} else {
console.log('browser');
}
模块
要在模块中对外输出变量,用:
module.exports = variable;
要引入其他模块输出的对象,用:
var foo = require('other_module');
实现原理:
// 准备module对象:
var module = {
id: 'hello',
exports: {}
};
var load = function (module) {
// 读取的hello.js代码:
function greet(name) {
console.log('Hello, ' + name + '!');
}
module.exports = greet;
// hello.js代码结束
return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);
可见,变量module
是Node在加载js文件前准备的一个变量,并将其传入加载函数,我们在hello.js
中可以直接使用变量module
原因就在于它实际上是函数的一个参数:
module.exports = greet;
通过把参数module
传递给load()
函数,hello.js
就顺利地把一个变量传递给了Node执行环境,Node会把module
变量保存到某个地方。
由于Node保存了所有导入的module
,当我们用require()
获取module时,Node找到对应的module
,把这个module
的exports
变量返回,这样,另一个模块就顺利拿到了模块的输出:
var greet = require('./hello');
-
global nodejs的唯一全局对象
-
prosess 代表当前的唯一单进程
// process.nextTick()将在下一轮事件循环中调用: process.nextTick(function () { console.log('nextTick callback!'); });
Node.js进程本身的事件就由
process
对象来处理。如果我们响应exit
事件,就可以在程序即将退出时执行某个回调函数:// 程序即将退出时的回调函数: process.on('exit', function (code) { console.log('about to exit with code: ' + code); });
fs 文件系统
'use strict';
var fs = require('fs');
// 异步读文件
fs.readFile('sample.txt', 'utf-8', function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
// 同步读文件
var data = fs.readFileSync('sample.txt', 'utf-8');
console.log(data);
// 异步写
var data = 'Hello, Node.js';
fs.writeFile('output.txt', data, function (err) {
if (err) {
console.log(err);
} else {
console.log('ok.');
}
});
// 同步写
fs.writeFileSync('output.txt', data);
fs.stat(); // 文件状态
// 服务器端必须使用异步
http
'use strict';
// 导入http模块:
var http = require('http');
// 创建http server,并传入回调函数:
var server = http.createServer(function (request, response) {
// 回调函数接收request和response对象,
// 获得HTTP请求的method和url:
console.log(request.method + ': ' + request.url);
// 将HTTP响应200写入response, 同时设置Content-Type: text/html:
response.writeHead(200, {'Content-Type': 'text/html'});
// 将HTTP响应的HTML内容写入response:
response.end('<h1>Hello world!</h1>');
});
// 让服务器监听8080端口:
server.listen(8080);
console.log('Server is running at http://127.0.0.1:8080/');
'use strict';
var
fs = require('fs'),
url = require('url'),
path = require('path'),
http = require('http');
// 从命令行参数获取root目录,默认是当前目录:
var root = path.resolve(process.argv[2] || '.');
console.log('Static root dir: ' + root);
// 创建服务器:
var server = http.createServer(function (request, response) {
// 获得URL的path,类似 '/css/bootstrap.css':
var pathname = url.parse(request.url).pathname;
// 获得对应的本地文件路径,类似 '/srv/www/css/bootstrap.css':
var filepath = path.join(root, pathname);
// 获取文件状态:
fs.stat(filepath, function (err, stats) {
if (!err && stats.isFile()) {
// 没有出错并且文件存在:
console.log('200 ' + request.url);
// 发送200响应:
response.writeHead(200);
// 将文件流导向response:
fs.createReadStream(filepath).pipe(response);
} else {
// 出错了或者文件不存在:
console.log('404 ' + request.url);
// 发送404响应:
response.writeHead(404);
response.end('404 Not Found');
}
});
});
server.listen(8080);
console.log('Server is running at http://127.0.0.1:8080/');