閉包
變量作用域
變量根據(jù)作用域不同可以將函數(shù)分為全局變量和局部變量
- 函數(shù)內(nèi)部可以使用全局變量
- 函數(shù)外部不可以使用局部變量
- 函數(shù)執(zhí)行完畢后,本作用域的局部變量會(huì)銷(xiāo)毀
在JS中函數(shù)的閉包有廣泛的應(yīng)用場(chǎng)景,閉包常用于私有化對(duì)象數(shù)據(jù)、事件處理、回調(diào)函數(shù)等
閉包的定義
閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中變量的函數(shù),簡(jiǎn)單來(lái)說(shuō),一個(gè)作用域可以訪問(wèn)另一個(gè)作用域內(nèi)部的局部變量
function func() {
var num = 10
return function () {
console.log(num)
}
}
var f = func()
f() // 10
閉包如何使用
在一個(gè)函數(shù)中定義另一個(gè)函數(shù),并把它暴露出去。有了兩種方式:作為返回值返回;調(diào)用另一個(gè)函數(shù)將它作為參數(shù)。
閉包的使用
循環(huán)注冊(cè)點(diǎn)擊事件
函數(shù)定義完后成立即執(zhí)行,往往只執(zhí)行一次
var lis = document.querySelectorAll('li')
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
console.log(i)
}
}
上面的代碼有一個(gè)問(wèn)題,當(dāng)按鈕點(diǎn)擊的時(shí)候,打印的總是lis.length。因?yàn)辄c(diǎn)擊按鈕的時(shí)候循環(huán)已經(jīng)結(jié)束了
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () {
console.log(i)
}
})(i)
}
上面的立即執(zhí)行函數(shù)構(gòu)成一個(gè)閉包,因?yàn)樗锩嬗幸粋€(gè)匿名函數(shù)使用了它作用域范圍內(nèi)的變量
循環(huán)中的setTimeout()
需求:在等待3秒后打印所有節(jié)點(diǎn)
var lis = document.querySelectorAll('li')
for (var i = 0; i < lis.length; i++) {
(function (i) {
setTimeout(function () {
console.log(lis[i].innerHTML)
}, 3000)
})(i)
}
閉包的案例:打車(chē)計(jì)費(fèi)
var car = (function () {
var start = 13
var total = 0
return {
price: function (n) {
if (n <= 3) {
total = start
} else {
total = start + (n - 3) * 5
}
return total
},
// 擁堵后的費(fèi)用
yd(flag) {
return flag ? total + 10 : total
}
}
})()
console.log(car.price(10)) // 48
console.log(car.yd(true)) // 58
思考題
var name = 'The Window'
var object = {
name: 'My Object',
getNameFunc: function () {
return function () {
return this.name
}
}
}
console.log(object.getNameFunc()())
// The Window
object.getNameFunc()返回的是一個(gè)函數(shù),和后面的括號(hào)構(gòu)成立即執(zhí)行函數(shù),而立即執(zhí)行函數(shù)的this指向window。這段代碼中沒(méi)有閉包,因?yàn)椴粷M足閉包的條件
var name = 'The Window'
var object = {
name: 'My Object',
getNameFunc: function () {
var that = this
return function () {
return that.name
}
}
}
console.log(object.getNameFunc()())
// My Object
getNameFunc構(gòu)成了一個(gè)閉包,因?yàn)樗黵eturn的函數(shù)使用了其的局部變量that。
遞歸
函數(shù)內(nèi)部調(diào)用自己,必須添加遞歸退出條件
無(wú)限遞歸的例子
(function fn() {
console.log('start')
fn()
})()
遞歸的案例
var data = [
{
id: 1,
name: '水果',
goods: [
{
id: 11,
name: '蘋(píng)果'
},
{
id: 12,
name: '梨'
}
]
},
{
id: 2,
name: '蔬菜'
}
]
function getID(json, id) {
var o = {}
json.forEach(function (item) {
if (item.id === id) {
o = item
// 遞歸遍歷子列表
} else if (item.goods && item.goods.length > 0) {
// 注意: getID()是有返回值的,必須有變量進(jìn)行接收
o = getID(item.goods, id)
}
})
return o
}
console.log(getID(data, 11))
變量提升
var定義的變量會(huì)被提升到最前面
cnsole.log(a) // undefined
var a = 10
如果把上面的
var換成const和let或者不加任何修飾符,結(jié)果均報(bào)錯(cuò)
類(lèi)和對(duì)象
類(lèi)繼承和super
class A {
constructor(a, b) {
this.x = a
this.y = b
}
sum() {
return this.x + this.y
}
}
class B extends A {
constructor(a, b, c) {
// super 在this前面
super(a, b)
this.c = c
}
}
console.log(new B(1, 2, 3).sum()) // 3
super關(guān)鍵字調(diào)用父類(lèi)構(gòu)造方法。子類(lèi)在構(gòu)造函數(shù)中使用super,必須在this前面
通過(guò)super關(guān)鍵字調(diào)用父類(lèi)普通方法
class A {
sayHi() {
console.log('Hello')
}
}
class B extends A {
sayHi() {
super.sayHi()
}
}
new B().sayHi() // Hello
類(lèi)里面this指向
constructor里面的this指向的是實(shí)例對(duì)象,方法里面的this指向的是這個(gè)方法的調(diào)用者
var that
class A {
constructor(name) {
that = this
this.name = name
this.btn = document.querySelector('button')
this.btn.onclick = this.click
}
click() {
// console.log(`${this.name} clicked`)
console.log(`${that.name} clicked`)
}
}
var a = new A('aaa')
a.click()
由于上面調(diào)用click函數(shù)的是按鈕,所以
this指向的是按鈕,不能通過(guò)this.name訪問(wèn)到類(lèi)里面的屬性。上面解決方案是用一個(gè)全局變量保存this
對(duì)象
在es6之前,有三種方式構(gòu)造對(duì)象:對(duì)象字面量、new Object()、自定義對(duì)象構(gòu)造函數(shù)
對(duì)象字面量
var obj = new Object()
對(duì)象字面量
var obj = {}
函數(shù)構(gòu)造法
function person(name, age) {
this.name = name
this.age = age
this.sing = function () {
console.log(`${this.name} can sing`)
}
}
var aa = new person('aa', 12)
aa.sing()
構(gòu)造函數(shù)是一種特殊的函數(shù),它主要用來(lái)初始化對(duì)象,總是和
new同時(shí)使用,new在執(zhí)行時(shí)進(jìn)行四件操作:1. 在內(nèi)存中創(chuàng)建一個(gè)新的對(duì)象,2. 讓this指向這個(gè)新的對(duì)象 3. 執(zhí)行構(gòu)造函數(shù)中的代碼,給這個(gè)新對(duì)象添加屬性和方法 4. 返回這個(gè)新的對(duì)象(無(wú)需return)
實(shí)例成員和靜態(tài)成員
實(shí)例成員
只能通過(guò)實(shí)例化的對(duì)象訪問(wèn)的成員
靜態(tài)成員
只能通過(guò)構(gòu)造函數(shù)來(lái)訪問(wèn),不能通過(guò)對(duì)象來(lái)訪問(wèn)
function person(name, age) {
this.name = name
this.age = age
this.sing = function () {
console.log(`${this.name} can sing`)
}
}
person.gender = 'unkonwn'
console.log(person.gender) // unknown
構(gòu)造函數(shù)和原型
構(gòu)造函數(shù)
構(gòu)造函數(shù)的問(wèn)題:每創(chuàng)建一個(gè)對(duì)象,都會(huì)對(duì)開(kāi)辟單獨(dú)空間存放成員,復(fù)雜類(lèi)型如方法等也不例外,造成內(nèi)存空間浪費(fèi)。
function person(name, age) {
this.name = name
this.age = age
this.sing = function () {
console.log(`${this.name} can sing`)
}
}
var aa = new person('aa', 12)
var bb = new person('bb', 22)
console.log(aa.sing === bb.sing) // false
上面兩個(gè)實(shí)例對(duì)象的sing函數(shù)不在同一個(gè)地址,打印的false
構(gòu)造函數(shù)原型prototype
每個(gè)構(gòu)造函數(shù)都有一個(gè)
prototype屬性,它是一個(gè)對(duì)象,稱(chēng)為原型對(duì)象,這個(gè)對(duì)象的所有屬性(通過(guò).prototyoe.prop添加)和方法(通過(guò).prototyoe.func添加)都會(huì)被構(gòu)造函數(shù)所擁有。一般把方法定義在原型上,這樣所有實(shí)例都可以訪問(wèn)這些方法
function person(name, age) {
this.name = name
this.age = age
}
person.prototype.sing = function () {
console.log(`${this.name} can sing`)
}
var aa = new person('aa', 12)
var bb = new person('bb', 22)
console.log(aa.sing === bb.sing)
aa.sing()
對(duì)象原型
對(duì)象都會(huì)有一個(gè)屬性
__proto__指向構(gòu)造函數(shù)的原型對(duì)象。對(duì)象原型__proto__和原型對(duì)象prototype是等價(jià)的,對(duì)象原型是非標(biāo)準(zhǔn)屬性,在實(shí)際開(kāi)發(fā)中,不可以使用
console.log(person.prototype === aa.__proto__) // true
constructor
原型對(duì)象或?qū)ο笤陀幸粋€(gè)屬性
constructor,這個(gè)屬性用于指定構(gòu)造函數(shù)
function person(name, age) {
this.name = name
this.age = age
}
person.prototype.sing = function () {
console.log(`${this.name} can sing`)
}
var aa = new person('aa', 12)
var bb = new person('bb', 22)
console.log(person.prototype.constructor) // [Function: person]
console.log(aa.__proto__.constructor) // [Function: person]
修改原型對(duì)象
一般在函數(shù)方法比較多的時(shí)候會(huì)以對(duì)象形式修改原型對(duì)象,這時(shí)必須手動(dòng)指定原型對(duì)象的構(gòu)造函數(shù)
function person(name, age) {
this.name = name
this.age = age
}
// person.prototype.sing = function () {
// console.log(`${this.name} can sing`)
// }
// 以對(duì)象形式修改原型對(duì)象
person.prototype = {
constructor: person,
sing: function () {
console.log(`${this.name} can sing`)
},
dance: function () {
console.log(`${this.name} can dance`)
}
}
var aa = new person('aa', 12)
var bb = new person('bb', 22)
aa.sing()
原型鏈
Star構(gòu)造函數(shù)的原型對(duì)象(prototype)也具有對(duì)象原型(_proto_),指向的是
Object.prototype,Object構(gòu)造函數(shù)的原型對(duì)象的對(duì)象原型是null

原型鏈查找機(jī)制
- 當(dāng)訪問(wèn)一個(gè)對(duì)象屬性(方法)時(shí),首先查找這個(gè)對(duì)象自身有沒(méi)有該屬性
- 如果沒(méi)有就查找它的原型(也就是_proto_指向的prototype原型對(duì)象)
- 如果沒(méi)有就查找原型對(duì)象的原型(Object原型對(duì)象)
- 以此類(lèi)推,一致找到Object為止
- 查找遵循就近原則
原型對(duì)象里面的this指向問(wèn)題
原型對(duì)象里面的this指向的是實(shí)例對(duì)象
擴(kuò)展內(nèi)置對(duì)象
修改內(nèi)置對(duì)象的原型對(duì)象可以擴(kuò)展自定義方法
Array.prototype.sum = function () {
var sum = 0
// 這里this指向的是實(shí)例對(duì)象
for (var i = 0; i < this.length; i++) {
sum += this[i]
}
return sum
}
繼承
es6之前沒(méi)有提供繼承,可以通過(guò)
構(gòu)造函數(shù)+原型對(duì)象來(lái)模擬實(shí)現(xiàn)繼承,稱(chēng)為組合繼承
call()
有兩個(gè)作用:1.修改函數(shù)運(yùn)行時(shí)的this指向 2.調(diào)用函數(shù)
fun.call(thisArg, arg1, arg2, ...)
thisArg 當(dāng)前調(diào)用函數(shù)this的指向?qū)ο?/p>
arg1, arg2 傳遞的其他參數(shù)
function fn(x, y) {
console.log(this)
console.log(x + y)
}
// fn.call() // 此時(shí)this指向的是Window
var o = {
name: 'andy'
}
// fn.call(o) // 此時(shí)this指向的是對(duì)象o
fn.call(o, 1, 2)
使用call()實(shí)現(xiàn)繼承
function Animal(name) {
this.name = name
}
Animal.prototype.eat = function () {
console.log(`${this.name} need eat`)
}
function Cat(name) {
// 修改了Animal的this指向,實(shí)現(xiàn)繼承Animal的屬性
Animal.call(this, name)
}
// 利用構(gòu)造函數(shù)修改Cat的原型對(duì)象,constructor也會(huì)變成Animal的構(gòu)造函數(shù)
Cat.prototype = new Animal()
// 將Cat的構(gòu)造函數(shù)改回之前的
Cat.prototype.constructor = Cat
var cat = new Cat('huahua')
cat.eat() // huahua need eat
類(lèi)的本質(zhì)
es6類(lèi)的本質(zhì)是函數(shù),它具有構(gòu)造函數(shù)的一切特點(diǎn)
對(duì)象方法
Object.defineProperty()
定義新屬性或者修改原有屬性
Object.defineProperty(obj, prop, descriptor)
obj 新增或者修改屬性的對(duì)象
prop 屬性名
descriptor 是一個(gè)對(duì)象,屬性如下
- value 設(shè)置屬性的值,默認(rèn)為undefined
- writable 值是否可以重寫(xiě),默認(rèn)為false,設(shè)置為true則不能修改
- enumerate 目標(biāo)屬性是否可以被枚舉,默認(rèn)為false
- configurable 目標(biāo)屬性是否可以被刪除或者可以再次修改特性,默認(rèn)為false。設(shè)置一次后不能再次上午好自
var person = {
name: 'siri'
}
// way 1
// person.age = 3
// way 2
Object.defineProperty(person, 'age', {
value: 3,
// 在nodejs中下面這句是必須加的,否則遍歷的時(shí)候不顯示
enumerable: true,
// 不允許刪除
configurable: false
})
console.log(person)
es5中新增的方法
數(shù)組方法
迭代方法:forEach() 、map() 、filter() 、some()、every()
forEach
array.forEach(function(currentValue, index, arr))
- currentValue 數(shù)組當(dāng)前項(xiàng)的值
- index 數(shù)組當(dāng)前項(xiàng)的索引
- arr 數(shù)組對(duì)象本身
filter
array.filter(function(currentValue, index, arr))
- filter()方法創(chuàng)建一個(gè)新的數(shù)組,新數(shù)組中的元素通過(guò)檢查指定數(shù)組中符合條件的所有元素,主要用于篩選數(shù)組
- 該函數(shù)直接返回新的數(shù)組
- currentValue 數(shù)組當(dāng)前值
- index 數(shù)組當(dāng)前項(xiàng)的索引
- arr 數(shù)組對(duì)象本身
var arr = [12, 40, 55, 23]
var newArr = arr.filter(function (value, index) {
return value > 20
})
console.log(newArr) // [ 40, 55, 23 ]
some
array.some(function(currentValue, index, arr))
- some()方法用于檢測(cè)數(shù)組中元素是否滿足指定條件,即查找數(shù)組中是否有滿足條件的元素
- 返回的是布爾值,查找成功返回true,失敗返回false
- 找到第一個(gè)滿足條件的就終止循環(huán)不再查找
- currentValue 數(shù)組當(dāng)前項(xiàng)的值
- index 數(shù)組當(dāng)前項(xiàng)的索引
- arr 數(shù)組對(duì)象本身
every
array.every(function(currentValue, index, arr))
- every()方法用于檢測(cè)數(shù)組中元素是否都滿足指定條件
- 返回的是布爾值
- currentValue 數(shù)組當(dāng)前項(xiàng)的值
- index 數(shù)組當(dāng)前項(xiàng)的索引
- arr 數(shù)組對(duì)象本身
map
array.map(function(currentValue, index, arr),thisValue)
- map()方法用于對(duì)數(shù)組的每個(gè)元素進(jìn)行處理,返回一個(gè)新的數(shù)組
- currentValue 數(shù)組當(dāng)前項(xiàng)的值
- index 數(shù)組當(dāng)前項(xiàng)的索引
- arr 數(shù)組對(duì)象本身
var arr = [2, 4, 6, 8]
var newArr = arr.map(function (value) {
return value + 1
})
console.log(newArr) // [ 3, 5, 7, 9 ]
forEach()和some()的區(qū)別
注意NodeJS和JS中的不同表現(xiàn)
forEach()迭代遇到return不會(huì)終止(),NodeJS會(huì)
some()迭代遇到
return true終止循環(huán),NodeJS遇到return就終止循環(huán)
字符串
trim()
去除字符串
兩邊的字符
應(yīng)用場(chǎng)景:判斷用戶輸入是否為空
var input = document.querySelector('input')
var btn = document.querySelector('button')
btn.onclick = function () {
if (input.value.trim() === '') {
alert('請(qǐng)輸入內(nèi)容')
}
}
startsWith()和endsWith()
這兩個(gè)方法是ES6中字符串新增的方法。
startsWith()表示參數(shù)字符串是否在原字符串的頭部,返回布爾值;endsWith()表示字符串是否在原字符串尾部,返回布爾值
let str = 'Hello World!'
console.log(str.startsWith('Hello')) // true
console.log(str.endsWith('World!')) // true
repeat()
這個(gè)方法也是ES6中的語(yǔ)法。方法將原字符串重復(fù)n次,返回一個(gè)新的字符串
console.log('x'.repeat(5)) // xxxxx
函數(shù)
函數(shù)的定義
使用function關(guān)鍵字
匿名函數(shù)
new Function()
new Function("arg1", "arg2", "函數(shù)體")

函數(shù)均為對(duì)象
函數(shù)的調(diào)用
普通函數(shù)
function fn() {
console.log('hello')
}
fn()
// 或者
fn.call()
對(duì)象的方法
var person = {
sayHi: function () {
console.log('Hi')
}
}
person.sayHi()
構(gòu)造函數(shù)
new 關(guān)鍵字調(diào)用
function Person() {}
var p = new Person()
綁定事件的函數(shù)
由事件觸發(fā)
btn.onclick()=function(){}
定時(shí)器函數(shù)
setInterval(function(){}, 1000)
立即執(zhí)行函數(shù)
(function(){
console.log("Hello")
})()
函數(shù)中的this指向
| 調(diào)用方式 | this指向 |
|---|---|
| 普通函數(shù)調(diào)用 | window |
| 構(gòu)造函數(shù)調(diào)用 | 實(shí)例對(duì)象,原型對(duì)象里面的方法中this也指向?qū)嵗龑?duì)象 |
| 對(duì)象方法函數(shù) | 該方法所屬對(duì)象 |
| 事件綁定方法 | 綁定事件的對(duì)象 |
| 定時(shí)器函數(shù) | window |
| 立即執(zhí)行函數(shù) | window |
改變函數(shù)內(nèi)的this指向
call()
fn.call(obj,param1,param2)
實(shí)例
var o = {
name: 'he'
}
function fn() {
console.log(this)
}
fn.call(o)
apply()
fn.apply(thisArg, [argsArray])
- thisArg 在函數(shù)運(yùn)行時(shí)的this指向
- argsArray 傳遞的值,可以為空,如果不空必須包含在數(shù)組里面
- 返回值就是函數(shù)的返回值
實(shí)例:求最值
var arr = [1, 6, 2, 7]
var max = Math.max.apply(Math, arr)
// 這里Math用null可能會(huì)出問(wèn)題
console.log(max)
var min = Math.min.apply(Math, arr)
bind()
fn.bind(thisArg, arg1, arg2)
bind()方法不會(huì)調(diào)用函數(shù),但是能改變函數(shù)內(nèi)部的this指向
- thisArg 在函數(shù)運(yùn)行時(shí)的this指向
- arg1, arg2 傳遞的參數(shù)
- 返回有指定的this值和初始化參數(shù)改造的原函數(shù)的拷貝
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(a + b)
}
var newFn = fn.bind(o, 1, 2)
newFn(1, 2)
bind應(yīng)用場(chǎng)景:需要改變this指向但不需要立即執(zhí)行
var btn = document.querySelector('button')
btn.onclick = function () {
this.disabled = true
setTimeout(
function () {
this.disabled = false
}.bind(this),
2000
)
}
上面
.bind(this)將定時(shí)器函數(shù)中的this指向由window改變?yōu)閎tn
this在循環(huán)中的應(yīng)用
var btns = document.querySelectorAll('btns')
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
this.disabled = true
setTimeout(
function () {
// btns[i].disabled = false 錯(cuò)誤寫(xiě)法,會(huì)下標(biāo)越界
this.disabled = false
}.bind(this),
2000
)
}
}
call apply bind總結(jié)
- call()經(jīng)常用作繼承
- apply()經(jīng)常跟數(shù)組有關(guān)系
- bind 不調(diào)用函數(shù)但仍想改變this的指向,比如定時(shí)器內(nèi)部的this指向
高階函數(shù)
高階函數(shù)是對(duì)其他函數(shù)進(jìn)行操作的函數(shù),它接受函數(shù)作為參數(shù)或者將函數(shù)作為返回值輸出
// 將函數(shù)作為參數(shù)
function fn(callback) {
callback && callback()
}
// 將函數(shù)作為返回值
function fn() {
return function () {}
}
嚴(yán)格模式
JS除了正常模式外還提供嚴(yán)格模式。嚴(yán)格模式下消除了JS中語(yǔ)法不合理、不嚴(yán)謹(jǐn)之處,減少一些怪異行為;消除代碼一些不安全因素;提高編譯效率和運(yùn)行速度;禁用ECMAScript未來(lái)版本中可能會(huì)定義的語(yǔ)法。如關(guān)鍵字不能作為變量名
為腳本開(kāi)啟嚴(yán)格模式
在整個(gè)腳本開(kāi)啟嚴(yán)格模式,需要在所有語(yǔ)句之前使用
'use strict;'??梢约釉谀_本最上方,也可以加在立即執(zhí)行函數(shù)上。加在立即執(zhí)行函數(shù)的優(yōu)點(diǎn)是創(chuàng)建獨(dú)立作用域,從而不影響引入的非嚴(yán)格模式的文件。
<script>
'use strict;'
</script>
<!-- 加在立即執(zhí)行函數(shù)上 -->
<script>
(function () {
'use strict'
})()
</script>
為函數(shù)開(kāi)啟嚴(yán)格模式
function fn() {
'use strict';
}
嚴(yán)格模式的變化
變量需要先聲明
正常模式下,一個(gè)變量沒(méi)有聲明就賦值,默認(rèn)是全局變量,嚴(yán)格模式下禁止這種用法
'use strict'
num = 10
console.log(num) // error
聲明的變量不能刪除
正常模式下聲明的變量可以用delete刪除,在嚴(yán)格模式下是不可行的
嚴(yán)格模式下this指向問(wèn)題
- 正常模式下全局函數(shù)的this指向的是window,在嚴(yán)格模式下全局函數(shù)this指向的是undefined
'use strict'
function fn() {
console.log(this) // undefined
}
- 普通模式下沒(méi)有加
new關(guān)鍵字當(dāng)普通函數(shù)調(diào)用,嚴(yán)格模式下構(gòu)造函數(shù)不加new調(diào)用,this會(huì)報(bào)錯(cuò)。
'use strict'
function fn(name) {
this.name = name
}
var ff = new fn('aa')
console.log(ff.name) // aa
- 通過(guò)new創(chuàng)造的實(shí)例this指向?qū)嵗旧?/li>
- 定時(shí)器this還是指向window
- 事件、對(duì)象還是指向調(diào)用者
函數(shù)參數(shù)不允許重名
函數(shù)必須聲明在頂層
不允許在非函數(shù)的代碼塊內(nèi)寫(xiě)函數(shù),如if語(yǔ)句中
淺拷貝和深拷貝
淺拷貝只是拷貝一層,更深層次對(duì)象級(jí)別的只拷貝引用;深拷貝拷貝多層,每一級(jí)別數(shù)據(jù)都會(huì)拷貝
淺拷貝
Object.assign(target, source)
var aa = {
name: 'a',
hobbies: ['swim', 'football']
}
var bb = {}
Object.assign(bb, aa)
bb.hobbies[1] = 'sing'
console.log(aa.hobbies) // [ 'swim', 'sing' ]
console.log(bb.hobbies) // [ 'swim', 'sing' ]
淺拷貝后的對(duì)象和原來(lái)對(duì)象引用類(lèi)型數(shù)據(jù)公用一個(gè)地址,會(huì)相互影響
深拷貝
var aa = {
name: 'a',
hobbies: ['swim', 'football'],
friends: {
cc: 18,
dd: 19
}
}
var bb = {}
function deepCopy(newObj, oldObj) {
for (var k in oldObj) {
// 判斷屬性值屬于那種數(shù)據(jù)類(lèi)型
var item = oldObj[k]
// 判斷這個(gè)值是否是數(shù)組
if (item instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], item)
} else if (item instanceof Object) {
// 判斷這個(gè)值是否屬于對(duì)象
newObj[k] = {}
deepCopy(newObj[k], item)
} else {
// 簡(jiǎn)單數(shù)據(jù)類(lèi)型
newObj[k] = item
}
}
}
deepCopy(bb, aa)
console.log(bb)
數(shù)組也屬于對(duì)象,這里需要判斷是否屬于數(shù)組
正則
正則表達(dá)式
用于匹配字符串中的字符組合。在JS中,正則表達(dá)式也是對(duì)象。正則表達(dá)式常見(jiàn)于
檢索、替換、提取。
正則表達(dá)式創(chuàng)建
利用RegExp對(duì)象方式創(chuàng)建
var regexp = new RegExp(/123/)
利用字面量方式創(chuàng)建
var rg = /123/
測(cè)試正則表達(dá)式
regexObj.test(str)
- regexObj 是正則表達(dá)式實(shí)例
- str 是要檢測(cè)的文本
- 文本符合正則表達(dá)式返回true,不符合返回false
var regexp = new RegExp(/123/)
console.log(regexp.test('123')) // true
正則表達(dá)式參數(shù)
/表達(dá)式/[switch] switch(也稱(chēng)為修飾符),表示按照什么方式來(lái)匹配
| 修飾符 | 說(shuō)明 |
|---|---|
| 無(wú) | 只匹配一個(gè)結(jié)果 |
| g | 全局匹配 |
| i | 忽略大小寫(xiě) |
| gi | 全局匹配+忽略大小寫(xiě) |
正則表達(dá)式中的特殊字符
正則表達(dá)式由普通字符和特殊字符(元字符)構(gòu)成。元字符是在正則表達(dá)式中具有特殊意義的專(zhuān)用符號(hào)
邊界符
正則表達(dá)式中的邊界符用來(lái)提示字符所處的位置,主要有兩個(gè)字符
| 邊界符 | 說(shuō)明 |
|---|---|
| ^ | 表示匹配行首的文本 |
| $ | 表示匹配行尾的文本 |
匹配行首
var regexp = new RegExp(/^abc/)
console.log(regexp.test('abcd')) // true
console.log(regexp.test('dabc')) // false
匹配行尾
var regexp = new RegExp(/abc$/)
console.log(regexp.test('abcd')) // false
console.log(regexp.test('dabc')) // true
精確匹配
var regexp = new RegExp(/^abc$/)
console.log(regexp.test('abc')) // true
console.log(regexp.test('dabc')) // false
console.log(regexp.test('abcabc')) // false
字符類(lèi)
[]
| 符號(hào) | 說(shuō)明 |
|---|---|
| [] | 只要匹配其中一個(gè)即可 |
var rg = /[abcd]/
console.log(rg.test('ddos')) // true
只有滿足其中任何一個(gè)
var rg = /^[abcd]$/ // a、b、c、d中的一個(gè)
console.log(rg.test('df')) // false
[-] 方括號(hào)內(nèi)部范圍符
表示某個(gè)范圍內(nèi)的任意字符
var rg = /^[a-z]$/
console.log(rg.test('c')) // true
字符組合
var rg = /^[a-zA-Z0-9]$/
console.log(rg.test(9)) // true
[^]方括號(hào)內(nèi)部取反符
表示不能出現(xiàn)方括號(hào)內(nèi)部任意字符
var rg = /^[^abc]$/
console.log(rg.test('bcd')) // false
() 優(yōu)先級(jí)
表示優(yōu)先級(jí)。比如將
abc作為整體重復(fù)三次
var correct = /^(abc){3}$/ // 正確寫(xiě)法
var wrong = /^abc{3}$/ // 錯(cuò)誤寫(xiě)法,只是c重復(fù)三次
(|) 或者
表示幾個(gè)表達(dá)式符合任何一個(gè)即可
var rg = /^abc|edg$/
console.log(rg.test('edgf')) // false
量詞符號(hào)
量詞符號(hào)用來(lái)設(shè)定某個(gè)模式出現(xiàn)的次數(shù)
| 量詞 | 說(shuō)明 |
|---|---|
| * | 重復(fù)零次或更多次 |
| + | 重復(fù)一次或者更多次 |
| ? | 重復(fù)零次或者一次 |
| {n} | 重復(fù)n次 |
| {n,} | 重復(fù)n次或者更多次 |
| {n, m} | 重復(fù)n次到m次 |
var rg = /^a*$/ // 必須以a開(kāi)頭,或者為空
console.log(rg.test('')) // true
console.log(rg.test('c')) // false
案例:用戶名驗(yàn)證
- 假定用戶名規(guī)定只能為英文字母、數(shù)字、下劃線或者短橫線組成,并且用戶名長(zhǎng)度為6-16位
- /^[a-zA-Z0-9-_]{6,16}$/
- 當(dāng)表單失去焦點(diǎn)時(shí)開(kāi)始驗(yàn)證
- 如果符合正則規(guī)范,則讓后面的span標(biāo)簽添加right類(lèi)
- 如果不符合正則規(guī)范,則讓后面的span標(biāo)簽添加wrong類(lèi)
var rg = /^[a-zA-Z0-9_-]{6,16}$/
var uname = document.querySelector('.uname')
var span = document.querySelector('span')
uname.onblur = function () {
if (rg.test(this.value)) {
span.className = 'right'
span.innerHTML = '用戶名輸入正確'
} else {
span.className = 'wrong'
span.innerHTML = '用戶名輸入錯(cuò)誤'
}
}
預(yù)定義類(lèi)
預(yù)定義類(lèi)是指某些常見(jiàn)模式的簡(jiǎn)寫(xiě)方式
| 預(yù)定義類(lèi) | 說(shuō)明 |
|---|---|
| \d | 匹配0-9之間的任一數(shù)字,相當(dāng)于[0-9] |
| \D | 匹配所有0-9以外的字符,相當(dāng)于[^0-9] |
| \w | 匹配任意的字母、數(shù)字、下劃線,相當(dāng)于[A-Za-z0-9_] |
| \W | 除任意的字母、數(shù)字、下劃線以外的字符,相當(dāng)于[^A-Za-z0-9_] |
| \s | 匹配空格(包括換行符、制表符、空格符),相當(dāng)于[\t\r\n\v\f] |
| \S | 匹配非空格字符,相當(dāng)于[^\t\r\n\v\f] |
案例:電話號(hào)碼
全國(guó)座機(jī)號(hào)碼:兩種格式:010-12345678 或者 0530-1234567
var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/
console.log(reg.test('010-23333333'))
案例:表單驗(yàn)證
var regmsg = /^\d{6}$/
var msg = document.querySelector('.msg')
regExp(msg, regmsg)
function regExp(ele, reg) {
ele.onblur = function () {
if (reg.test(this.value)) {
// 符合條件
} else {
// 不符合條件
}
}
}
替換
replace()方法實(shí)現(xiàn)替換字符串操作,用來(lái)替換的參數(shù)可以是一個(gè)字符串或者一個(gè)正則表達(dá)式
str.replace(regexp/substr, replacement)。如果需要替換所有匹配項(xiàng),帶上修飾符g
- 第一個(gè)參數(shù) 被替換的字符串或者正則表達(dá)式
- 第二個(gè)參數(shù) 替換為的字符串
- 返回值是替換完畢的字符串
var str = 'abc、abc、efg'
console.log(str.replace('abc', 'abcd')) // abcd、abc、efg
console.log(str.replace(/abc/g, 'abcd')) // abcd、abcd、efg
ES6新增語(yǔ)法
let
let聲明的變量只在塊級(jí)有效,var聲明的變量沒(méi)有這個(gè)特點(diǎn)。
for (var i = 0; i < 3; i++) {}
console.log(i) // 3
for (let j = 0; j < 3; j++) {}
console.log(j) // not defined
let聲明的變量沒(méi)有變量提升,只能先聲明后使用
console.log(a) // Cannot access 'a' before initialization
let a = 5
console.log(a) // undefined
var a = 5
使用let聲明的變量具有暫時(shí)性死區(qū)特性。在塊中聲明和外部相同的名稱(chēng)的變量,塊內(nèi)變量會(huì)覆蓋外部變量
var num = 5
if (true) {
console.log(num) // Cannot access 'num' before initialization
let num = 10
}
面試題
var arr = []
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[0]() // 2
arr[1]() // 2
用var聲明的i是全局變量,循環(huán)結(jié)束后調(diào)用函數(shù),這時(shí)函數(shù)訪問(wèn)的是全局中的i,所以結(jié)果為2
var arr = []
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[0]() // 0
arr[1]() // 1
循環(huán)時(shí)產(chǎn)生了多個(gè)塊級(jí)作用域,這些塊級(jí)作用域的i的值各不相同
const
聲明常量,具有塊級(jí)作用域,必須賦初始值,賦值后不能更改(即內(nèi)存地址不能更改)
const、var、let的比較
| var | let | const |
|---|---|---|
| 函數(shù)級(jí)作用域 | 塊級(jí)作用域 | 塊級(jí)作用域 |
| 變量提升 | 不存在變量提升 | 不存在變量提升 |
| 值可以更改 | 值可更改 | 值不可更改 |
解構(gòu)賦值
從數(shù)組數(shù)組和對(duì)象中提取值,為變量賦值
數(shù)組解構(gòu)
let [a, b, ...c] = [1, 2, 3, 4]
console.log(a) // 1
console.log(b) // 2
console.log(c) // [ 3, 4 ]
解構(gòu)不成功,值為undefined
let [foo] =[]
console.log(foo) // undefined
對(duì)象解構(gòu)
let person = {
name: 'aa',
age: 19
}
let { name, age } = person
console.log(name) // aa
console.log(age) // 19
對(duì)解構(gòu)后的變量重新命名
let person = {
name: 'aa',
age: 19
}
let { name: myName, age: myAge } = person
console.log(myName) // aa
console.log(myAge) // 19
箭頭函數(shù)
const fn = ()=>{}
const fn = () => {
console.log('hello')
}
fn() // hello
如果函數(shù)體只有一句代碼,且代碼執(zhí)行結(jié)果就是返回值,可以省略大括號(hào)
const fn = (a, b) => a + b
console.log(fn(1, 2)) // 3
如果形參只有一個(gè),可以省略小括號(hào)
const fn = a => a + 1
console.log(fn(1)) // 2
箭頭函數(shù)中的this指向問(wèn)題
箭頭函數(shù)不綁定this關(guān)鍵字,箭頭函數(shù)中的this指向的是
函數(shù)定義位置的上下文this
沒(méi)有使用箭頭函數(shù)
const obj = {
name: 'aa'
}
function fn() {
console.log(this) // obj
return function () {
console.log(this) // Window
}
}
const resFn = fn.call(obj)
使用了箭頭函數(shù)
const obj = {
name: 'aa'
}
function fn() {
console.log(this) // obj
return () => {
console.log(this) // obj
}
}
const resFn = fn.call(obj)
面試題
var age = 100
var obj = {
age: 20,
say: () => {
console.log(this.age)
}
}
obj.say() // 100
箭頭函數(shù)中的this指向的是Window,在NodeJS中是undefined
剩余參數(shù)
...args將不定數(shù)量的參數(shù)放到一個(gè)數(shù)組中
const sum = (...args) => {
let total = 0
args.forEach((item) => (total += item))
return total
}
console.log(sum(1, 2, 3)) // 6
剩余參數(shù)和解構(gòu)一起使用
let arr = [1, 2, 3]
let [aa, ...bb] = arr
console.log(aa) // 1
console.log(bb) // [ 2, 3 ]
Array
擴(kuò)展運(yùn)算符
擴(kuò)展運(yùn)算符定義
將數(shù)組或者對(duì)象轉(zhuǎn)化為用逗號(hào)分隔的參數(shù)序列
let arr = [1, 2, 3]
console.log(...arr) // 1 2 3
擴(kuò)展運(yùn)算符用于合并數(shù)組
// 方法一
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
let arr = [...arr1, ...arr2]
console.log(arr) // [ 1, 2, 3, 4, 5, 6 ]
// 方法二
arr1.push(...arr2)
console.log(arr1) // [ 1, 2, 3, 4, 5, 6 ]
將偽數(shù)組轉(zhuǎn)化為真正的數(shù)組
DOM元素構(gòu)成的數(shù)組不是真正意義上的數(shù)組,可以通過(guò)擴(kuò)展運(yùn)算符將它轉(zhuǎn)化為真正的數(shù)組
let lis = document.querySelectorAll('li')
lis = [...lis]
console.log(lis)
Array.from()
Array.from(arrLike, item=>{}) 將類(lèi)數(shù)組或可以迭代的對(duì)象轉(zhuǎn)化為真正的數(shù)組。
- 第一個(gè)參數(shù)是一個(gè)類(lèi)數(shù)組元素
- 第二個(gè)參數(shù)類(lèi)似于map方法,對(duì)數(shù)組每項(xiàng)進(jìn)行處理,返回新數(shù)組
對(duì)象轉(zhuǎn)化為數(shù)組
對(duì)象的鍵表示下標(biāo),必須有l(wèi)ength屬性
let arr = {
'0': 1,
'1': 2,
'2': 4,
length: 3
}
let newArr = Array.from(arr, (item) => item ** 2)
console.log(newArr) // [ 1, 4, 16 ]
find()
查找數(shù)組中第一個(gè)符合條件的數(shù)組成員,如果沒(méi)有找到返回undefined
var arr = [
{
id: 1,
name: 'aa'
},
{
id: 2,
name: 'bb'
}
]
console.log(arr.find((item) => item.id === 2)) // { id: 2, name: 'bb' }
findIndex()
用于查找第一個(gè)符合條件的數(shù)組成員的位置,如果沒(méi)有找到返回-1
let arr = [1, 2, 3, 4, 5, 6]
let index = arr.findIndex((item) => item >= 4)
console.log(index) // 3
includes()
表示某個(gè)數(shù)組是否包含給定的值,返回布爾值
let arr = [1, 2, 4, 5, 6]
console.log(arr.includes(2)) // true
模板字符串
ES6新增的創(chuàng)建字符串的形式,使用反引號(hào)定義
模板字符串可以解析變量
function Person(name) {
this.name = name
}
Person.prototype.sayHi = function () {
console.log(`${this.name} say Hi`)
}
let aa = new Person('aa')
aa.sayHi() // aa say Hi
模板字符串可以換行
let person = {
name: 'aa',
age: 15
}
let html = `
<div>
<span>${person.name}</span>
<span>${person.age}</span>
</div>
`
console.log(html)
模板字符串可以調(diào)用函數(shù)
const fn = () => 'fn()'
let html = `${fn()} 在模板字符串中被調(diào)用`
console.log(html) // fn() 在模板字符串中被調(diào)用
Set數(shù)據(jù)結(jié)構(gòu)
Set是一種數(shù)據(jù)結(jié)構(gòu),它類(lèi)似于數(shù)組,但其成員是唯一的,沒(méi)有重復(fù)的值
創(chuàng)建Set
const s1 = new Set()
console.log(s1) // Set(0) {}
console.log(s1.size) // 0
創(chuàng)建Set時(shí)傳遞一個(gè)數(shù)組進(jìn)行初始化
const s1 = new Set([1, 2, 2, 2, 3, 4, 5, 5])
console.log(s1) // Set(5) { 1, 2, 3, 4, 5 }
console.log(s1.size) // 5
利用Set進(jìn)行數(shù)組去重
const arr1 = [1, 2, 2, 2, 3, 4, 5, 5]
const s1 = new Set(arr1)
const arr = [...s1]
console.log(arr) // [ 1, 2, 3, 4, 5 ]
Set實(shí)例的方法
add(value)
添加某個(gè)值,返回修改后的Set
delete(value)
刪除某個(gè)值,返回一個(gè)布爾值,表示刪除是否成功
has(value)
返回一個(gè)布爾值,表示該值是否為Set的成員
clear()
清除所有成員,沒(méi)有返回值
const s = new Set()
console.log(s.add(1).add(2)) // Set(2) { 1, 2 }
console.log(s.delete(1)) // true
console.log(s.has(1)) // false
s.clear()
console.log(s.size) // 0
遍歷Set
Set和數(shù)組一樣,也擁有forEach()方法,用于對(duì)每個(gè)成員執(zhí)行某種操作,沒(méi)有返回值
let s = new Set([1, 2, 3, 4, 5])
s.forEach(item => {
console.log(item)
})
Promise
Promise基礎(chǔ)
為什么使用Promise?Promise解決了異步編程的回調(diào)地獄問(wèn)題?;卣{(diào)是JS實(shí)現(xiàn)異步編程的方式。以下是一個(gè)例子方便了解回調(diào)是如何工作的
function getPosts() {
setTimeout(() => {
console.log('posts fetched')
}, 1000)
}
function createPost(callback) {
setTimeout(() => {
console.log('posts created')
callback()
}, 1000)
}
createPost(getPosts)
當(dāng)創(chuàng)建一個(gè)帖子時(shí),我們希望更新帖子列表,這就需要用到回調(diào)函數(shù),向
createPost傳遞一個(gè)函數(shù),在創(chuàng)建帖子完成后調(diào)用這個(gè)函數(shù)。下面用Promise改寫(xiě)代碼
function getPosts() {
setTimeout(() => {
console.log('posts fetched')
}, 1000)
}
function createPost() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 異步操作的代碼
console.log('post making ... ')
const error = false
if (!error) {
console.log('post created')
resolve()
} else {
reject({ msg: 'post create failed' })
}
}, 1000)
})
}
createPost()
.then(getPosts)
.catch((err) => {
console.log(err)
})
createPost函數(shù)返回一個(gè)Promise,在這個(gè)Promise里面進(jìn)行異步操作。異步操作成功則調(diào)用resolve,失敗則調(diào)用reject。這兩個(gè)回調(diào)函數(shù)都可以傳遞參數(shù)。如果一個(gè)函數(shù)返回的是Promise,則可以使用.then()和.catch()的語(yǔ)法,前者在異步操作成功時(shí)執(zhí)行,后者在異步操作失敗時(shí)執(zhí)行。大多數(shù)情況下,Promise不是我們自己來(lái)構(gòu)造,如使用mongoose進(jìn)行數(shù)據(jù)庫(kù)操作,axios、fetch進(jìn)行網(wǎng)絡(luò)請(qǐng)求,它們返回的結(jié)果都是Promise,我們只需處理這些Promise。
Promise.all()
用于批量處理Promise,接受一個(gè)數(shù)組。Promise.all()使用場(chǎng)景:多個(gè)異步操作可以同時(shí)進(jìn)行,如同時(shí)查詢(xún)用戶A和B的資料。如果兩個(gè)異步操作是有先后順序的,則不能使用Promise.all()
const promise1 = Promise.resolve('Hello')
const promise2 = 10
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'ok')
})
const promise4 = fetch('https://v1.hitokoto.cn').then((res) => res.json())
Promise.all([promise1, promise2, promise3, promise4]).then((values) => console.log(values))
async和await
每個(gè)異步函數(shù)都會(huì)返回一個(gè)Promise(用
async標(biāo)記的函數(shù)如果有return語(yǔ)句,返回結(jié)果一定是Promise),await的對(duì)象也是一個(gè)Promise,這點(diǎn)非常重要
function getPosts() {
setTimeout(() => {
console.log('posts fetched')
}, 1000)
}
function createPost() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 異步操作的代碼
console.log('post making ... ')
const error = false
const bb = 100
if (!error) {
console.log('post created')
resolve(error, bb)
} else {
reject({ msg: 'post create failed' })
}
}, 1000)
})
}
async function init() {
await createPost()
getPosts()
}
init()
// post making ...
// post created
// posts fetched
await createPost()表示createPost()執(zhí)行結(jié)束后才會(huì)執(zhí)行后面的代碼
async/await捕獲異常
使用await之后,異步代碼代碼執(zhí)行有變得同步,可以使用try/catch語(yǔ)句來(lái)捕獲異常
myApp.registerEndpoint('GET', '/api/firstUser', async function(req, res) {
try {
let firstUser = await getFirstUser();
res.json(firstUser)
} catch (err) {
console.error(err);
res.status(500);
}
});
沒(méi)有使用await的情況
一般情況下,如果函數(shù)返回的是Promise,那么必須使用await來(lái)獲取結(jié)果,除非你確實(shí)需要這個(gè)Promise,參考記住Promise
async/async的應(yīng)用
async function fetchData() {
const res = await fetch('https://v1.hitokoto.cn')
const data = await res.json()
console.log(data)
}
fetchData()
async和await的好處是避免使用
.then()和.catch()的語(yǔ)法。注意fetch()和res.json()返回的都是Promise。
使用Promise改寫(xiě)回調(diào)函數(shù)
Promise.race()
將返回最先完成的Promise的結(jié)果。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one')
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two')
})
Promise.race([promise1, promise2])
.then((value) => {
console.log(value)
// Both resolve, but promise2 is faster
})
.catch((err) => {
console.log(err)
})
兩個(gè)promise結(jié)果都是resolve,但promise2先返回,所以只有promise的結(jié)果。
Promise.race()可用于判斷請(qǐng)求是否超時(shí),如用定時(shí)函數(shù)設(shè)置2s后reject,這樣不管請(qǐng)求完成如何,只要超過(guò)2s秒,結(jié)果都是reject
const promise1 = new Promise((resolve, reject) => {
setTimeout(reject, 2000, 'one')
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'two')
})
Promise.race([promise1, promise2])
.then((value) => {
console.log(value)
})
.catch((err) => {
console.log(err)
})
高階函數(shù)使用Promise
const arr = [ { key: 1 }, { key: 2 }, { key: 3 } ]
const results = arr.map(async (obj) => { return obj.key; });
// document.writeln( `Before waiting: ${results}`);
Promise.all(results).then((completed) => document.writeln( `\nResult: ${completed}`));