学习浏览器的断开,单步执行等用法(相见恨晚、以后不用
console.log()
啦!)最好不要一行写两个语句。
要特别注意相等运算符
==
。JavaScript在设计时,有两种比较运算符:第一种是
==
比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;第二种是
===
比较,它不会自动转换数据类型,如果数据类型不一致,返回false
,如果一致,再比较。由于JavaScript这个设计缺陷,不要使用
==
比较,始终坚持使用===
比较。另一个例外是
NaN
这个特殊的Number与所有其他值都不相等,包括它自己:1NaN === NaN; // false唯一能判断
NaN
的方法是通过isNaN()
函数:1isNaN(NaN); // truenull和undefined
null
表示一个“空”的值,它和0
以及空字符串''
不同,0
是一个数值,''
表示长度为0的字符串,而null
表示“空”。在其他语言中,也有类似JavaScript的
null
的表示,例如Java也用null
,Swift用nil
,Python用None
表示。但是,在JavaScript中,还有一个和null
类似的undefined
,它表示“未定义”。JavaScript的设计者希望用
null
表示一个空的值,而undefined
表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用null
。undefined
仅仅在判断函数参数是否传递的情况下有用。对象里的键可以称为是对象的属性。可以直接用
object.key
动态语言与静态语言
这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。例如Java是静态语言,赋值语句如下:
12int a = 123; // a是整数类型变量,类型用int申明a = "ABC"; // 错误:不能把字符串赋给整型变量和静态语言相比,动态语言更灵活,就是这个原因。
字符串、数组
多行字符串
由于多行字符串用
\n
写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,用反引号 ` … ` 表示:123`这是一个多行字符串`;如果有很多变量需要连接,用
+
号就比较麻烦。ES6新增了一种模板字符串,表示方法和上面的多行字符串一样,但是它会自动替换字符串中的变量:1234var name = '小明';var age = 20;var message = `你好, ${name}, 你今年${age}岁了!`;alert(message);indexOf
indexOf()
会搜索指定字符串出现的位置:123var s = 'hello, world';s.indexOf('world'); // 返回7s.indexOf('World'); // 没有找到指定的子串,返回-1substring
substring()
返回指定索引区间的子串:123var s = 'hello, world's.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello's.substring(7); // 从索引7开始到结束,返回'world'请注意,如果通过索引赋值时,索引超过了范围,同样会引起
Array
大小的变化:123var arr = [1, 2, 3];arr[5] = 'x';arr; // arr变为[1, 2, 3, undefined, undefined, 'x']大多数其他编程语言不允许直接改变数组的大小,越界访问索引会报错。然而,JavaScript的
Array
却不会有任何错误。在编写代码时,不建议直接修改Array
的大小,访问索引时要确保索引不会越界。slice
slice()
就是对应String的substring()
版本,它截取Array
的部分元素,然后返回一个新的Array
:123var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C']arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']注意到
slice()
的起止参数包括开始索引,不包括结束索引。如果不给
slice()
传递任何参数,它就会从头到尾截取所有元素。利用这一点,我们可以很容易地复制一个Array
:1234var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];var aCopy = arr.slice();aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G']aCopy === arr; // false数组的堆栈用法:
push和pop
push()
向Array
的末尾添加若干元素,pop()
则把Array
的最后一个元素删除掉:123456789var arr = [1, 2];arr.push('A', 'B'); // 返回Array新的长度: 4arr; // [1, 2, 'A', 'B']arr.pop(); // pop()返回'B'arr; // [1, 2, 'A']arr.pop(); arr.pop(); arr.pop(); // 连续pop 3次arr; // []arr.pop(); // 空数组继续pop不会报错,而是返回undefinedarr; // []unshift和shift
如果要往
Array
的头部添加若干元素,使用unshift()
方法,shift()
方法则把Array
的第一个元素删掉:123456789var arr = [1, 2];arr.unshift('A', 'B'); // 返回Array新的长度: 4arr; // ['A', 'B', 1, 2]arr.shift(); // 'A'arr; // ['B', 1, 2]arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次arr; // []arr.shift(); // 空数组继续shift不会报错,而是返回undefinedarr; // []
sort排序
Reverse反序
splice
splice()
方法是修改Array
的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:12345678910var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];// 从索引2开始删除3个元素,然后再添加两个元素:arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']// 只删除,不添加:arr.splice(2, 2); // ['Google', 'Facebook']arr; // ['Microsoft', 'Apple', 'Oracle']// 只添加,不删除:arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']concat
concat()
方法把当前的Array
和另一个Array
连接起来,并返回一个新的Array
:1234var arr = ['A', 'B', 'C'];var added = arr.concat([1, 2, 3]);added; // ['A', 'B', 'C', 1, 2, 3]arr; // ['A', 'B', 'C']请注意,
concat()
方法并没有修改当前Array
,而是返回了一个新的Array
。实际上,
concat()
方法可以接收任意个元素和Array
,并且自动把Array
拆开,然后全部添加到新的Array
里:12var arr = ['A', 'B', 'C'];arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4]join方法和ruby的类似
访问属性是通过
.
操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用''
括起来:1234var xiaohong = {name: '小红','middle-school': 'No.1 Middle School'};如果我们要检测
xiaoming
是否拥有某一属性,可以用in
操作符:12345678910var xiaoming = {name: '小明',birth: 1990,school: 'No.1 Middle School',height: 1.70,weight: 65,score: null};'name' in xiaoming; // true'grade' in xiaoming; // false不过要小心,如果
in
判断一个属性存在,这个属性不一定是xiaoming
的,它可能是xiaoming
继承得到的:1'toString' in xiaoming; // true因为
toString
定义在object
对象中,而所有对象最终都会在原型链上指向object
,所以xiaoming
也拥有toString
属性。要判断一个属性是否是
xiaoming
自身拥有的,而不是继承得到的,可以用hasOwnProperty()
方法:12345var xiaoming = {name: '小明'};xiaoming.hasOwnProperty('name'); // truexiaoming.hasOwnProperty('toString'); // false可以省略{}在选择性语句中:var age = 20;if (age >= 18) { // 如果age >= 18为true,则执行if语句块 alert(‘adult’);} else { // 否则执行else语句块 alert(‘teenager’);}
其中
else
语句是可选的。如果语句块只包含一条语句,那么可以省略{}
:12345var age = 20;if (age >= 18)alert('adult');elsealert('teenager');(但,不能超过一个语句!!!)
JavaScript把
null
、undefined
、0
、NaN
和空字符串''
视为false
,其他值一概视为true
prompt可以直接通过浏览器提示框让用户输入!!!
循环For:
JavaScript的循环有两种,一种是
for
循环,通过初始条件、循环条件和递增条件来循环执行语句块:123456var x = 0;var i;for (i=1; i<=10000; i++) {x = x + i;}x; // 50005000让我们来分析一下
for
循环的控制条件:i=1 这是初始条件,将变量i置为1;
i<=10000 这是判断条件,满足时就继续循环,不满足就退出循环;
i++ 这是每次循环后的递增条件,由于每次循环后变量i都会加1,因此它终将在若干次循环后不满足判断条件
i<=10000
而退出循环。
`for`循环的3个条件都是可以省略的,如果没有退出循环的判断条件,就必须使用`break`语句退出循环,否则就是死循环:
1234567
var x = 0;for (;;) { // 将无限循环下去 if (x > 100) { break; // 通过if判断来退出循环 } x ++;}
for … in
for
循环的一个变体是for ... in
循环,它可以把一个对象的所有属性依次循环出来:12345678var o = {name: 'Jack',age: 20,city: 'Beijing'};for (var key in o) {console.log(key); // 'name', 'age', 'city'}由于
Array
也是对象,而它的每个元素的索引被视为对象的属性,因此,for ... in
循环可以直接循环出Array
的索引:12345var a = ['A', 'B', 'C'];for (var i in a) {console.log(i); // '0', '1', '2'console.log(a[i]); // 'A', 'B', 'C'}请注意,
for ... in
对Array
的循环得到的是String
而不是Number
。while
for
循环在已知循环的初始和结束条件时非常有用。而上述忽略了条件的for
循环容易让人看不清循环的逻辑,此时用while
循环更佳。while
循环只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环。比如我们要计算100以内所有奇数之和,可以用while循环实现:1234567var x = 0;var n = 99;while (n > 0) {x = x + n;n = n - 2;}x; // 2500在循环内部变量
n
不断自减,直到变为-1
时,不再满足while
条件,循环退出。do … while
最后一种循环是
do { ... } while()
循环,它和while
循环的唯一区别在于,不是在每次循环开始的时候判断条件,而是在每次循环完成的时候判断条件:12345var n = 0;do {n = n + 1;} while (n < 100);n; // 100用
do { ... } while()
循环要小心,循环体会至少执行1次,而for
和while
循环则可能一次都不执行。
5种循环方式:
- for(起始条件;循环条件;递增条件)……
- For….in…..
- while……
- Do……while…..
- 数组的
forEach()
Map
Map
是一组键值对的结构,具有极快的查找速度。举个例子,假设要根据同学的名字查找对应的成绩,如果用
Array
实现,需要两个Array
:12var names = ['Michael', 'Bob', 'Tracy'];var scores = [95, 75, 85];给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,Array越长,耗时越长。
如果用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用JavaScript写一个Map如下:
12var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);m.get('Michael'); // 95初始化
Map
需要一个二维数组,或者直接初始化一个空Map
。Map
具有以下方法:1234567var m = new Map(); // 空Mapm.set('Adam', 67); // 添加新的key-valuem.set('Bob', 59);m.has('Adam'); // 是否存在key 'Adam': truem.get('Adam'); // 67m.delete('Adam'); // 删除key 'Adam'm.get('Adam'); // undefinedSet
Set
和Map
类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set
中,没有重复的key。要创建一个
Set
,需要提供一个Array
作为输入,或者直接创建一个空Set
:12var s1 = new Set(); // 空Setvar s2 = new Set([1, 2, 3]); // 含1, 2, 3重复元素在
Set
中自动被过滤:12var s = new Set([1, 2, 3, 3, '3']);s; // Set {1, 2, 3, "3"}注意数字
3
和字符串'3'
是不同的元素。通过
add(key)
方法可以添加元素到Set
中,可以重复添加,但不会有效果:1234s.add(4);s; // Set {1, 2, 3, 4}s.add(4);s; // 仍然是 Set {1, 2, 3, 4}通过
delete(key)
方法可以删除元素:1234var s = new Set([1, 2, 3]);s; // Set {1, 2, 3}s.delete(3);s; // Set {1, 2}
这两个有点像redis里的方法。
函数
匿名函数:
function (x) { ... }
函数的传参数量没有严格限定。可以少也可以多个,在函数内部可以用
arguments
把所有的参数当一个数组来调用。但这种方法在函数内部需要自己写语句来判断,显得有点别扭。新的做法是:…rest
12345function foo(a, b, ...rest) {console.log('a = ' + a);console.log('b = ' + b);console.log(rest);}变量提升
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:
12345678910111213141516function foo() {var x = 'Hello, ' + y;console.log(x);var y = 'Bob';}foo();相当于:function foo() {var y; // 提升变量y的申明,此时y为undefinedvar x = 'Hello, ' + y;console.log(x);y = 'Bob';}所以:通常会在定义函数伊始就定义好所有的变量!
全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象
window
,全局作用域的变量实际上被绑定到window
的一个属性:12345'use strict';var course = 'Learn JavaScript';alert(course); // 'Learn JavaScript'alert(window.course); // 'Learn JavaScript'因此,直接访问全局变量
course
和访问window.course
是完全一样的。名字空间
全局变量会绑定到
window
上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
1234567891011// 唯一的全局变量MYAPP:var MYAPP = {};// 其他变量:MYAPP.name = 'myapp';MYAPP.version = 1.0;// 其他函数:MYAPP.foo = function () {return 'foo';};原来还有命名空间这么一说。
常量(这里把let 和 const讲得很接地气!)
由于
var
和let
申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:1var PI = 3.14;ES6标准引入了新的关键字
const
来定义常量,const
与let
都具有块级作用域:12345'use strict';const PI = 3.14;PI = 3; // 某些浏览器不报错,但是无效果!PI; // 3.14快速赋值和提取属性:
12345678var person = {name: '小明',age: 20,gender: 'male',passport: 'G-12345678',school: 'No.4 middle school'};var {name, age, passport} = person;还可以有默认值:666
1234567891011var person = {name: '小明',age: 20,gender: 'male',passport: 'G-12345678'};// 如果person对象没有single属性,默认赋值为true:var {name, single=true} = person;name; // '小明'single; // true对象的解构还是蛮方便的!直接用对象来解构
如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如,下面的函数可以快速创建一个
Date
对象:123function buildDate({year, month, day, hour=0, minute=0, second=0}) {return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);}
方法
在一个对象中绑定函数,称为这个对象的方法。
在JavaScript中,对象的定义是这样的:
1234var xiaoming = {name: '小明',birth: 1990};但是,如果我们给
xiaoming
绑定一个函数,就可以做更多的事情。比如,写个age()
方法,返回xiaoming
的年龄:1234567891011var xiaoming = {name: '小明',birth: 1990,age: function () {var y = new Date().getFullYear();return y - this.birth;}};xiaoming.age; // function xiaoming.age()xiaoming.age(); // 今年调用是25,明年调用就变成26了this指的是调用者本身。(这里有坑需要注意!)
apply
虽然在一个独立的函数调用中,根据是否是strict模式,
this
指向undefined
或window
,不过,我们还是可以控制this
的指向的!要指定函数的
this
指向哪个对象,可以用函数本身的apply
方法,它接收两个参数,第一个参数就是需要绑定的this
变量,第二个参数是Array
,表示函数本身的参数。用
apply
修复getAge()
调用:12345678910111213function getAge() {var y = new Date().getFullYear();return y - this.birth;}var xiaoming = {name: '小明',birth: 1990,age: getAge};xiaoming.age(); // 25getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
高阶函数
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为
高阶函数
。
map和reduce
要把
[1, 3, 5, 7, 9]
变换成整数13579,reduce()
也能派上用场:1234var arr = [1, 3, 5, 7, 9];arr.reduce(function (x, y) {return x * 10 + y;}); // 13579和rails基本无差,只是在调用的时候用法上有点小差别:
12345function pow(x) {return x * x;}var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];var results = arr.map(pow); //这里调用时,直接就自动传参啦,233filter函数:
把一个
Array
中的空字符串删掉,可以这么写:12345var arr = ['A', '', 'B', null, undefined, 'C', ' '];var r = arr.filter(function (s) {return s && s.trim(); // 注意:IE9以下的版本没有trim()方法});r; // ['A', 'B', 'C']把返回false的参数过滤掉!
Sort函数
排序算法
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个对象呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。通常规定,对于两个元素
x
和y
,如果认为x < y
,则返回-1
,如果认为x == y
,则返回0
,如果认为x > y
,则返回1
,这样,排序算法就不用关心具体的比较过程,而是根据比较结果直接排序。JavaScript的
Array
的sort()
方法就是用于排序的,但是排序结果可能让你大吃一惊:12345678// 看上去正常的结果:['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];// apple排在了最后:['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']// 无法理解的结果:[10, 20, 1, 2].sort(); // [1, 10, 2, 20]js 的排序方法绝对是大坑!
改进:
1234567891011var arr = [10, 20, 1, 2];arr.sort(function (x, y) {if (x < y) {return -1;}if (x > y) {return 1;}return 0;});console.log(arr); // [1, 2, 10, 20]sort方法会直接改变数组本身!
箭头函数
|
|
上面的箭头函数相当于:
|
|
箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }
和return
都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }
和return
:
|
|
如果参数不是一个,就需要用括号()
括起来:
|
|
如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错:
|
|
因为和函数体的{ ... }
有语法冲突,所以要改为:
|
|
重点!
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this
是词法作用域,由上下文确定。
回顾前面的例子,由于JavaScript函数对this
绑定的错误处理,下面的例子无法得到预期结果:
|
|
现在,箭头函数完全修复了this
的指向,this
总是指向词法作用域,也就是外层调用者obj
:
|
|
如果使用箭头函数,以前的那种hack写法:
|
|
就不再需要了。万岁!
总结箭头函数:
- 简化
- 自动指定this
- 没有参数也需要用括号
- call和apply方法无法再用this来传数。
Generator函数
generator还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。这个好处要等到后面学了AJAX以后才能体会到。
没有generator之前的黑暗时代,用AJAX时需要这么写代码:
|
|
回调越多,代码越难看。
有了generator的美好时代,用AJAX时可以这么写:
|
|
点评:用法就是遇到yield的时候暂停,返回return一下yield后面返回的值,等函数再次调用时再运行下一步!
Date函数
关于时间的一些方法
|
|
正则表达式
|
|
1. 切分字符串
用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
|
|
嗯,无法识别连续的空格,用正则表达式试试:
|
|
这里原来有这样更好的用法!!!!
2. 分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()
表示的就是要提取的分组(Group)。比如:
^(\d{3})-(\d{3,8})$
分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
|
|
如果正则表达式中定义了组,就可以在RegExp
对象上用exec()
方法提取出子串来。
exec()
方法在匹配成功后,会返回一个Array
,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。
exec()
方法在匹配失败时返回null
。
3. 贪婪匹配
需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0
:
|
|
由于\d+
采用贪婪匹配,直接把后面的0
全部匹配了,结果0*
只能匹配空字符串了。
必须让\d+
采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0
匹配出来,加个?
就可以让\d+
采用非贪婪匹配:
|
|
4. 全局搜索
JavaScript的正则表达式还有几个特殊的标志,最常用的是g
,表示全局匹配:
|
|
全局匹配可以多次执行exec()
方法来搜索一个匹配的字符串。当我们指定g
标志后,每次运行exec()
,正则表达式本身会更新lastIndex
属性,表示上次匹配到的最后索引:
|
|
全局匹配类似搜索,因此不能使用/^...$/
,那样只会最多匹配一次。
正则表达式还可以指定i
标志,表示忽略大小写,m
标志,表示执行多行匹配。
Json
|
|
要输出得好看一些,可以加上参数,按缩进输出:
|
|
结果:
|
|
序列化和反序列化:
|
|
javascript面向对象
概念:
|
|
注意最后一行代码把xiaoming
的原型指向了对象Student
,看上去xiaoming
仿佛是从Student
继承下来的:
|
|
xiaoming
有自己的name
属性,但并没有定义run()
方法。不过,由于小明是从Student
继承而来,只要Student
有run()
方法,xiaoming
也可以调用:
JavaScript的原型链和Java的Class区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。
但通常不要这样直接__proto__
来指定原型。
更好的作法:
Object.create()
方法可以传入一个原型对象,并创建一个基于该原型的新对象:
|
|
不要把原型链搞得太长
函数也是一个对象,它的原型链是:
1foo ----> Function.prototype ----> Object.prototype ----> null由于
Function.prototype
定义了apply()
等方法,因此,所有函数都可以调用apply()
方法。很容易想到,如果原型链很长,那么访问一个对象的属性就会因为花更多的时间查找而变得更慢,因此要注意不要把原型链搞得太长。
构造函数
热泪盈眶,终于知道这个是怎么回事了!
123456function Student(name) {this.name = name;this.hello = function () {alert('Hello, ' + this.name + '!');}}你会问,咦,这不是一个普通函数吗?
这确实是一个普通函数,但是在JavaScript中,可以用关键字
new
来调用这个函数,并返回一个对象:123var xiaoming = new Student('小明');xiaoming.name; // '小明'xiaoming.hello(); // Hello, 小明!用
new Student()
创建的对象还从原型上获得了一个constructor
属性,它指向函数Student
本身:123456xiaoming.constructor === Student.prototype.constructor; // trueStudent.prototype.constructor === Student; // trueObject.getPrototypeOf(xiaoming) === Student.prototype; // truexiaoming instanceof Student; // true注意:
123var xiaohong = new Student();var xiaoliang = new Student();xiaohong.hello() === xiaoliang.hello(); // false这里两个hello函数是各自对象里的方法。所以如果new了很多对象,就会浪费很多。解法:
1234567function Student(name) {this.name = name;}Student.prototype.hello = function () {alert('Hello, ' + this.name + '!');};这样,xiaoming, xiaoliang下次调用是到Student找到这个方法来调用!
不要忘记写new
因为如果忘了写new, 在函数里很多属性会直接通过this增加到window,会更糟糕!So,一般构造函数通常会以大写字母开关。
更好的做法是,自定一个函数:
123456789101112function 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
来调用,二是参数非常灵活,可以不传,也可以这么传:12345var xiaoming = createStudent({name: '小明'});xiaoming.grade; // 1如果拿到的是json数据 ,还可以直接new对象。
重新认识APPLE函数
apply就是用来改变方法内部若出现的this对象
|
|
apply带两个参数:第一个参数是要塞给方法内部this的对象,第二个是传给方法的参数,需要数组形式。
|
|
对普通函数调用,我们通常把this
绑定为null
。
原型继承
|
|
更好的作法,封装一个函数:
|
|
使用:
|
|
终于ES6自己也看不过去了,出了个class来简化这些过程:
|
|
如果用新的class
关键字来编写Student
,可以这样写:
构造函数:
|
|
原型的继承:
|
|
这里的super方法理解为调用父方法!
帅!!!
Promise
|
|
其中的resolve和reject函数作用:把promise状态切换为已完成或者失败,并把结果往外传递。
|
|
finally()方法是最后的回调,无论如何都会执行;
|
|
这里的Promise.all相当于ruby中的线程join方法,等all中的所有方法都有回调后才往下执行。
|
|
这里嵌套后,p2的状态会因为p1而改变。
有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()
实现:
|
|
如何往Promise里的调用函数传参数?下面是个例子:
|
|
错误处理:
|
|
抛出错误: throw
|
|