JS進(jìn)階

閉包

變量作用域

變量根據(jù)作用域不同可以將函數(shù)分為全局變量和局部變量

  1. 函數(shù)內(nèi)部可以使用全局變量
  2. 函數(shù)外部不可以使用局部變量
  3. 函數(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換成constlet或者不加任何修飾符,結(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ī)制
  1. 當(dāng)訪問(wèn)一個(gè)對(duì)象屬性(方法)時(shí),首先查找這個(gè)對(duì)象自身有沒(méi)有該屬性
  2. 如果沒(méi)有就查找它的原型(也就是_proto_指向的prototype原型對(duì)象)
  3. 如果沒(méi)有就查找原型對(duì)象的原型(Object原型對(duì)象)
  4. 以此類(lèi)推,一致找到Object為止
  5. 查找遵循就近原則
原型對(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ì)象,屬性如下

  1. value 設(shè)置屬性的值,默認(rèn)為undefined
  2. writable 值是否可以重寫(xiě),默認(rèn)為false,設(shè)置為true則不能修改
  3. enumerate 目標(biāo)屬性是否可以被枚舉,默認(rèn)為false
  4. 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))
  1. currentValue 數(shù)組當(dāng)前項(xiàng)的值
  2. index 數(shù)組當(dāng)前項(xiàng)的索引
  3. arr 數(shù)組對(duì)象本身
filter
array.filter(function(currentValue, index, arr))
  1. filter()方法創(chuàng)建一個(gè)新的數(shù)組,新數(shù)組中的元素通過(guò)檢查指定數(shù)組中符合條件的所有元素,主要用于篩選數(shù)組
  2. 該函數(shù)直接返回新的數(shù)組
  3. currentValue 數(shù)組當(dāng)前值
  4. index 數(shù)組當(dāng)前項(xiàng)的索引
  5. 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))
  1. some()方法用于檢測(cè)數(shù)組中元素是否滿足指定條件,即查找數(shù)組中是否有滿足條件的元素
  2. 返回的是布爾值,查找成功返回true,失敗返回false
  3. 找到第一個(gè)滿足條件的就終止循環(huán)不再查找
  4. currentValue 數(shù)組當(dāng)前項(xiàng)的值
  5. index 數(shù)組當(dāng)前項(xiàng)的索引
  6. arr 數(shù)組對(duì)象本身
every
array.every(function(currentValue, index, arr))
  1. every()方法用于檢測(cè)數(shù)組中元素是否都滿足指定條件
  2. 返回的是布爾值
  3. currentValue 數(shù)組當(dāng)前項(xiàng)的值
  4. index 數(shù)組當(dāng)前項(xiàng)的索引
  5. arr 數(shù)組對(duì)象本身
map
array.map(function(currentValue, index, arr),thisValue)
  1. map()方法用于對(duì)數(shù)組的每個(gè)元素進(jìn)行處理,返回一個(gè)新的數(shù)組
  2. currentValue 數(shù)組當(dāng)前項(xiàng)的值
  3. index 數(shù)組當(dāng)前項(xiàng)的索引
  4. 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)

  1. forEach()迭代遇到return不會(huì)終止(),NodeJS會(huì)

  2. 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])
  1. thisArg 在函數(shù)運(yùn)行時(shí)的this指向
  2. argsArray 傳遞的值,可以為空,如果不空必須包含在數(shù)組里面
  3. 返回值就是函數(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指向

  1. thisArg 在函數(shù)運(yùn)行時(shí)的this指向
  2. arg1, arg2 傳遞的參數(shù)
  3. 返回有指定的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é)
  1. call()經(jīng)常用作繼承
  2. apply()經(jīng)常跟數(shù)組有關(guān)系
  3. 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)題
  1. 正常模式下全局函數(shù)的this指向的是window,在嚴(yán)格模式下全局函數(shù)this指向的是undefined
'use strict'
function fn() {
  console.log(this) // undefined
}
  1. 普通模式下沒(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
  1. 通過(guò)new創(chuàng)造的實(shí)例this指向?qū)嵗旧?/li>
  2. 定時(shí)器this還是指向window
  3. 事件、對(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)

  1. regexObj 是正則表達(dá)式實(shí)例
  2. str 是要檢測(cè)的文本
  3. 文本符合正則表達(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)證
  1. 假定用戶名規(guī)定只能為英文字母、數(shù)字、下劃線或者短橫線組成,并且用戶名長(zhǎng)度為6-16位
  2. /^[a-zA-Z0-9-_]{6,16}$/
  3. 當(dāng)表單失去焦點(diǎn)時(shí)開(kāi)始驗(yàn)證
  4. 如果符合正則規(guī)范,則讓后面的span標(biāo)簽添加right類(lèi)
  5. 如果不符合正則規(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

  1. 第一個(gè)參數(shù) 被替換的字符串或者正則表達(dá)式
  2. 第二個(gè)參數(shù) 替換為的字符串
  3. 返回值是替換完畢的字符串
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ù)組。

  1. 第一個(gè)參數(shù)是一個(gè)類(lèi)數(shù)組元素
  2. 第二個(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ù)

參考 異步回調(diào)函數(shù)傳值問(wèn)題

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}`));
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容