1.
JavaScript的函数亦是对象。
// 第一种方法:函数声明
function abs() {
if (x >= 0) {
return x;
} else {
return -x;
}
}
abs(10); // 返回10
// 第二种方法:函数表达式
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
abs(-9); // 返回9
第一种定义的abs()函数实际为一个函数对象,而函数名abs可以视为指向该函数的变量。
第二种function (x) { ... } 为一个匿名函数,然后把这个匿名函数赋值给了变量abs,所以通过变量abs可以调用该函数。
2.
小心return语句。
// 第一种方式
function testReturn(a) {
return
a;
}
var b = testReturn(1);
console.log(b); // 输出undefined
// 第二种方式
function testReturn(a) {
return a;
}
var b = testReturn(1);
console.log(b); // 输出 1;
由于JavaScript引擎在行末自动添加分号的机制,第一种方式种return后会自动变为return;后面语句就无法执行到,所以输出undefined;
3.
变量提升
function foo() {
var x = 'Hello, ' + y;
console.log(x);
var y = 'world';
}
foo(); // 输出 Hello, undefined
JavaScript函数的定义会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,但不会提示升变量的赋值。相当于:
function foo() {
var y; // 提升变量的声明,此时y为undefined
var x = 'Hello, ' + y;
console.log(x);
y = 'world';
}
foo(); // 输出 Hello, undefined
4.
高阶函数(Higher-order function)
function add(x, y, f) {
return f(x) + f(y);
}
console.log(add(-5, 6, Math.abs)); // 输出11
函数的参数能够接收别的函数,这种函数即高阶函数。
// 示例 1
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4];
var res = arr.map(pow);
console.log(res); // [1, 4, 9, 16]
// 示例 2
var arr = [1, 2, 3, 4];
var res = arr.map(String);
console.log(res); // ["1", "2", "3", "4"]
5.
闭包
function lazySum(arr) {
var sum = function() {
return arr.reduce(function(x,y) {
return x + y;
});
}
return sum;
}
var arr = [1, 2, 3, 4];
var f1 = lazySum(arr);
var f2 = lazySum(arr);
console.log(f1()); // 输出 10
console.log(f2()); // 输出 10
console.log(f1 === f2); // false
1.函数作为返回值;
2.当lazySum返回函数sum时,相关参数和变量都保存在返回的函数中,此时形成了闭包(Closure);
3.调用lazySum()时,每次都返回一个新的函数;
4.返回的函数并没有立即执行,直到调用了f1()才执行;
// 示例 1
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];
console.log(f1()); \\ 16
console.log(f2()); \\ 16
console.log(f3()); \\ 16
返回的函数中引用了变量 i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量 i 已变成了4,因此最终结果16。
\\ 示例 2
function count() {
var arr = [];
for (var i = 1; i <= 3; i++) {
arr.push((function(n) {
return function () {
return n * n;
}
})(i))
}
return arr;
}
var res = count();
var f1 = res[0];
var f2 = res[1];
var f3 = res[2];
console.log(f1()); \\ 1
console.log(f2()); \\ 4
console.log(f3()); \\ 9
用到了一个“创建一个匿名函数并立即执行”语句。
无论该循环变量后续如何更改,已绑定到函数参数的值不变。
注:返回闭包时牢记:返回函数不要引用任何循环变量或者后续会发生变化的变量。如果一定要引用循环变量,方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。
6.
Generator
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
var c = yield a;
console.log("c is " + c);
[a, b] = [b, a + b];
n ++;
}
return;
}
var f = fib(3);
// 方法一
console.log(f.next(1)); // {value: 0, done: false}
console.log(f.next(2)); // c is 2 {value: 1, done: false}
console.log(f.next(3)); // c is 3 {value: 1, done: false}
console.log(f.next(4)); // c is 4 {value: undefined, done: false}
// 方法二
for (var item of f) {
console.log(item); // 0 1 1
}
① yield 关键字用来暂停和继续执行一个生成器函数。当外部调用生成器的next()方法时,yield关键字右侧的表达式才会执行。
② 每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束。如果done为true,则value的值为return 的返回值。
③ 对于 var foo = yield expression;yield 左侧变量foo的值将在下一次调用next()方法时获得,并且等于调用next方法的参数。
7.
Date
var now = new Date();
console.log(now); // Tue Jun 05 2018 10:18:12 GMT+0800 (中国标准时间)
console.log(now.getFullYear()); // 2018, 年份
console.log(now.getMonth()); // 5, 表示6月,0-11 表示 1-12月
console.log(now.getDate()); // 5, 表示5号
console.log(now.getDay()); // 2, 表示周二
console.log(now.getHours()); // 10, 表示10点,24小时制
console.log(now.getMinutes()); //18, 分钟
console.log(now.getSeconds()); // 12, 秒
console.log(now.getMilliseconds()); //405 , 毫秒
console.log(now.getTime()); // 1528165092405, 时间戳
Date 对象月份值从0开始,取值范围[0, 11],依次表示 1-12月。
// 创建指定日期
// 方法一
var date1 = new Date(2018,5,5,10,15,0);
console.log(date1); // Tue Jun 05 2018 10:15:00 GMT+0800 (中国标准时间)
// 方法二
var d = Date.parse('2015-06-24T19:49:22.875+08:00'); // 参数须符合ISO 8601格式的字符串,返回值为时间戳
var date2 = new Date(d);
console.log(date2); // Wed Jun 24 2015 19:49:22 GMT+0800 (中国标准时间)
// 方法三
var date3 = new Date(1528165092405);
console.log(date3); // Tue Jun 05 2018 10:18:12 GMT+0800 (中国标准时间)
8.
JSON
/*
* 序列化
*/
var lucy = {
name: 'Lucy',
age: 17,
gender: false,
height: 165,
grade: 2,
skills: ['JavaScript', 'Java', 'Python']
};
// 将对象序列化成JSON格式的字符串
var s1 = JSON.stringify(lucy);
console.log(s1);
// 控制格式,按缩进输出
var s2 = JSON.stringify(lucy, null, ' ');
console.log(s2);
// 筛选对象的键值
var s3 = JSON.stringify(lucy, ['name', 'skills'], ' ');
console.log(s3);
// 传入函数,对象的每个键值对都会被函数先处理
function convert(key, value) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value;
}
var s4 = JSON.stringify(lucy, convert, ' ');
console.log(s4);
// toJSON
var andy = {
name: 'andy',
age: 17,
gender: true,
height: 175,
skills: ['JavaScript', 'Java', 'Python'],
toJSON: function() {
return {
'Name': this.name,
'Age': this.age
}
}
}
var s5 = JSON.stringify(andy);
console.log(s5);
/*
* 反序列化
*/
// 将一个JSON格式的字符串变成一个JavaScript对象
console.log(JSON.parse('[1, 2, 3, true]')); // [1, 2, 3, true]
console.log(JSON.parse('{"name": "andy", "age": "17"}')); // {name: "andy", age: "17"}
console.log(JSON.parse('true')); // true
console.log(JSON.parse('123.45')); // 123.45
// 传入函数用来转换解析出的属性
var obj = JSON.parse('{"name": "小明", "age": "17"}', function(key, value) {
if (key === 'name') {
return value + '同学';
}
return value;
});
console.log(JSON.stringify(obj)); // {"name":"小明同学","age":"17"}
9.
面向对象
① JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。
// 示例 1
var arr = [1, 2, 3];
原型链为:
arr ---- > Array.prototype ---- > Object.prototype ---- > null
// 示例 2
function foo() { ... }
原型链为:
foo ---- > Function.prototype ---- > Object.prototype ---- > null
② 构造函数
/*
*创建对象
*/
// 方法一
var obj = { ... }
// 方法二 构造函数
function Student(name) {
this.name = name;
this.hello = function() {
console.log('Hello ' + this.name);
}
}
var andy = new Student('Andy');
console.log(andy.name); // Andy
andy.hello(); // Hello Andy
andy.constructor === Student.prototype.construtor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(andy) === Student.prototype; // true
andy instanceof Student; // true;
注: 如果不用关键字new,函数Student即一个普通函数,返回undefined。用new去调用之后,变成一个构造函数,绑定的this指向新创建的对象,并默认返回this,无需在函数体末尾写return this;
- andy原型链为:
andy ---- > Student.prototype ---- > Object.prototype ---- > null- 用 new Student() 创建的对象从原型上获得一个constructor属性,指向函数Student本身。
③ 原型继承
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function() {
console.log('Hello' + this.name);
}
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 实现原型继承链
inherits(PrimaryStudent, Student);
var andy = new PrimaryStudent({
name: 'Andy',
grade: '2'
});
console.log(andy.name); // Andy
console.log(andy.grade); // 2
// 验证原型
console.log(andy.__proto__ === PrimaryStudent.prototype); // true
console.log(andy.__proto__.__proto__ === Student.prototype); // true
// 验证继承关系
console.log(andy instanceof PrimaryStudent); // true
console.log(andy instanceof Student); // true
④ class 继承
// Student 类
class Student {
constructor(name) {
this.name = name;
}
hello() {
console.log('Hello ' + this.name);
}
}
var andy = new Student('Andy');
andy.hello(); // Hello Andy
// 实现继承
class PrimaryStudent extends Student {
constructor(name, grade){
super(name); // super调用父类的构造函数
this.grade = grade;
}
myGrade() {
console.log(this.name + ' at grade ' + this.grade);
}
}
var lucy = new PrimaryStudent('Lucy', 1);
lucy.myGrade(); // Lucy at grade 1
10.
Promise
new Promise(function(resolve, reject) {
console.log('start promise...');
var t = Math.random() * 2;
console.log("Timeout is " + t );
setTimeout(function(){
if (t <1) {
console.log('call resolve()...');
resolve('200 OK');
} else {
console.log('call reject()...');
reject('timeout in ' + t);
}
}, t * 1000);
}).then(function(r) {
console.log('Done: ' + r);
}).catch(function(reason) {
console.log('Failed: ' + reason);
});
// Promise.all() --- 并行执行异步任务
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then
Promise.all([p1, p2]).then(function(results) {
console.log(results); // 输出 ["P1", "P2"]
});
// 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(results) {
console.log(results); // P1
});
11.
错误处理
/*
* try ... catch ... finally
*/
var r1, r2, s = null;
try {
r1 = s.length; // 产生错误
r2 = 100; // 不会执行
} catch (e) {
console.log('出错了:' + e);
// Error 派生 TypeError、ReferenceError
if (e instanceof TypeError) {
console.log('Type Error'); // 输出 Type Error
} else if (e instanceof Error) {
console.log(e.message);
}
} finally {
console.log('finally always execute'); // 无论是否有错误,finally一定会被执行
}
console.log('r1= ' + r1);
console.log('r2 = ' + r2);
/*
* 抛出错误
*/
var num;
try {
num = prompt('输入一个数字');
if (isNaN(parseInt(num))) {
throw new Error('输入错误'); // 抛出异常
}
console.log(num + '*' + num + '=' + num * num);
} catch(e) {
// 当输入的不为数字时,捕获抛出的异常
console.log('出错了:' + e); // 出错了:Error: 输入错误
}
注: 异步代码和事件绑定代码处,无法捕获事件处理函数的错误。
12.
underscore
jQuery在加载时,会把自身绑定到唯一的全局变量$上,underscore与其类似,会把自身绑定到唯一的全局变量_上。
- Collections
① map/filter
var obj = {
name: 'Andy',
age: 17,
grade: 1
}
var obj1 = _.map(obj, function(value, key){
return value;
});
console.log(JSON.stringify(obj1)); // ["Andy", 17 ,1]
var obj2 = _.filter(obj, function(value, key) {
return typeof value === 'string';
})
console.log(JSON.stringify(obj2)); // ["Andy"]
② every/some
// _.every() -- 所有元素满足条件返回true;_.some() -- 有一个元素满足条件,返回true
var arr = [1, 4, 7, -3, -9];
var flag1 = _.every(arr, (x) => x > 0);
var flag2 = _.some(arr, (x) => x > 0);
console.log(flag1); // false
console.log(flag2); // true
③ max/min
var arr = [1, 2, 3, 4];
var obj = {
a: 1,
b: 2,
c: 3
};
console.log(_.max(arr)); // 4
console.log(_.min(arr)); // 1
// 空数组会返回-Infinity和Infinity
console.log(_.max([])); // -Infinity
console.log(_.min([])); // Infinity
// 对象会忽略键名
console.log(_.max(obj)); // 3;
console.log(_.min(obj)); // 1
④ groupBy
// 把集合的元素按照key归类, key由传入的函数返回
var scores = [20, 81, 75, 40, 91, 59, 77, 66, 75, 88, 99];
var groups = _.groupBy(scores, function(x) {
if (x < 60) {
return 'C';
} else if (x < 80) {
return 'B';
} else {
return 'A';
}
});
console.log(groups); // {A: [81, 91, 88, 99], B: [75, 77, 66, 75], C: [20, 40, 59]}
⑤ shuffle/sample
var arr = [1, 2, 3, 4, 5, 6];
// shuffle() 用洗牌算法随机打乱一个集合(每次结果都不一样)
// shuffle(list)
console.log(_.shuffle(arr)); // [2, 1, 3, 6, 4, 5]
// sample() 随机选择一个或多个元素(每次结果都不一样)
// sample(list, n)
console.log(_.sample(arr)); // 5
console.log(_.sample(arr, 3)); // [2, 5, 3]
- Arrays
① first / last
var arr = [1, 2, 3, 4];
// 取第一个元素
console.log(_.first(arr)); // 1
// 取最后一个元素
console.log(_.last(arr)); // 4
② flatten
// 无论嵌套了多少个Array,最后变成一个一维数组
var arr = [1, [2], [3, [[4], [5]]]];
console.log(_.flatten(arr)); // [1, 2, 3, 4, 5]
③ zip / unzip
// zip()把两个或多个数组的所有元素按索引对齐,然后按索引合并成新数组
var names = ["Andy", "Lucy", "Lily"];
var scores = [90, 96, 98];
console.log(_.zip(names, scores)); // [["Andy", 90], ["Lucy", 96], ["Lily], 98]
// unzip 反过来
var namesAndScores = [["Any", 90], ["Lucy", 96], ["Lily", 98]];
console.log(_.unzip(namesAndScores)); // [["Andy", "Lucy", "Lily"], [90, 96, 98]]
④ object
// object() 变成一个对象
var names = ["Andy", "Lucy", "Lily"];
var scores = [90, 96, 98];
console.log(_.object(names, scores)); // {Andy: 90, Lucy: 96, Lily: 98}
⑤ range
// range(start, end, step) 快速生成一个序列
// 从0开始小于10:
console.log(_.range(10)); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// 从1开始小于11:
console.log(_.range(1, 11)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 从0开始小于30,步长5:
console.log(_.range(0, 30, 5)); // [0, 5, 10, 15, 20, 25]
// 从0开始大于-10,步长-1:
console.log(_.range(0, -10, -1)); // [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
- Function
① bind
var s = ' Hello ';
// 直接调用
console.log(s.trim()); // Hello
// 用一个变量保存起来
var fn = s.trim;
console.log(fn.call(s)); // Hello
// 用bind把对象s绑定在f()的指针上
var f = _.bind(s.trim, s);
console.log(f()); // Hello
② partial
// 为函数创建一个片函数 例如:2的y次方
var pow2N = _.partial(Math.pow, 2);
console.log(pow2N(3)); // 8
console.log(pow2N(5)); // 32
③ memoize
// 缓存上次的计算结果,如果调用的函数相同
var factorial = _.memoize(function(n) {
console.log('start calculate ' + n + '!...');
var s = 1, i = n;
while (i > 1) {
s = s * i;
i --;
}
console.log(n + '! = ' + s);
return s;
});
// 第一次调用
factorial(10); // start calculate 10!... 10! = 3628800
console.log(factorial(10)); // 仅输出 3628800 ;没有重新计算
factorial(9); // start calculate 9!... 9! = 362880
④ delay
// delay() 作用和 setTimeout() 一样
_.delay(console.log('2 秒后输出'), 2000); // 2 秒后输出
- Objects
① keys / allKeys
// keys()可以非常方便地返回一个object自身所有的key,但不包含从原型链继承下来的
function Student(name, age) {
this.name = name;
this.age = age;
}
var andy = new Student("Andy", 17);
console.log(_.keys(andy)); //["name", "age"]
// allKeys()除了object自身的key,还包含从原型链继承下来的
function Student(name, age) {
this.name = name;
this.age = age;
}
Student.prototype.school = "No.1 Middle School";
var lucy = new Student("Lucy", 17);
console.log(_.allKeys(lucy)); // ["name", "age", "school"]
② values
// values()返回object自身但不包含原型链继承的所有值
var obj = {
name: "Andy",
age: 17
};
console.log(_.values(obj)); // ["Andy", 17]
③ mapObject
var obj = {
a: 1,
b: 2,
c: 3
};
console.log( _.mapObject(obj, (v, k) => 100 + v)); // {a: 101, b: 102, c: 103}
④ invert
// 把对象的key-value作对话
var obj = {
a: 1,
b: 2,
c: 3
};
console.log(_.invert(obj)); // {1: 'a', 2: 'b', 3: 'c'}
⑤ extend / extendOwn
// extend()把多个object的key-value合并到第一个object并返回
// extendOwn()和extend()类似,但获取属性时忽略从原型链继承下来的属性。
var a = {
a: 1,
b: 2,
c: 3
};
_.extend(a, {d: 4}, {e: 5}, {f: 6, g: 7});
console.log(a); // {a: 1, b: 2, c: 3, d: 4, e: 5, f:6, g:7}
⑥ clone(浅拷贝)
// 复制一个object对象,把原有对象的所有属性都复制到新的对象中
var obj1 = {
a: 1,
b: 2,
c: 3
};
var obj2 = _.clone(obj1);
console.log(obj1); // {a: 1, b:2, c: 3}
console.log(obj2); // {a: 1, b:2, c: 3}
delete obj1.a; // 删除obj1中属性,不会影响obj2
console.log(obj1); // {b: 2, c: 3}
console.log(obj2); // {a: 1, b:2, c: 3}
⑦ isEqual
// 对两个object进行深度比较,如果内容完全相同,则返回true
// 比较对象
var o1 = { name: 'Bob', skills: { Java: 90, JavaScript: 99 }};
var o2 = { name: 'Bob', skills: { JavaScript: 99, Java: 90 }};
console.log(o1 === o2); // false
console.log(_.isEqual(o1, o2)); // true
// 比较数组
var arr1 = ['Bob', { skills: ['Java', 'JavaScript'] }];
var arr2 = ['Bob', { skills: ['Java', 'JavaScript'] }];
console.log(arr1 === arr2); // false
console.log(_.isEqual(arr1, arr2)); // true
⑧ chain
// 把对象包装成能进行链式调用的方法
var arr = [1, 4, 9 ,16, 25];
var res = _.chain(arr).map(Math.sqrt).filter(x => x % 2 === 1).value();
console.log(res); // [1, 3, 5]