前端笔记/JavaScript
基本
数据类型
6种数据类型
- 基本类型: Undefined、Null、Boolean、String、Number
- 复杂类型: Object
基本类型与引用类型的区别
基本类型
基本类型(String、Boolean、Number、null、undefined),是按值访问,可以操作保存在变量中的实际值
- 基本类型的值不可变
var name = 'jobs'
console.log(name.toUpperCase()) // 输出JOBS
console.log(name) // 输出jobs
name.age = 20
name.say = function() {……}
console.log(name.age) // undefined
console.log(name.say) // undefined
// 其中name的实际值并未变化,toUpperCase()方法是返回了一个新的字符串,也不能对基本类型添加属性和方法,表明了基本类型的值不可变
- 基本类型的比较是值的比较,只有在值相等时才相等
- 基本类型的变量存放在栈区
var name = 'jobs'
var age = 20
栈区 | |
---|---|
name | age |
jobs | 20 |
栈区包含了变量的标识符和值
- 基本类型复制
var a = 10
var b = a
a++
console.log(a, b) // 11 10
// 基本类型在复制操作后变量是相互独立的互不影响
引用类型
就是除了基本类型外的Object类型
- 引用类型值可以变,可以有属性和方法
var person = {} // 创建个控对象 --引用类型
person.name = 'jozo'
person.age = 22
person.sayName = function(){console.log(person.name)}
person.sayName() // 'jozo'
delete person.name // 删除person对象的name属性
person.sayName() // undefined
// 引用类型的值是同时保存在栈内存和堆内存中的对象
栈区内存保存变量的标识符和指向堆内存中该对象的指针也就是该对象在堆内存中的地址
var person1 = {name:'jozo'}
var person2 = {name:'xiaom'}
var person3 = {name:'xiaoq'}
栈区 | 堆区 | ||
---|---|---|---|
person1 | 堆内存地址1 | ——> | Object1 |
person2 | 堆内存地址2 | ——> | Object2 |
person3 | 堆内存地址3 | ——> | Object3 |
引用类型比较是引用的比较
var p1 = {}
var p2 = {}
console.log(p1 == p2) // false
// 引用类型是按引用访问的,所以是比较它们的堆内存中地址是否相同
对象引用
var a = {} // 新建空对象
var b = a // 赋值后a和b指向同一个空对象
a.name = 'jobs'
b.age = 20
console.log(a.name, a.age, b.name, b.age) // jobs 20 jobs 20
// 引用类型的复制是对象保存在栈区的堆内存的地址的复制,两个变量实际上指向同一个对象,因此任何操作都会互相影响
传递参数
- 所有函数的参数都是按值传递
var p1 = {
name: 'Vian'
}
var setName = function(obj) {
obj.name = 'jobs'
return obj
}
var res = setName(p1)
console.log(p1.name) // jobs
console.log(res.name) // jobs
// 把p1传入setName()中,obj和p1指向同一个对象,修改obj的name值其实是修改了它们共同指向的对象的name值
var p = {
name: 'alice'
}
var set = function(ot) {
ot = {}
ot.name = 'tom'
return ot
}
var re = set(p)
console.log(p.name) // alice
console.log(re.name) // tom
// 在函数内部ot新定义了一个对象,obj引用了一个局部对象,外部p和内部ot指向了不同对象,所以不会影响
引用类型详解
Object类型
- new操作符跟随Object构造函数
var person = new Object()
person.name = 'alice'
- 对象字面量表示法
var person = {
name: 'alice',
'age': 20
}
属性名也能使用字符串访问对象属性一般使用点语法,但js中也能使用方括号语法,并且可以使用变量访问属性
alert(person.name) // alice
alert(person['name']) // alice
var personName = 'name'
alert(person[personName]) // alice
Array类型
数组每一项都可以保存任何类型的数据,而且数组大小可以动态调整,跟随数据的添加而自动增长
创建数组的方法
var arr = new Array() // 使用Array构造函数
var arr = [] // 使用数组字面量表示法
length属性
- length总会返回大于0的整数,而且它不是只读属性
- 当设置length的值小于当前的数组长度时,会从末尾开始删减元素
- 当设置length为大于当前数组长度时,则会从末尾新增项,新增每一项的值都为undefined
检测数组
- 对只有一个全局作用域而言,使用instanceof操作符便可以
if (arr instanceof Array) {
// 对数组的操作
}
// instanceof操作符问题在于,它假定单一的全局执行环境。若网页中包含不同框架,那么实际上会有两个以上不同的全局执行环境,从而存在两个以上不同的Array构造函数
-
Array.isArray()
方法来判断
if (Array.isArray(arr)) {
// 对数组的操作
}
数组常用方法
- 栈方法:
-
push()
: 可接受任意个参数,逐个添加到数组末尾,并返回修改后数组长度 -
pop()
: 从数组末尾移除最后一项,减少length的值,然后返回移除的项 - 队列方法:
-
unshift()
: 在数组前面添加任意个项,并返回新数组的长度 -
shift()
: 移除数组第一个项,减少length的值,并返回移除的项 - 排序方法:
-
reverse()
: 反转数组项的顺序 -
sort()
: 默认升序排列,可接收一个比较函数作为参数,比较函数有两个参数,若第一个参数要在第二个参数之前,则返回负数,相等则返回0,若第一个参数要在第二个参数之后,则返回正数
> sort()会调用每项的toString()转型方法,比较字符串,即使是数值也会转型后比较字符串,因此[0, 1, 5, 10, 15]使用这个方法会变成[0, 1, 10, 15, 5]
- 操作方法
-
concat()
: 可接收数组、等作为参数,将数组合并
var color1 = ['pink', 'yellow']
var color2 = color1.concat('skyblue', ['black', 'orange'])
console.log(color1) // pink,yellow
console.log(color2) // pink,yellow,skyblue,black,orange
* `join()`: 可接受一个参数作为分隔符,将数组转换为字符串
* `slice()`: 基于当前数组创建一个新数组,接受一个或者两个参数,起始位置和结束位置,不会影响当前数组。
> 若只有一个参数,则返回从该参数位置开始到末尾所有的项
> 若两个参数则返回起始位置到结束位置之间的项,但不包括结束位置
-
splice()
: 主要作用向数组中插入项,始终返回一个数组,数组为原数组中删除的项,若没有则返回一个空数组,可以有三种用途
- 删除:删除任意数量的项,接受2个参数:要删除的第一项的位置和要删除的个数
- 插入:在指定位置插入任意数量的项,接受3个参数:起始位置、0(要删除的项数、要插入的项),如果要插入多个项,可以再传入第四个,五个,任意个项
- 替换: 可以在任意位置插入任意项,同时删除任意项,接受3个参数:起始位置、要删除的项数、要插入的项
- 位置方法
-
indexOf()
和lastIndexOf()
- 都接受2个参数:要查找的项和(可选)表示查找起点位置的索引,
indexOf()
从头开始找,lastIndexOf()
从末尾开始找 - 都返回要查找的项在数组中的位置,没找到则返回-1,并且查找时是使用全等操作符(===)
- 都接受2个参数:要查找的项和(可选)表示查找起点位置的索引,
- 迭代方法
共有五个迭代方法,每个方法都接受两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响this的值。传入这些方法中的函数接收三个参数:数组项的值、该项在数组中的位置和数组对象本身
-
every()
: 对数组每一项运行给定函数,若该函数对每一项都返回true,则返回true -
some()
: 对数组每一项运行给定函数,若该函数对任意一项返回true,则返回true -
filter()
: 对数组每一项运行给定函数,返回该函数会返回true的项组成的数组 -
map()
: 对数组每一项运行给定函数,返回每次函数调用结果组成的数组 -
forEach()
: 对数组每一项运行给定函数,没有返回值
以上方法都不会修改数组中的包含的值
var numbers = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
// every
var a1 = numbers.every(function(item, index, array) {
return item > 2
})
console.log(a1) // false
// some
var a2 = numbers.some(function(item, index, array) {
return item > 8
})
console.log(a2) // true
// filter
var a3 = numbers.filter(function(item, index, array) {
return item % 2 === 0
})
console.log(a3) // [2, 4, 6, 8, 0]
// map
var a4 = numbers.map(function(item, index, array) {
return item * 2
})
console.log(a4) // [2, 6, 10, 14, 18, 4, 8, 12, 16, 0]
// forEach
/*
没有返回值和for循环迭代数组一样
*/
- reduce
reduce(function(total, currentValue [,currentIndex]) [,initialValue]): 是一种数组运算,通常用于将数组的所有成员"累积"为一个值。
- total:必选,初始值或者累积变量
- currentValue:必选,当前元素
- currentIndex:可选,当前元素的索引
- initialValue:可选,传递给函数的初始值
- 基本用法
/**
* reduce(function(total, currentValue, currentIndex) [,initialValue]) 用于数组计算,通常用于将数组累积为一个值
*/
/**
* 参数tmp是累积变量,item为当前数组成员,index为数组下标;每次运行时item会累加到tmp,最后输出tmp
*/
let arr = [1, 2, 3, 4, 5, 6];
let sum = arr.reduce((tmp, item, index) => tmp + item);
console.log(sum); // 21
- map是reduce的特例
/*
*map是reduce的特例
累积变量的初始值可以是一个数组,例子中初始值是一个空数组,结果返回一个新的数组,等同于map操作;map操作都可以用reduce,所以map是reduce的特例
*/
let handler = (newArr, x) => {
newArr.push(x + 1);
return newArr;
}
let arr2 = arr.reduce(handler, []);
console.log(arr2); // [ 2, 3, 4, 5, 6, 7 ]
数组排序常见方法
var arr = [1, 10, 3, 8, 5, 9, 4, 6];
/*冒泡排序*/
function pops(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
console.log(pops(arr));
/*快速排序*/
function quick(arr) {
if (arr.length <= 1) {
return arr;
}
var len = Math.floor(arr.length / 2);
var aj = arr.splice(len, 1);
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < aj) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quick(left).concat(aj, quick(right));
}
console.log(quick(arr));
Date类型
Error类型
- try/catch/finally
try {
tryCode - 尝试执行代码块
}
catch(err) {
catchCode - 捕获错误的代码块
}
finally {
finallyCode - 无论 try / catch 结果如何都会执行的代码块
}
// 如果使用了try那么catch和finally必须选一个
RegExp类型
概述
通过RegExp
类型来支持正则表达式,使用类似Perl的语法来创建正则表达式
var expression = / pattern / flags;
-
其中模式(pattern)可以是任何简单或复杂的正则表达式,每个正则表达式可以有一个或者多个标志(flags),用以标明正则表达式的行为,匹配模式有以下三个标志.
- g:全局模式,模式将被用于整个字符串而非在发现第一个匹配项时立即停止
- i:不区分大小写模式,即在匹配时忽略大小写
- m:多行模式,即在到达一行文字末尾时还会继续查找下一行中是否存在与模式匹配的项
实例方法
-
exec()
: 接受一个参数,即要使用模式的字符串- 返回包含第一个匹配项的信息的数组,额外附带2个属性index和input,index表示匹配项在字符串中的位置,input表示应用正则表达式的字符串,若没有匹配项返回null
- 对于
exec()
而言即使在模式匹配设置了全局标志(g),也只会返回第一个匹配项,在不设置时,在同一字符串上多次调用也只会始终返回第一个匹配项的信息;而设置了,则每次调用都会继续匹配新的项
-
test()
:接受一个字符串参数,在模式与该参数匹配的情况下返回true否则返回false
Function 类型
函数其实是对象,每一个函数都是Function的实例,因此函数名实际上也是一个指向函数对象的指针,不会与函数绑定
函数声明与函数表达式
解析器对函数声明和函数表达式有差别对待
- 解析器会先读取函数声明,并使其在执行任何代码前可用(可以访问);至于函数表达式,则必须等到解析器执行到所在代码行才会真正被解析
// 函数声明
console.log(sum(10, 10)) // 20
function sum(a, b) {
return a + b
}
// 函数表达式
console.log(add(10, 10)) // 报错
var add = function(a, b) {
return a + b
}
- 函数声明,在代码开始执行前,解析器通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中,并放到源代码树的顶端,因此可以先调用再定义
- 函数表达式,函数位于一个初始化语句中,也就是在执行到函数代码前,变量不会保存有对函数的引用
函数内部属性
在函数内部有两个特殊对象arguments和this
- arguments是一个类数组对象,保存着函数的所有参数;它还有一个callee属性,它是一个指针,指向拥有这个arguments对象的函数
- callee
function factorial(num) {
if (num <= 1) {
return 1
} else {
return num * arguments.callee(num - 1)
/* arguments.callee即是factorial函数 */
}
}
console.log(factorial(10)) // 3628800
- caller,es5中定义的函数对象属性,返回一个调用当前函数的引用,如果是由顶层调用的话,则返回null
function a() {
console.log(arguments.callee.caller)
// 因为arguments.callee就是函数a,所以arguments.callee.caller等于a.caller
}
function b() {
a()
}
a() // null 因为是顶层调用
b() // b函数的源代码
- this,this引用的是(this的值)函数据以执行的环境对象,在网页全局作用域中调用函数时候this的引用对象就是window
函数的属性和方法
- 每个函数都包含两个属性:length和prototype
- length: 形参个数
- prototype: 原型
- 每个函数包含两个非继承而来的方法: apply()和call();
作用都是在特定作用域中调用函数,实际上等于设置this的值
-
apply()
:接收2个参数,一个是在其中运行的函数的作用域,一个是Array实例或者arguments对象 -
call()
:接收2个参数,一个是在其中运行的函数的作用域,其余参数要直接传递给函数,也就是逐一列举
function sum1(a, b) {
return a + b
}
function sum2(a, b) {
var s1 = sum1.apply(this, arguments) // arguments对象
var s2 = sum1.apply(this, [a, b]) // Array实例
console.log(s1, s2)
}
sum2(10, 10) //20 20
function sum3(a, b) {
return sum1.call(this, a, b) // 参数逐一传入
}
console.log(sum3(10, 10)) // 20
apply()
和call()
,真正的用武之地并不在于传递参数,而是扩充函数赖以运行的作用域
var color = 'blue'
var o = {
color: 'pink'
}
function sayColor() {
console.log(this.color)
}
/* this指向了全局作用域window */
sayColor() // blue
sayColor(this) // blue
sayColor(window) // blue
/* this指向了o */
sayColor.apply(o) // pink
sayColor.call(o) // pink
-
bind()
: 它会创建一个函数的实例,其this的值会被绑定到传给bind函数的值
var colors = 'skyblue'
var c = {
colors: 'orange'
}
function say() {
console.log(this.colors)
}
var say2 = say.bind(c)
say2() // orange
bind与apply、call不同,不会立刻执行
Boolean类型
暂无
Number类型
-
toFixed()
: 接受一个参数,代表保留几位小数,会四舍五入
var num = 10
console.log(num.toFixed(2)) // 10.00
String类型
string包含length属性,返回字符串长度
字符串方法
- 字符方法
charAt()
和charCodeAt()
:这两个方法都接收一个参数,即基于0的字符的位置.其中charAt()
以单字符字符串的形式返回给定位置的字符,而charCodeAt()
则是返回编码
var stringTXT = 'hellow'
console.log(stringTXT.charAt(3)) // l
console.log(stringTXT.charCodeAt(3)) // 108
- 字符串操作方法
-
concat()
:用于将一个或多个字符串拼接起来 -
slice()
,substr()
,substring()
:这三个方法都返回被操作字符串的子字符串,也都接受一或两个参数,第一个参数指定开始位置,第二个指定结束位置;slice和substring的第二个参数指定的是子字符串的最后一个字符后面的位置,substr的第二个参数则是指定返回字符的个数
var txt = 'abcdefg'
console.log(txt.slice(1, 3)) // bc
console.log(txt.substring(1, 3)) // bc
console.log(txt.substr(1, 3)) // bcd
- 位置方法
indexOf()
和lastIndexOf()
:这两个方法接受2个参数,第一个为要查找的字符串,(可选)第二个表示查找起点位置的索引,都是从字符串中寻找指定的字符串,然后返回该字符串的位置,若没有则返回-1,一个从头开始找,一个从末尾开始找
- 大小写转换
-
toLowerCase()
和toUpperCase()
:全部转换成小写,大写 -
toLocaleLowerCase()
和toLocaleUpperCase()
:全部转换成小写,大写,这是根据特定地区来转换大小写,在不知道代码运行在哪个语言的情况下使用 - 字符串的模式匹配方法
-
match()
:接受一个参数,参数为正则表达式或者RegExp对象,返回一个数组,数组保存着每一个匹配的子字符串
var txt = 'abcdefgabcdefgabcdefg'
var reg = /ab/gi
console.log(txt.match(reg)) // ['ab', 'ab', 'ab']
-
search()
:接受一个参数,参数为正则表达式或者RegExp对象,从头开始匹配,返回第一个匹配项的索引,没有则返回-1 -
replace()
:接收2个参数,字符串或者正则表达式或者RegExp对象,第二个则是用于替换的字符串或者函数;若第一个参数为字符串则只会替换第一个子字符串,要想替换所有,则必须是正则表达式,并且指定全局标志(g);若第二个参数是函数,则给这个函数传入的三个参数分别是匹配项第一个匹配项,第二个等等,最后两个参数为匹配项的位置和原始字符串 -
split()
:将字符串以指定分隔符分割成字符串,分隔符可以是字符串也可以是正则表达式或者RegExp对象;也可以接受第二个参数,用于指定数组大小
内置单体对象
Global对象
URI编码方法
- encodeURI():主要用于整个uri,它不会对本身属于uri的特殊字符进行编码如冒号、正斜杠、问好、井号
- encodeURIComponent():主要用于对其中一段进行编码,它会对任何非标准字符进行编码
var uri = 'http://www.who.com/search?wd=123'
console.log(encodeURI(uri))
// 结果: http://www.who%08.com/search?wd=123
console.log(encodeURIComponent(uri))
// 结果: http%3A%2F%2Fwww.who%08.com%2Fsearch%3Fwd%3D123
encodeURIComponent使用的比encodeURI多
-
decodeURI()
:解码方法,只能对encodeURI()
编码的进行解码 -
decodeURIComponent()
:解码方法,可以解码所有字符
eval
-
eval()
:这个方法像是一个完整的ECMAScript解析器,它接受一个字符串参数,就是要执行的js代码字符串
Math对象
常用属性
- Math.PI: π的值
常用方法
- 求极值
-
min()
,max()
:求最小,最大值;可以接受任意个参数 - 舍入方法
-
ceil()
:向上求整 -
floor()
:向下求整 -
round()
:四舍五入 - 随机值
-
random()
: 返回一个0-1之间的随机数,但不包含0和1,从区间内取一个值,可以用一下公式
(max - min) * Math.random() + min
- 绝对值
-
abs()
: 绝对值 - 次方
-
pow(a, n)
: 第一个为底,第二个参数为次方值,a的n次方
原型和闭包
对象
(undefined,number,string,boolean)属于简单的值类型,不是对象。函数、数组、对象、null、构造函数都是对象,都是引用类型判断一个变量是不是对象,值类型用typeof,引用类型用instanceof
- 在js中数组、函数、对象都是对象,对象里面一切都是属性,只有属性,没有方法; 方法也是一种属性
- 对象都是通过函数创建的,函数是对象
function F() {
this.name = 'jobs'
}
var f1 = new F() // 对象可以通过函数创建
var arr = [1, 2, 3] // 语法糖,本质是下面的简写
var arr = new Array() // Array是构造函数
arr[0] = 1
arr[1] = 2
arr[2] = 3
原型
prototype原型
- 函数默认有一个prototype属性,prototype属性的值是一个对象(属性的集合),默认只有一个constructor属性指向这个函数本身
[image:DF9C0DF3-83B1-4F0C-8F2B-39C112BA24F6-1188-00000B31D26AD3B2/prototype-1.png]
- 如图所示,superType是一个函数,右边是原型
- 原型作为对象,属性的集合,可以自定义许多属性,例如Object,里面包含了其他几个属性
[image:C2050E95-6407-4A59-9B93-D6F7081CD863-1188-00000B3DEE188E44/instanceof-2.png]
- 可以在自定义的方法的prototype中新增自己的属性
function Fn() {
Fn.prototype.name = '王福鹏'
Fn.prototype.getYear = function() {
return 1988
}
}
[image:E2D062D3-B7D0-457C-97D2-6607E729A1C5-1188-00000B470B9F9F19/prototype-3.png]
function Fn() {
Fn.prototype.name = '王福鹏'
Fn.prototype.getYear = function() {
return 1988
}
}
var f1 = new Fn()
console.log(f1.name) // 王福鹏
console.log(f1.getYear()) // 1988
console.log(f1.__proto__ === Fn.prototype) // true
- 上述代码,f1对象是从Fn中new出来的,这样f1就可以调用Fn中的属性,因为每个对象都有一个隐藏属性'__proto__',这个属性引用了创建这个对象的函数的prototype,所以f1.__proto__ === Fn.prototype,这里的'__proto__'成为隐式原型
隐式原型
__proto__
是浏览器提供的属性,就是[[prototype]]
这个隐藏属性,__proto__
因为不是规范属性,所以要避免使用;
- es5中用Object.getPrototypeOf(obj)函数获得一个对象的
[[prototype]]
-
es6中用Object.setPrototypeOf(obj, prototype)函数可以直接修改一个对象的
[[prototype]]
- 参数obj将被设置原型的对象.
- prototype该对象新的原型(可以是一个对象或者null).
- 每个对象都有一个
__proto__
,可称为隐式原型 -
__proto__
是一个隐藏属性
var obj = {}
console.log(obj.__proto__ === Object.prototype)
- 有上述代码可知:每个对象都有一个
__proto__
`属性指向创建该对象的函数的prototype属性 - 有一个特例
Object.prototype.__proto__ === null
instanceof 操作符
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true
console.log(Object instanceof Function) // true
console.log(Object instanceof Object) // true
- instanceof判断规则是,沿着a的
__proto__
来找,沿着b的prototype来找,如果引用的同一个引用就返回true
[image:E4783050-568F-4369-A33E-192AE586FBE7-1188-00000B9545C250D1/instanceof-1.png]
- 即instanceof表示的就是一种继承关系,或者原型链的结构
[image:8826A813-C433-43DB-9C3D-CF0964451DDA-1188-00000B97B94D72D0/instanceof-2.png]
继承
- JavaScript的继承是用原型链来体现的
function Foo() {}
var f1 = new Foo()
f1.a = 10
Foo.prototype.a = 100
Foo.prototype.b = 200
console.log(f1.a, f1.b) // 10 200
上述代码,f1是Foo函数new出来的对象,f1.a是f1对象的基本属性,而f1.b是从Foo.prototype得来的,因为f1.__proto__
指向Foo.prototype
- 访问一个对象的属性时,现在基本属性中查找,如果没有,就沿着
__proto__
链向上查找,这就是原型链 - 如何区分一个属性是基本的还是从原型中找到的,用hasOwnProperty
[image:802EAC7F-3F88-4EA9-A120-E988C06FB7B3-1188-00000B9C76399666/prototype-4.png]
- f1中没有hasOwnProperty方法,它是从Object.prototype而来的
[image:7231EB7A-F0A5-482E-9632-8C88F3FA7749-1188-00000BA5D0FE85B9/prototype-5.png]
- 对象的原型链都会找到Object.prototype,因此所有对象都有Object.prototype的方法,这就是继承
执行上下文
- 上下文环境
[image:B0C9A9CF-FB04-4776-A2E5-AA72796DC573-1188-00000BA936740391/this-1.png]
- 第一句报错a未定义,二三句a为undefined,说明在js代码一句句执行前,浏览器就已经开始一 些准备工作,其中就包括对变量的声明,而不是赋值,赋值操作是在运行到语句时才执行;
[image:2185D116-A4C9-41BC-BF30-92F3E57EC3B3-1188-00000BAD8E05AD77/this-2.png]
- 这是第一种情况
[image:DCC314B3-40D8-4F45-8DC1-72AB6A089648-1188-00000BB1A9090280/this-3.png]
- 第一种情况是对变量只声明,未赋值;第二种直接赋值给了this;这也是‘准备工作’之一
[image:88C73D7D-40A0-470F-81BE-B2651D40A767-1188-00000BB70805B00A/this-4.png]
- 在这里对函数表达式和普通变量赋值一样,而函数声明则立刻赋值了
-
准备工作完成的事
- 变量、函数表达式 —— 变量声明,默认赋值为undefined
- this —— 赋值
- 函数声明 —— 赋值
- 这三种数据准备情况就是‘执行上下文’或者‘执行上下文环境’
-
js在执行代码片段前都会进行准备工作来生成‘执行上下文’,代码片段分三种情况:全局代码、函数体、eval代码
- 在函数中,arguments和函数参数会直接赋值;函数每调用一次,都会产生一个新的执行上下文环境;而且函数在定义的时候(不是调用的时候),就已经确定函数体内部自由变量的作用域
- 在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空
- 全局代码的上下文环境为
普通变量(包含函数表达式) | 声明(默认赋值为undefined) |
函数声明 | 赋值 |
this | 赋值 |
- 如果代码片段是函数体,则在此基础上附加
参数 | 赋值 |
arguments | 赋值 |
自由变量的取值作用域 | 赋值 |
this
- 函数中this到底是什么值,是在函数真正被调用时确定的,函数定义时确定不了;this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的;因为this取值是执行上下文环境的一部分,每次调用都会产生一个新的执行环境上下文
-
分四种状况
- 构造函数
function F(num) {
this.num = num
this.log = 'txt'
console.log(this)
}
- 把函数作为构造函数,那么this就是new出来的对象
- 函数作为对象的一个属性
- 如果函数作为对象的一个属性,并且作为对象的属性被调用时,this指向该对象
var obj = {
x: 10,
fn: function() {
console.log(this) // {x:10, fn: function}
console.log(this.x) // 10
}
}
obj.fn()
- 函数调用call和apply,bind时调用
- 当一个函数被call和apply调用时,this的值就取传入的对象的值
var obj = {
x: 10
}
function fn() {
console.log(this)
console.log(this.x)
}
fn.call(obj) // {x: 10} 10
fn.apply(obj) // {x: 10} 10
var fns = fn.bind(obj)
fns() // {x: 10} 10
-
全局和调用普通函数
- this指向window
作用域
- 作用域
[image:C6407A7F-8B78-4A86-8C15-6697981F687C-1188-00000BFD4FA9B6C0/this-5.png]
[image:08320C13-AE3E-4C60-A615-25442AD209CA-1188-00000BFECEDB8EC1/this-6.png]
- 作用域中变量的值是在执行过程中产生的确定的
- 如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值
自由变量和作用域链
- 要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记——其实这就是所谓的“静态作用域”
- 作用域链
[image:12DFA8C1-973E-4680-A97D-C57F33ABEBCB-1188-00000C050C4391C4/this-7.png]
面向对象的程序设计
属性类型
包含两种数据属性和访问器属性
数据属性
数据属性包含一个数据值的位置,在这个位置可以读取和写入值,数据属性有4个描述其行为的特性
-
[[Configurable]]
:表示能否通过delete删除属性,从而重新定义属性,能否修改属性特性,能否把属性修改为访问器属性 -
[[Enumerable]]
:能否通过for-in循环返回属性 -
[[Writable]]
:能否修改属性值 -
[[Value]]
:包含这个属性的数据值,默认undefined
直接在对象上定义的属性,它们的[[Configurable]]、[[Enumerable]]、[[Writable]]的值默认为true,[[Value]]为指定值
var p = { name: 'nico' } // [[Value]]的值为 nico
要修改属性默认特性,必须使用es5的Object.defineProperty()方法,接收三个参数,属性所在对象,属性名字和一个描述符对象。其中描述符对象属性必须是:configurable、enumerable、writable、value.设置其中一个或多个值(小写)可以多次调用Object.defineProperty(),但是Configurable一旦设置为false(不可配置),就不能再将其设置为true(可配置),而且为false时其他属性也受到限制了
在调用Object.defineProperty()时,若不设置,configurable、enumerable、writable的值都为false
var Person = {}
Object.defineProperty(Person, 'name', {
configurable: false,
wirtable: false,
value: 'alice'
})
console.log(Person.name) // alice
delete Person.name
console.log(Person.name) // alice
Person.name = 'Nico'
console.log(Person.name) // alice
Object.defineProperty(Person, 'name', {
configurable: true,
value: 'Nico'
})
console.log(Person.name) // 报错
访问器属性
访问器属性不包含数据值,包含一对getter和setter函数(不过它们不是必须的),在读取访问器属性时,会调用getter函数,它负责返回有效的值;在写入访问器属性时会调用setter函数,它负责处理数据;访问器有如下4个特性
* [[Configurable]]
:表示能否通过delete删除属性,从而重新定义属性,能否修改属性特性,能否把属性修改为访问器属性
-
[[Enumerable]]
:能否通过for-in循环返回属性 -
[[Get]]
:在读取时调用的函数,默认值为undefined -
[[Set]]
:在写入属性时调用的函数,默认值为undefined
访问器属性不能直接定义,必须使用Object.defineProperty()来定义
var book = {
_year: 2014, // _ 是一种常用标记,用于表示只能通过对象方法访问的属性
edition: 1
}
Object.defineProperty(book, 'year', {
get: function() {
return this._year
},
set: function(val) {
if (val > 2014) {
this._year = val
this.edition = val - 2014
}
}
})
book.year = 2016
console.log(book.edition) // 2
这是使用访问器属性的常见方式,即设置一个属性的值会导致其他属性发生变化不一定要同时使用getter和setter,只指定getter表示属性不能写,尝试写入属性会被忽略;只指定setter表示不能读,否则会返回undefined;在严格模式下都会抛出错误
定义多个属性
Object.defineProperties(),es5中定义的,可以通过描述符一次定义多个属性,接收2个对象参数,第一个是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性要一一对应
var book = {}
Object.defineProperties(book, {
_year: {
value: 2000
},
edition: {
writable: true,
value: 1
},
year: {
get: function() {
return this._year
},
set: function(val) {
if (val > 2000) {
this._year = val
this.edition += val - 2000
}
}
}
})
console.log(book.year) // 2000
book.year = 2017
console.log(book.edition) // 18
读取属性的特性
Object.getOwnPropertyDescriptor(),可以取得给定属性的描述符;接收2个参数:属性所在对象和要读取的其描述符的属性名,返回值为一个对象;如果是数据属性,这个对象的属性有configurable、enumerable、writable、value;如果是访问器属性,这个对象的属性有configurable、enumerbale、get、set
创建对象
工厂模式
function createperson(name, age) {
var o = {}
o.name = name
o.age = age
o.say = function() {
console.log(this.name, this.age)
}
return o
}
var p1 = createperson('alice', 18)
var p2 = createperson('lulu', 20)
console.log(p1 instanceof Object) // true
console.log(p1 instanceof createperson) // false
工厂模式解决了创建多个相似对象的问题,但没有解决对象识别问题(怎么知道一个对象的类型)
构造函数模式
function Person(name, age) {
this.name = name
this.age = age
this.say = function() {
console.log(this.name, this.age)
}
}
var p1 = new Person('alice', 18)
var p2 = new Person('lulu', 20)
console.log(p1 instanceof Object) // true
console.log(p1 instanceof Person) // true
与工厂模式对比,没有显示的创建对象,直接将属性和方法赋给了this对象,没有return语句在这个模式下创建的所有对象既是Object的实例又是Person的实例
创建自定义构造函数意味着可以将它的实例标识为一种特定类型,这正是构造函数模式胜过工厂模式的地方
使用构造函数会使得每个方法都要在每个实例上创建一遍,在上述例子中p1和p2都有say方法,但那不是同一个Fuction的实例,因为在js中函数是对象,会导致不同的作用域链和标识符解析
console.log(p1.say == p2.say) // false
原型模式
创建的函数都有一个prototype(原型)属性,它是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法,也就是prototype就是通过调用构造函数而创建的那个对象实例的原型对象
function Person() {}
Person.prototype.name = 'nico'
Person.prototype.say = function() {
console.log(this.name)
}
var p1 = new Person()
p1.say() // nico
console.log(p1 instanceof Person) // true
-
理解原型对象
- 只要创建了新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象
- 默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针,例如前面的代码中Person.prototype.constructor指向Person;通过这个构造函数可以继续为原型对象添加其它属性和方法
- 创建了自定义构造函数后,其原型对象默认只会取得constructor属性;至于其他方法,则从Object继承而来
- 当调用一个构造函数创建一个新的实例后,该实例内部包含一个指针(内部属性),指向构造函数的原型对象;es5中这个指针叫[[Prototype]],在脚本中没有标准的方式访问[[Prototype]],但Firefox,Safari,Chrome在每个对象上都支持一个属性__proto__;这个链接存在于实例与构造函数的原型对象之间,而不是实例与构造函数之间
- 可以通过对象实例访问保存在原型中的值,但却不能重写原型中的值
- 原型模式最大问题是由其共享的本性所导致的,改变一个实例的属性,所有实例都会变
- 更简单的原型语法
function Person() {}
Person.prototype = {
name: 'nico',
say: function() {
console.log(this.name)
}
}
var p1 = new Person()
p1.say() // nico
console.log(p1 instanceof Person) // true
console.log(p1.constructor == Person) // false
- 以对象字面量形式创建,会导致constructor属性不再指向Person
- 可以设置constructor为适当值
function Person() {}
Person.prototype = {
constructor: Person,
name: 'nico',
say: function() {
console.log(this.name)
}
}
var p1 = new Person()
p1.say() // nico
console.log(p1 instanceof Person) // true
console.log(p1.constructor == Person) // true
但这样会导致constructor的[[Enumerable]]为true,原生的constructor为不可枚举属性,这时候要用es5的Object.defineProperty()重写constructor属性
function Person() {}
Person.prototype = {
constructor: Person,
name: 'nico',
say: function() {
console.log(this.name)
}
}
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value: Person
})
var p1 = new Person()
p1.say() // nico
console.log(p1 instanceof Person) // true
console.log(p1.constructor == Person) // true
组合模式 - 最常用方式
-
概述
-
组合使用构造函数模式和原型模式
- 这是创建自定义类型最常见的模式
- 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
- 这样,每个实例都有一份自己的实例属性的副本,同时又共享着对方法的引用,还支持向构造函数传参
- 这种模式是目前使用最广泛的一种创建自定义类型的方法,可以说是用来定义引用类型的一种默认模式
-
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person,
say: function() {
console.log(this.name, this.age)
}
}
var p1 = new Person('alice', 20)
var p2 = new Person('nico', 30)
p1.say()
console.info(p1.name == p2.name) // false
console.info(p1.say == p2.say) // true
动态原型模式
- 为了解决独立的构造函数和原型,动态原型模式将所有信息封装到构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点;也就是通过检测某个应该存在的方法是否有效来决定是否初始化原型
function Person(name, age) {
this.name = name
this.age = age
if (typeof this.say != 'function') {
Person.prototype.say = function() {
console.log(this.name, this.age)
}
}
}
var p1 = new Person('alice', 20)
var p2 = new Person('nico', 30)
console.info(p1.name == p2.name) // false
console.info(p1.say == p2.say) // true
- 这里只有在say方法不存在时,才会将它添加到原型中,这段代码只有在初次调用构造函数时才会执行
- if语句检查可以使初始化之后应该存在的任何属性或方法,不必一堆if语句来检测每个属性和每个方法,只要检查其中一个即可,还可以使用instanceof来确定类型
寄生构造函数模式
与工厂模式一样,instanceof操作符不能用来确定对象类型,在能使用其他模式的情况下不推荐使用
稳妥构造函数模式
function Person(name, age) {
var o = {}
o.say = function() {
console.log(name)
}
return o
}
var p1 = Person('alice', 20)
p1.say() // alice
这样变量Person中保存的是一个稳妥对象,除了调用say()外,无法访问其数据成员,这种模式instanceof也没什么意义
继承
借用构造函数
- 使用call或者apply,将父对象的构造函数绑定在子对象上
function Animal() {
this.species = '猫科动物'
}
function Cat(name, color) {
Animal.apply(this, arguments)
this.name = name
this.color = color
}
var cat1 = new Cat('kit', 'white')
console.log(cat1.species) // 猫科动物
- 方法都在构造函数中定义,函数无法复用,借用构造函数很少单独使用
原型链
- 使用prototype属性,如果子构造函数的prototype对象指向一个父构造函数的实例,那么所有子构造函数的实例都可以继承父构造函数了
function Animal() {
this.species = '猫科动物'
}
function Cat(name, color) {
this.name = name
this.color = color
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
var cat1 = new Cat('ly', 'blue')
console.log(cat1.species) // 猫科动物
-
解析上述代码
Cat.prototype = new Animal()
将Cat的prototype对象指向一个Animal的实例,它相当于删除了prototype原先的值,然后赋予一个新值
- `Cat.prototype.constructor = Cat`任何一个prototype都有一个constructor属性,由于上述代码,会导致Cat.prototype.constructor指向Animal,需要重新将构造函数定义回Cat
- 如果替换了prototype对象那么下一步必定是将constructor指向原来的构造函数
* 由于不能在不影响所有对象实例的情况下传参和由于原型中包含的引用类型值得问题,很少会单独使用原型链
原型式继承(用空对象做中介)
function Animal() {
this.species = '猫科动物'
}
function Cat(name, color) {
this.name = name
this.color = color
}
function F() {}
F.prototype = new Animal()
Cat.prototype = new F()
var cat1 = new Cat('ly', 'blue')
console.log(cat1.species) // 猫科动物
console.log(Animal.prototype.constructor) // Animal的源代码
function Animal() {
this.species = '猫科动物'
}
function Cat(name, color) {
this.name = name
this.color = color
}
function extend(child, parent) {
var F = function() {}
F.prototype = new parent()
child.prototype = new F()
child.prototype.constructor = child
child.uber = parent.prototype
}
extend(Cat, Animal)
var cat1 = new Cat('jack', 'orange')
console.log(cat1.species) // 猫科动物
这个extend函数,就是YUI库如何实现继承的方法最后一行只是为了实现继承的完备性,纯属备用性质
寄生继承
- 创建一个仅用于封装继承过程的函数
- 无法实现函数复用
function creatAnother(obj) {
var clone = Object(obj)
clone.say = function() {
console.log('hi')
}
return clone
}
var Person = {
name: 'nico'
}
var an = creatAnother(Person)
an.say() // hi
组合继承 - 最常用方式
- 也叫伪经典继承,将原型链和借用构造函数的技术整合一起,从而发挥二者之长
- 通过原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承
- 最常用方式
- 函数可复用,可传参,不存在引用属性共享问题
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name)
}
Person.prototype.sayAge = function() {
console.log(this.age)
}
function Girls(name, age) {
Person.apply(this, arguments)
}
Girls.prototype = new Person()
Girls.prototype.constructor = Girls;
var alice = new Girls('alice', 16)
alice.sayName() // alice
alice.sayAge() // 16
寄生组合式继承
- 最好的继承方式
function inter(Super, Sub) {
var clone = Object(Super.prototype) // 创建对象
clone.constructor = Sub // 增强对象
Sub.prototype = clone // 指定对象
}
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function() {
console.log(this.name, this.age)
}
function Girls(name, age, play) {
Person.apply(this, arguments)
this.play = play
}
inter(Person, Girls)
Girls.prototype.plays = function() {
console.log(this.play)
}
var alice = new Girls('alice', 16, 'game')
alice.say() // alice 16
alice.plays() // game
- inter函数实现了寄生组合的最简单形式,在函数内部先创建超类原型副本,为创建的副本添加constructor属性,最后将副本赋给子类型的原型
浏览器环境
DOM和BOM
BOM
浏览器对象模型,提供了独立于浏览器显示内容而与浏览器窗口进行交互的对象
location
- 用于存储当前页面URL信息的对象
- location的属性如下:
//假设url为 http://search.phpied.com:8080/search?p=javascript#results
location.href = "http://search.phpied.com:8080/search?p=javascript#results";
location.hash = "";
location.host = "search.phpied.com:8080";
location.hostname = "search.phpied.com";
location.pathname = "/search";
location.port = "8080";
location.protocol = "http:";
location.search = "?p=javascript";
-
location的三个方法:
-
reload()
: 无参数,重新载入当前页面; location = location也能用于重新载入当前页面 -
assign(newURL)
: 载入新页面会留下历史记录 -
replace(newURL)
: 载入新页面不会留下历史记录
-
histroy
- 存储了页面的访问记录
-
window.history.lenght
: 存储的记录数 -
history.forward()
: 前进 -
history.back()
: 后退 -
history.go()
: 0重新载入当前页面; 正值前进几个页面; 负值后退几个页面
screen
- 提供了桌面信息
-
screen.width,screen.height
: 桌面分辨率,总大小 -
screen.availWidth,screen.availHeight
: 除去操作系统菜单栏(例如windows的任务栏)以外的区域大小
alert,prompt,confirm
这几个并不属于ECMAScript,而是BOM方法
定时器
定时器也是BOM方法
DOM
文档对象模型,将xml或html文档解析成树形节点的方法
DOM节点
DOM节点属性
- nodeName : 节点的名称
- nodeValue :节点的值
- nodeType :节点的类型
DOM访问快捷方式
- getElementsByTagName、getElementsByName、getElementById
- 可以用属性形式访问attribute,因为class为保留字,所以class要用className访问
兄弟节点、body元素、首尾子节点
- 属性:nextSibling与previousSibling,得到下一个兄弟节点、上一个兄弟节点;空行也算
- 属性:nextElementSibling 与previousElementSibling,得到下一个上一个兄弟元素节点
修改节点
- innerHtml,可用于修改节点内容
- style属性用于修改样式
创建节点
-
document.createElement()
: 创建元素节点 -
document.createTextNode
: 创建文本节点
克隆节点
-
cloneNode()
: 该方法有一个参数,true深拷贝,包括所有子节点, false浅拷贝,只针对当前节点
var odv = document.getElementsByTagName('div')[0];
var p2 = document.getElementsByTagName('p')[1];
odv.appendChild(p2.cloneNode(false));
odv.appendChild(p2.cloneNode(true));
// 浅拷贝,文本节点不会拷贝
插入节点
-
appendChild(newChild)
: 将节点插入到 节点的子节点列表的末尾 -
insertBefore(newChild, refChild)
: 将节点插入节点指定子节点的前面
var odv = document.getElementsByTagName('div')[0];
var p2 = document.getElementsByTagName('p')[1];
odv.appendChild(p2.cloneNode(true));
odv.insertBefore(p2.cloneNode(true), document.getElementsByTagName('p')[0]);
移除、替换节点
-
removeChild(child)
: 从子节点列表中移除指定节点 -
replaceChild(newChild, oldChild)
: 将指定子节点替换成新节点
var odv = document.getElementsByTagName('div')[0];
var p2 = document.getElementsByTagName('p')[1];
odv.replaceChild(document.createElement('li'), p2);
只适用于HTML的DOM方法
访问文档的基本方式
- document.body
- document.images, 相当于Core DOM组件的document.getElementsByTagName('img')的调用
- document.forms, 获取所form,包含子元素;然后可以通过elements来访问子元素,如果form或者子元素有名字,也可用过名字访问
var forms = document.forms[0];
// var user = forms.elements[0]; 和下行一样
var user = forms.user;
console.log(user);
user.value = 'admin';
// <input name='user' /> 标签里有了admin
CookiesTitleReferrerDomain
事件
DOM的事件监听
-
addEventListener(even, function, boolean)
: 第一个参数为事件名不加on,第二个参数为函数,第三个为布尔值默认为false在冒泡阶段执行,true在捕获阶段执行 -
removeEventListener()
: 该方法与上一个参数相同,它是移除监听器;但若是第二个参数为匿名函数则移除不了
捕捉和冒泡
- 捕捉:事件先发生在document上,依次传递给body等,最终达到该元素上
- 冒泡:事件先发生在该元素上,再依次向上,然后到body,直至document对象上
阻断事件传播
-
stopPropagation()
: 这样就使得事件无法传播了,只发生在自己身上
var op = document.getElementsByTagName('p');
op[0].addEventListener('click', function(e) {
console.log(e.target);
e.stopPropagation();
}, false);
阻止默认事件
-
pereventDefault()
: 不是所有默认事件都能阻止
var alink = document.getElementsByTagName('a');
alink[0].addEventListener('click', function(e) {
e.preventDefault();
console.log(123);
}, false);
跨浏览器事件监听(兼容IE)
DOM2属性 | IE对应属性 | 说明 |
---|---|---|
addEventListener | attachEvent | 事件监听 |
event | window.event | IE中的全局事件对象 |
target | srcElement | 事件元素的target属性 |
stopPropagation | cancelBubble | only-IE属性,设置为true |
preventDefault | returnValue | only-IE属性,设置为false |
removeEventListener | detachEvent | 移除事件监听 |
编程模式与设计模式
编程模式
行为隔离
就是HTML、CSS和JS分开
命名空间
为了减少命名冲突,通常减少全局变量的使用;更好的方法是将不同变量和方法定义在不同命名空间中;本质是只定义一个全局变量,并将其它变量和方法定义为该变量的属性
将对象用作命名空间
// 新建全局变量MYAPP
var MYAPP = MYAPP || {};
// 添加属性
MYAPP.event = {};
// 添加方法
MYAPP.event = {
getEvent: function(e) {
// ......
},
// ......other methods
}
命名空间中构造器应用
我们可以在命名空间中使用构造器函数
// 本例中,我们用Element构造器创建一个dom元素
var MYAPP = MYAPP || {};
MYAPP.dom = {};
MYAPP.dom.Element = function(type, props) {
var tmp = document.createElement(type);
for (var i in props) {
tmp.setAttribute(i, props[i]);
console.log(i, props[i]);
}
return tmp;
}
var a = new MYAPP.dom.Element('a', {
href: "javascript.void(0);"
});
document.body.appendChild(a);
namespace方法
var MYAPP = {};
MYAPP.namespace = function(str){
var parts = str.split("."),
parent = MYAPP,
i=0,
l=0;
if(parts[0]==="MYAPP"){
parts = parts.slice(1);
}
for(i=0,l=parts.length; i<l;i++){
if(typeof parent[parts[i]] === "undefined"){
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
}
设计模式
设计模式有23种甚至更多,下面为4种常用模式
单例模式
工厂模式
装饰器模式
观察者模式
var sub = {
callbacker: [],
// 发布
add: function(fn) {
this.callbacker.push(fn);
},
// 订阅
fire: function(fn) {
this.callbacker.forEach(function(element) {
element();
});
}
}
sub.add(function() {
console.log(1)
});
sub.add(function() {
console.log(2)
});
sub.fire(); // 1 2
ES6
let和const
let
- let用于申明变量,并且只在该代码块内有效
- let不存在变量提升,只能先声明再使用
- 暂时性死区,在代码块内未使用let申明变量前,变量都是不可用的
- 不允许重复声明变量
- let和const实际上为js提供了块级作用域
const
- const就是常量
- 一旦声明,必须就赋值,且不可变更
- 无法重复声明
全局对象的属性
- 全局对象是最顶层的对象,在浏览器环境中是window对象,在nodeJS中是global对象
- es5中全局对象属性与全局变量等价
- es6中var,function命令声明的全局变量依然是全局对象的属性;而let、const、class命令声明的全局变量则不再是全局对象的属性
变量的解构赋值
数组的结构赋值
基本用法
let [a, b, c] = [1, 'a', ['c']];
console.log(a, b, c); // 1, 'a', ['c']
- 即匹配模式,等号两边格式一样,即可进行变量的赋值
- 若右边表达式不是数组,则会报错;只要有Iterator接口的数据结构都可以使用数组形式的解构赋值
- 适用于var、let、const命令
- 若解构失败变量值为undefined
默认值
- 解构赋值允许有默认值
- ES6内部使用严格相等运算符(===),所以一个数组成员不严格等于undefined是不会使用默认值的
var [a = 1, b = 2, c = 3, d = 4] = [undefined, null, 'c', d];
console.log(a, b, c, d); //1 null "c" 4
对象的解构赋值
var {x, y} = {x: 1, y: 'a' };
console.log(x, y); //1 "a"
var { bar: baz } = { bar: 'hello' };
console.log(baz); //hello
/*下面例子中js会将{}当成代码块,此时需要用小括号括起来*/
var k;
// {k} = {k: 123};会报错,js会将{}当成代码块,此时需要用小括号括起来
({ k } = { k: 123 });
console.log(k); // 123
/*在这个例子中,loc是模式不是变量不会赋值*/
var local = {
loc: {
ui: 'ui',
txt: 'txt'
},
title: '标题'
}
var {
loc: {
ui,
txt
},
title
} = local;
// console.log(loc); loc is not defined
console.log(ui, txt, title); // ui txt 标题
/*对象的解构赋值也能设置默认值,同样只有在值严格为undefined时,默认值才会有用*/
var {bat = 123, bas = 456, bad} = {bat: undefined, bas: null, bad: 'bad'};
console.log(bat, bas, bad); // 123 null "bad"
/*若解构失败变量值为undefined*/
/*对象的解构赋值,可以方便的将对象方法赋值到某个变量中*/
var {pow, random} = Math;
// 将Math对象的次方和随机数方法赋值给了pow和random
console.log(pow(2, 3)); // 8
console.log(random()); // 随机数
/*因为数组也是特殊对象,因此数组也能进行对象的解构赋值*/
var arr = [1, 2, 3];
var {0: first, [arr.length - 1]: last} = arr;
console.log(first, last); // 1 3
字符串的解构赋值
// 将字符串转成了类似数组的对象
const [a, b] = 'hi';
console.log(a, b); // h i
// 字符串有length属性,因此可以对这个对象进行解构赋值
var {
length: len
} = 'hello';
console.log(len); // 5
函数参数的解构赋值
function add([x, y]) {
return x + y;
}
console.log(add([1, 2])) // 3
function move({
x = 0,
y = 0
} = {}) {
return [x, y];
}
console.log(move({
x: 3
})) // [3, 0]
console.log(move({
y: 8
})) // [0, 8]
console.log(move({
x: 3,
y: 8
})) // [3, 8]
解构赋值的应用
- 交换变量值
[x, y] = [y, x];
- 从函数返回多个值
- 函数参数定义和设置默认值
- 遍历Map结构
- 输入模块的指定方法
- 提取JSON
var data = {
id: 1,
name: 'admin',
type: 0,
data: {
goods: [9001, 9002],
goods_type: [0, 1]
}
}
var {
id,
name,
type,
data: {
goods,
goods_type
}
} = data;
console.log(id, name, type, goods, goods_type); // 1 "admin" 0 (2) [9001, 9002] (2) [0, 1]
数组的扩展
新增方法
Array.from
Array.from方法用于将类似数组的对象和可遍历对象(包含set、map)转换成数组
- 实际应用中可以将DOM操作返回的NodeList和函数内arguments转为数组
- 还能接受第二个参数,作用类似于数组的map方法,对每个元素进行处理,将处理后的值放入返回的数组
Array.of
- 用于将一组值转换为数组
var arr1 = Array();
var arr2 = Array(3);
var arr3 = Array(1, 2);
console.log(arr1); // []
console.log(arr2); // [ , , ]
console.log(arr3); // [1, 2]
var arr4 = Array.of(1);
console.log(arr4); // [1]
/*因为数组的构造函数,只有在参数不小于2时才会作为数组值,一个参数时作为数组长度;Array.of则是保持一致,无论参数多少都作为数组元素*/
copyWithin
- copyWithin(target, start, end): 将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组,也就是会修改当前数组
-
参数:
- target(必须): 从该位置开始替换
- start(可选):从该位置读取,默认为0,若为负值则为倒数
- end(可选):读取到该位置结束,默认为数组长度,若为负值则为倒数
find和findIndex
- 参数为一个函数,将数组内元素依次执行该回调函数,找出第一个返回值为true的成员,find是返回这个成员,若没有符合条件的成员则返回undefined;findIndex是返回该成员位置,若没有符合条件的成员则返回-1
- 回调函数,接收三个参数:当前值、当前位置、原数组
fill
- 用给定值,填充一个数组,会覆盖原有全部值
- 可以接受第二、三个参数,用于指定填充的起始和结束位置
数组实例的keys、values、entries
- 都用于遍历数组,返回一个遍历器,可用for...of来遍历
- keys()是对键名,values()是对键值,entries()是对键值对
数组实例的includes
- includes返回一个布尔值,表示数组是否包含给定的值
- 该方法可选第二个参数,代表起始搜索位置,默认为0,若为负值则倒数
[1, 2, 3].includes(2) // true
函数扩展
函数的默认值
- 可以设置函数默认值,有默认值的参数最好作为尾参
- 可使用rest参数, 即...参数
function add(...val) {
let sum = 0;
for (let i of val) {
sum += i
}
console.log(sum);
}
add(1, 2, 3, 4, 5, 6, 7, 8, 9); //45
- rest参数是一个数组,可以使用任意数组方法
- rest参数后面不能再跟任何参数
- rest参数可以替代Array.prototype.slice.call(arguments)
扩展运算符
基本含义
- 扩展运算符就是三个点...,用于将数组转为用逗号分隔的参数序列
function add(...val) {
let sum = 0;
for (let i of val) {
sum += i
}
console.log(sum);
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
add(...arr); //45
用于替代数组apply方法
- 例如将数组追加另一个数组末尾
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
// es5
Array.prototype.push.apply(arr1, arr2);
// es6
// arr1.push(...arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
扩展运算符的应用
合并数组
// es5
var arr1 = [1, 2, 3]
var arr2 = arr1.concat([4, 5, 6], 7);
console.log(arr2); // [1, 2, 3, 4, 5, 6, 7]
// es6
var arr3 = ['a', 'b', 'c'];
var arr4 = [...arr3, ...['e', 'f', 'g'], 'h'];
console.log(arr4); //["a", "b", "c", "e", "f", "g", "h"]
实现了Iterator接口的对象
任何实现了Iterator接口的对象都可以使用扩展运算符转为真正的数组,对于没有Iterator接口的对象可以使用Array.from
箭头函数
基本用法
var f = () => 5;
/*
就等于
var f = function() {
return 5
}
*/
var five = f();
console.log(five); // 5
var f1 = v => v;
/*
就等于
var f1 = function(v) {
return v
}
*/
console.log(f1(10)); // 10
/*代码块部分语句多余一条,那么就需要用大括号括起来,并且用return返回*/
var sum = (num1, num2) => num1 + num2;
var sum2 = (num1, num2) => {
return num1 + num2
};
console.log(sum(10, 20), sum2(10, 20)); // 30 30
// 若要返回一个对象,那么就要将对象用小括号括起来
var getuser = id => ({
id: id,
temp: 'user'
});
console.log(getuser(01)); // {id: 1, temp: "user"}
/*和解构赋值相结合*/
var full = ({
user,
id
}) => 'id is ' + id + ', user is ' + user;
var us = {
id: 1,
user: 'admin'
};
console.log(full(us)); // id is 1, user is admin
箭头函数使用注意点
- 函数体内this对象就是定义时的对象而不是使用时的对象
- 不可以当做构造函数,也就是不能使用new命令,否则会报错
- 不可以使用arguments对象,该对象不在函数体内,若要用,可以使用rest参数代替
- 不可以使用yield命令,因此箭头函数不能用作Generator函数
函数绑定(ES7)
用于取代call、apply、bind
- 写法: 对象::函数
foo::bar; // 等同于 bar.bind(foo);
foo::bar(...arguments) // 等同于 bar.apply(foo, arguments);
- 若左边为空,右边为一个对象的方法,就等于是将该方法绑定到该对象上
let log = ::console.log;
// 等同于 var log = console.log.bind(console);
对象的扩展
属性简洁表示
- 属性简写
/*es6中允许只写属性名,不写属性值;这时属性值等于属性名所代表的变量*/
var foo = 123;
var baz = {
foo
};
console.log(baz); // {foo: 123}
/*等同于
var baz = {foo: foo}
*/
- 方法简写
/*es6中方法也能简写*/
var o = {
say() {
return 'hi!'
}
}
/*等同于
var o = {
say: function() {
return 'hi!'
}
}
*/
console.log(o.say()); // hi!
Symbol类型
var s1 = Symbol('foo');
var s2 = Symbol('foo');
var a = {
[s1]: '123456',
[s2]: '789456'
};
console.log(a[s1], a[s2]);
Set和Map数据结构
Set
基本用法
ES6提供了新的数据结构Set,它类似于数组,但是成员值都是唯一的,没有重复的值
var s = new Set();
[1, 2, 2, 3, 3, 4, 5, 6].map(x => s.add(x));
console.log(...s); // 1 2 3 4 5 6
console.log(s.size); // 6
- Set本身是一个构造函数用来生成set结构数据
- Set函数可以接收一个数组或者类数组的对象作为参数
- Set可以简单的实现数组去重
Set实例的属性和方法
Set实例方法分为操作方法和遍历方法
属性
- size: set对象的个数
操作方法
- add(value): 添加某个值,返回Set结构本身
- delete(value): 删除某个值,返回一个布尔值
- has(value): 返回一个布尔值,表示该值是否是set成员
- clear(): 清除所有成员,没有返回值
遍历方法
set遍历顺序是按照插入顺序
- keys(): 返回键名遍历器
- values(): 返回键值遍历器
- entries(): 返回键值对遍历器
- foreach(): 使用回调函数遍历每个成员
Map
基本用法和目的
js键值对中以往只能用字符串当做键;ES6提供了Map数据结构,它和对象类似也是键值对,但是它可以使用任意类型的值当做键
var m = new Map();
var o = {
name: 'admin',
id: 1
};
m.set(o, 'content');
console.log(m.get(o)); // content
console.log(m.has(o)); // true
console.log(m.delete(o)); // true
console.log(o.name); // admin
/*可以接受数组当参数*/
var mo = new Map([
['first', 10],
[true, 'foo']
]);
console.log(mo.get('first'), mo.get(true)); // 10 foo
- 对同一个键多次赋值,后面的赋值会覆盖前面的
- 只有对同一个对象的引用,Map才会将它当做同一个键
var mo = new Map();
var k1 = ['a'];
var k2 = ['a'];
mo.set(k1, 111).set(k2, 222);
console.log(mo.get(k1), mo.get(k2)); // 111 222
mo.set(['b'], 333);
console.log(mo.get(['b'])); // undefined
// 虽然值相同但是内存地址不同,不是同一个对象
- 若是键是简单类型,那么只要两个值严格相等,那么就视为同一个键,对于NaN,虽然NaN和自己不相等,但map将它视做同一个键
属性和方法
实例的属性和操作方法
- size: 得到成员数量
- set(k, v): 设置成员,返回整个map结构,若键已经存在,则会更新键值;因为set返回自身,所以可以采用链式写法
- get(key): 读取key对应的值,若不存在返回undefined
- has(key): 返回布尔值,表示键是否存在Map结构数据当中
- delete(key): 删除某个键,成功返回true,失败返回false
- clear(): 清除所有成员,没有返回值
遍历方法
set遍历顺序是按照插入顺序
- keys(): 返回键名遍历器
- values(): 返回键值遍历器
- entries(): 返回键值对遍历器
- foreach(): 遍历Map每个成员
Generator函数
- Generator函数为es6提供一种异步编程解决方案
- Generator函数是一个状态机,封装了多个内部状态
- 执行Generator函数会返回一个遍历器对象,也就是说Generator除了是状态机还是一个遍历器对象生成器
- Generator使用时function关键字与函数名之间要加一个星号;函数内部使用yield(英文是产出的意思)语句,定义不同的内部状态
function* test() {
yield 'hello';
yield 'world';
return 'end';
}
var t = test();
console.log(t.next()); // {value: "hello", done: false}
console.log(t.next()); // {value: "world", done: false}
console.log(t.next()); // {value: "end", done: true}
console.log(t.next()); // {value: undefined, done: true}
/*
调用Generator函数后, 该函数并不执行, 返回的也不是函数运行结果, 而是一个指向内部状态的指针对象, 也就是上一章介绍的遍历器对象(Iterator Object).
下一步, 必须调用遍历器对象的next方法, 使得指针移向下一个状态。 也就是说, 每次调用next方法, 内部指针就从函数头部或上一次停下来的地方开始执行, 直到遇到下一个yield语句(或return语句) 为止。 换言之, Generator函数是分段执行的, yield语句是暂停执行的标记, 而next方法可以恢复执行.
*/
next方法的参数
- yield语句本身没有返回值,next方法的参数会被当做上一个yield语句的返回值
for...of循环
- for...of循环可以自动遍历Generator函数时生成的Iterator对象, 且此时不再需要调用next方法
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
};
for (let v of foo()) {
console.log(v);
};
// 1 2 3 4 5
- 一旦next方法的返回对象的done属性为true, for...of循环就会中止, 且不包含该返回对象, 所以上面代码的return语句返回的6, 不包括在for...of循环之中
yield* 语句
- 在一个Generator函数中使用另一个Generator函数
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
};
function* ts() {
yield 'a';
yield* foo(); // yield* 语句
yield 'c';
};
for (let v of ts()) {
console.log(v);
};
// a 1 2 3 4 5 c
作为对象属性的Generator函数
let obj = {
*myGenerator() {
// ...
}
};
- 在属性前面写一个星号,则代表这个属性是Generator函数
async await
异步编程的最终解决方案
let show = async() => {
let a1 = await $.ajax({
url: './data/arr.txt',
type: 'GET',
dataType: 'json'
})
let j1 = await $.ajax({
url: './data/json.json',
type: 'GET',
dataType: 'json'
})
return [a1, j1]
}
show().then(v => {
let [a, j] = v;
console.log(a, j)
})
Promise对象
Promise是异步编程的一种解决方案, 比传统的解决方案——回调函数和事件——更合理和更强大 。Promise对象有以下两个特点:
(1) 对象的状态不受外界影响。 Promise对象代表一个异步操作, 有三种状态: Pending(进行中) 、 Resolved(已完成, 又称Fulfilled)
和Rejected(已失败) 。(2) 一旦状态改变, 就不会再变, 任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。
基本
var test = (resolve, reject) => {
var num = Math.random() * 100;
setTimeout(() => {
if (num > 50) {
// console.log('success');
resolve('200 ok!');
} else {
// console.log('error');
reject('400 fialt!');
}
}, 300)
}
var somepromise = new Promise(test).then((result) => {
console.log('成功' + result);
}, (err) => {
// 若有第二个函数失败后则执行这个,不执行catch
console.log('err' + err);
}).catch((error) => {
console.log('失败' + error);
})
/*
catch用来指定reject的回调,效果和写在then的第二个参数里面一样;
不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中;
这与我们的try/catch语句有相同的功能.
*/
链式调用
var p1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('数据1');
}, 100);
})
}
var p2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('数据2');
}, 100);
})
}
var p3 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('数据3');
}, 100);
})
}
p1().then((data) => {
console.log(data);
return p2();
}).then((data) => {
console.log(data);
return p3();
}).then((data) => {
console.log(data);
})
// 数据1 数据2 数据3
方法
all
Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调
var t1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('t1');
}, 1000);
});
}
var t2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('t2');
}, 600);
});
}
Promise.all([t1(), t2()]).then((data) => {
console.log('全部完成');
console.log(data);
});
// 全部完成 ["t1", "t2"]
race
竞速模式,谁先完成,以谁为准执行回调
var t1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('t1');
}, 1000);
});
}
var t2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('t2');
}, 600);
});
}
Promise.race([t1(), t2()]).then((data) => {
console.log('竞速模式');
console.log(data);
});
// 竞速模式 t2
Class
基本
class Student {
constructor(arr) {
//[name, age, sex]
[this.name, this.age, this.sex] = arr;
}
toString() {
console.log(this.name, this.age, this.sex);
}
}
var vian = new Student(['vian', 16, 'girl']);
vian.toString(); // vian 16 girl
console.log(typeof Student); // function
console.log(Student === Student.prototype.constructor); // true
console.log(vian.toString === Student.prototype.toString); // true
- 类的数据类型就是函数
- 类本身指向构造函数
- 在类的实例上调用方法就是调用原型上的方法
- 类的方法之间不加符号否则会报错
constructor
constructor方法是类的默认方法,new命令生成实例时自动调用该方法,必须有,若不显式定义,则会自动添加一个空的constructor方法;默认返回实例对象(this),也可以指定返回对象
类的实例对象
class Point {
constructor([x, y]) {
[this.x, this.y] = [x, y];
}
toString() {
console.log(...[this.x, this.y]);
}
}
var p1 = new Point([10, 20]);
var p2 = new Point([60, 80]);
console.log(p1.hasOwnProperty('x')); // true
console.log(p1.hasOwnProperty('y')); // true
console.log(p1.hasOwnProperty('toString')); // false
console.log(p1.__proto__.hasOwnProperty('toString')); // true
console.log(p1.__proto__ === p2.__proto__); // true
p1.__proto__.pasName = () => {
console.log('Oops');
}
p1.pasName(); // Oops
p2.pasName(); // Oops
/*
x和y都是实例对象point自身的属性(因为定义在this变量上) , 所以hasOwnProperty方法返回true, 而 toString是原型对象的属性(因为定义在Point类上) , 所以hasOwnProperty方法返回false。 这些都与ES5 的行为保持一致
*/
- 不能像调用函数一样调用class,会报错,要用new生成实例
- 与ES5一样, 实例的属性除非显式定义在其本身(即定义在this对象上) , 否则都是定义在原型上(即定义在class上)
- 与ES5一样, 类的所有实例共享一个原型对象 , 这就意味着可以通过实例的__proto__属性为Class添加方法
- class不存在变量提升,不会对class关键字进行提升,所以必须先定义再使用
class表达式
const myClass = class me {
getName() {
console.log(me.name);
}
}
let t1 = new myClass();
t1.getName(); // me
console.log(myClass.name); // me
// console.log(me.name); // me is not defined
/*
上面代码使用表达式定义了一个类。 需要注意的是, 这个类的名字是MyClass而不是Me, Me只在Class的内部代码可用, 指代当前类。
如果类的内部没用到的话, 可以省略Me, 也就是可以写成下面的形式。
const MyClass = class { //...... };
*/
// 采用Class表达式, 可以写出立即执行的Class
let person = new class {
constructor(name) {
this.name = name;
}
say() {
console.log(this.name);
}
}('张三');
person.say(); // 张三
class的继承
class Color {
constructor([x, y, z]) {
this.arr = [x, y, z];
}
getColor() {
console.log(...this.arr);
}
}
class myColor extends Color {
constructor([x, y, z], a) {
super([x, y, z]);
this.a = a;
}
geta() {
console.log(this.a);
}
}
var c = new myColor(['pink', 'skyblue', 'orange'], 'apple');
c.geta(); // apple
c.getColor(); // pink skyblue orange
- class之间可以通过extends关键字实现继承
- 子类必须要在构造函数中使用super方法,它在这里表示父类的构造函数, 用来新建父类的this对象;这是因为子类没有自己的this对象, 而是继承父类的this对象, 然后对其进行加工。 如果不调用super方法, 子类就得不到this对象
-
Object.getPrototypeOf()
方法可以用来从子类上获取父类 ,因此, 可以使用这个方法判断, 一个类是否继承了另一个类
super关键字
- 作为函数调用时(即super(...args)) , super代表父类的构造函数
- 作为对象调用时(即super.prop或super.method()) , super代表父类。 注意, 此时super即可以引用父类实例的属性和方法, 也可以引用父类的静态方法
实例的__proto__属性
- 子类实例的__proto__属性的__proto__属性, 指向父类实例的__proto__属性。 也就是说, 子类的原型的原型, 是父类的原型
原生构造函数的继承
原生构造函数是指语言内置的构造函数, 通常用来生成数据结构。 ECMAScript的原生构造函数大致有下面这些Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()以前, 这些原生构造函数是无法继承的 ;Es6中可以继承这些
class的get和set
class Test {
constructor(x) {
this.val = x;
}
set pro(val) {
this.val = val;
}
get pro() {
return this.val;
}
}
var t = new Test('测试');
console.log(t.pro); // 测试
t.pro = 123;
console.log(t.pro); // 123
Class的静态方法
class Bar {
static gey() {
console.log(123);
}
}
class Baz extends Bar {}
Baz.gey(); // 123
// 无静态属性,只能如下添加
Baz.ps = 456;
console.log(Baz.ps); // 456
- 静态方法实例无法调用,只有类本身调用
- 静态方法能够被继承
- 静态方法也能从super上调用
- Es6没有静态属性
Module
浏览器
- 浏览器使用ES6模块的语法如下
<script type="module" src="foo.js"></script>
export和import
export
// 写法1
export var one = 1;
// 写法2
var p1 = 1;
var p2 = 2;
export {p1, p2};
// 可以使用as关键字输出别名
export {p1 as pi, p2 as po};
/*
export 1; // 报错
var m = 1;
export m; // 报错
上面两种写法都会报错, 因为没有提供对外的接口。第一种写法直接输出1,第二种写法通过变量m,还是直接输出1。1只是一个值,不是接口。
*/
import
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
import { lastName as surname } from './profile';
- 使用export命令定义了模块的对外接口以后, 其他JS文件就可以通过import命令加载这个模块(文件)
- import命令具有提升效果, 会提升到整个模块的头部, 首先执行
foo();
import { foo } from 'my_module';
// 上面的代码不会报错, 因为import的执行早于foo的调用
整体输出
- 除了指定加载某个输出值, 还可以使用整体加载, 即用星号(*) 指定一个对象, 所有输出值都加载在这个对象上面
/*circle.js文件, 它输出两个方法area和circumference*/
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
/*现在, 加载这个模块*/
// 逐步加载写法
// main.js
import { area, circumference } from './circle';
console.log('圆面积: ' + area(4));
console.log('圆周长: ' + circumference(14));
// 整体加载写法
import * as circle from './circle';
console.log('圆面积: ' + circle.area(4));
console.log('圆周长: ' + circle.circumference(14));
export default
使用import命令的时候, 用户需要知道所要加载的变量名或函数名, 否则无法加载。
为了给用户提供方便, 让他们不用阅读文档就能加载模块, 就要用到export default命令, 为模块指定默认输出 。
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
/*
模块文件export-default.js, 它的默认输出是一个函数
其他模块加载该模块时, import命令可以为该匿名函数指定任意名字
上面代码的import命令, 可以用任意名称指向export-default.js输出的方法, 这时就不需要知道原模块输出的函数名。 需要注意的是, 这时import命令后面, 不使用大括号。
*/
export default命令用在非匿名函数前, 也是可以的
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;
// foo函数的函数名foo, 在模块外部是无效的。 加载的时候, 视同匿名函数加载
// 一个模块只能有一个默认输出, 因此export deault命令只能使用一次
// export default命令其实只是输出一个叫做default的变量, 所以它后面不能跟变量声明语句
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
// 有了export default命令, 输入模块时就非常直观了, 以输入jQuery模块为例
import $ from 'jquery';
// 如果想在一条import语句中, 同时输入默认方法和其他变量, 可以写成下面这样
import customName, { otherMethod } from './export-default';
// 如果要输出默认的值, 只需将值跟在export default之后即可
export default 42;
// export default也可以用来输出类
// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass';
let o = new MyClass();
Tips
几种遍历语法
- for循环
- foreach:为数组提供,但是无法中途结束,return和break都无效
- for...in: 遍历数组键名
- for...of:只有是具有Iterator接口的都能使用,可以中途结束
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。