javascript

javascript

数据结构

  1. 数字

  2. 字符串

    1. '\x41'; // 完全等同于 'A'
    2. '\u4e2d\u6587'; // 完全等同于 '中文'
    3. `用于包含多行字段
  3. 布尔值

  4. 运算符, == 与 === 不同,==会强制转换,1='1'为true;NaN与所有值都不同,包括自己,只有通过isNaN()函数才能得到true

  5. null 为空;undefine 为未定义,一般不用,只用于判断函数是否传参,访问对象空属性时返回undefine。

  6. 数组,var a = new Array(); || var a = [1, 3, 'abc', null, true];

  7. 对象,即无序键值对,键一定为字符串,

    1. obj.key=value; // 新增属性

    2. delete obj.key; // 删除属性

    3. 'key' in obj; // 检测是否有此属性

    4. xiaoming.hasOwnProperty('name'); // true 判断此属性是否是继承的
      
  8. Map,键值对,键不一定为字符串(ES6)

    1. var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
      
  9. Set (ES6)

变量

  1. 一个变量没有通过var申明,该变量自动被申明为全局变量;否则为局部变量
  2. strict模式:必须使用var声明变量。在js文件第一行写 'use strict'; 不支持的将此视为一个字符串。
  3. iterable:ArrayMapSet都属于iterable
    1. for ... 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包括在内,但Arraylength属性却不包括在内。

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);
});

函数

  1. 允许传入任意个参数而不影响调用

  2. arguments,

    1. 只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。
    2. 类似Array但它不是一个Array
    3. 即使函数没定义形参,也可通过其获得参数
  3. rest 参数:

    function foo(a, b, ...rest){	// 通过rest获得可变参数
    }
    
  4. 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指向undefinedwindow,不过,我们还是可以控制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;
});

其他

  1. every(fun) 判断是否所有元素满足条件
  2. forEach() 把每个元素依次作用于传入的函数,但不会返回新的数组
  3. 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操作符可以判断出numberbooleanstringfunctionundefined
  • 判断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

JS的prototype和__proto__

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指向的对象就是xiaomingxiaohong的原型对象,这个原型对象自己还有个属性constructor,指向Student函数本身。

另外,函数Student恰好有个属性prototype指向xiaomingxiaohong的原型对象,但是xiaomingxiaohong这些对象可没有prototype这个属性,不过可以用__proto__这个非标准用法来查看。

共享方法

现在我们就认为xiaomingxiaohong这些对象“继承”自Student

xiaoming.name; // '小明'
xiaohong.name; // '小红'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false

xiaomingxiaohong各自的name不同,这是对的,否则我们无法区分谁是谁了。

xiaomingxiaohong各自的hello是一个函数,但它们是两个不同的函数,虽然函数名称和代码都是相同的!

如果我们通过new Student()创建了很多对象,这些对象的hello函数实际上只需要共享同一个函数就可以了,这样可以节省很多内存。

要让创建的对象共享一个hello函数,根据对象的属性查找原则,我们只要把hello函数移动到xiaomingxiaohong这些对象共同的原型上就可以了,也就是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

继承原型链

继承原型链

  1. 定义新的构造函数,并在内部用call()调用希望“继承”的构造函数,并绑定this
  2. 借助中间函数F实现原型链继承,最好通过封装的inherits函数完成;
  3. 继续在新的构造函数的原型上定义新方法。

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/plainapplication/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的域名必须和当前页面完全一致。

  1. 通过在同源域名下架设一个代理服务器来转发,JavaScript负责把请求发送到代理服务器:

    '/proxy?url=http://www.sina.com.cn'
    
  2. 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将无法获取到响应的任何数据。

CORS请求

假设本域是my.com,外域是sina.com,只要响应头Access-Control-Allow-Originhttp://my.com,或者是*,本次请求就可以成功。

可见,跨域能否成功,取决于对方服务器是否愿意给你设置一个正确的Access-Control-Allow-Origin,决定权始终在对方手中。

上面这种跨域请求,称之为“简单请求”。简单请求包括GET、HEAD和POST(POST的Content-Type类型 仅限application/x-www-form-urlencodedmultipart/form-datatext/plain),并且不能出现任何自定义头(例如,X-Custom: 12345),通常能满足90%的需求。

简单请求包括GET、HEAD和POST(POST的Content-Type类型 仅限application/x-www-form-urlencodedmultipart/form-datatext/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,否则,抛出一个错误。

由于以POSTPUT方式传送JSON格式的数据在REST中很常见,所以要跨域正确处理POSTPUT请求,服务器端必须正确响应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,把这个moduleexports变量返回,这样,另一个模块就顺利拿到了模块的输出:

var greet = require('./hello');
  1. global nodejs的唯一全局对象

  2. 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/');
posted @ 2021-01-18 16:56  某某人8265  阅读(74)  评论(0编辑  收藏  举报