JavaScript核心知識點(上篇)

NO IMAGE

看了很多 JS 的書籍和教程,往往會模糊焦點,這篇文章會做一個梳理,將 JS 中的重難點進行總結。

文章分為上下兩篇。上篇是基本知識,下篇是進階知識。


#變量類型和計算

引例:

  1. 值類型和引用類型的區別

  2. typeof能判斷哪些類型?

  3. 何時使用 === 何時使用 ==

  4. 手寫深拷貝

變量類型

  • 值類型
  • 引用類型

值類型

let a = 100
let b = a
a = 200
console.log(b) // 100

引用類型

let a = { age: 20 }
let b = a
b.age = 21
console.log(a.age) // 21

分析:

值類型的變量,賦值時會重新將值拷貝一份,因此兩個變量之間相互獨立。

引用類型的變量名指向的是一個內存地址,真實的對象存儲在該內存地址所指向的內存中(堆)。當變量進行賦值時let b = ab 也會指向 a 所指向的那塊內存,兩個變量實際上指向的是同一個對象。

常見值類型

const s = 'abc' // 字符串
const n = 100 // 數字
const b = true // 布爾
const s = Symbol('s') // Symbol

常見值類型

const obj = { x: 100 } // 對象
const arr = ['a', 'b', 'c'] // 數組
const n = null // 空,特殊引用類型,指針指向為空地址
function fn() { } // 函數,特殊引用類型,但不用於存儲數據,所以沒有“拷貝,複製函數”這一說

typeof 運算符

  • 識別所有值類型
  • 識別函數
  • 判斷是否是引用類型(不可再細分)

識別所有值類型

let a                       typeof a // undefined
const s = 'abc'             typeof a // string
const n = 100               typeof n // number
const b = true              typeof b // boolean
const s = Symbol('s')       typeof s // symbol

識別函數

typeof console.log // function
typeof function () { } // function

判斷是否是引用類型(不可再細分)

typeof null // object
typeof ['a', 'b'] // object
typeof { x: 100 } // object

深拷貝

/**
* 深拷貝
*/
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
/**
* 深拷貝
* @param {Object} obj 要拷貝的對象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是對象和數組,直接返回
return obj
}
// 初始化返回結果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保證 key 不是原型的屬性
if (obj.hasOwnProperty(key)) {
// 遞歸調用!!!
result[key] = deepClone(obj[key])
}
}
// 返回結果
return result
}

變量計算

類型轉換:

  • 字符串拼接
  • ==
  • if語句和邏輯運算

字符串拼接

const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const c = true + '10' // 'true10'

==

100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true

注:除了 == null 之外,其他都一律用 === ,例如:

const obj = { x: 100 }
if (obj.a == null) { }
// 相當於:
// if (obj.a === null || obj.a === undefined) { }

if語句和邏輯運算

  • truly變量:!!a === true 的變量

  • falsely變量:!!a === false 的變量

truly變量

!!1 // true
!!{} // true

falsely變量

!!0 // false
!!null // false
!!'' // false
!!undefined // false
!!NaN // false
!!false // false

例子1:if 語句

// truly變量
const a = true
if (a) {
// ...
}
const b = 100
if (b) {
// ...
}
// falsely變量
const c = ''
if (c) {
// ...
}
const d = null
if (d) {
// ...
}
let e
if (e) {
// ...
}

例子2:邏輯判斷

console.log(10 && 0) // 0
console.log('' || 'abc') // 'abc'
console.log(!window.abc) // true

#原型和原型鏈

引例:

  1. 如何判斷一個變量是不是數組?
  2. 手寫一個簡易的jQuery,考慮插件和擴展性。
  3. class的原型本質,怎麼理解?

知識點:

  1. class和繼承
  2. 類型判斷instanceof
  3. 原型和原型鏈

class

  • constructor
  • 屬性
  • 方法

例子:

// 類
class Student {
constructor(name, number) {
this.name = name
this.number = number
// this.gender = 'male'
}
sayHi() {
console.log(`姓名 ${this.name} ,學號 ${this.number}`)
// console.log(
//     '姓名 ' + this.name + ' ,學號 ' + this.number
// )
}
// study() {
// }
}
// 通過類 new 對象/實例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
const madongmei = new Student('馬冬梅', 101)
console.log(madongmei.name)
console.log(madongmei.number)
madongmei.sayHi()

繼承

  • extends
  • super
  • 擴展或重寫方法
// 父類
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat something`)
}
}
// 子類
class Student extends People {
constructor(name, number) {
super(name)
this.number = number
}
sayHi() {
console.log(`姓名 ${this.name} 學號 ${this.number}`)
}
}
// 子類
class Teacher extends People {
constructor(name, major) {
super(name)
this.major = major
}
teach() {
console.log(`${this.name} 教授 ${this.major}`)
}
}
// 實例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
xialuo.eat()
// 實例
const wanglaoshi = new Teacher('王老師', '語文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()

類型判斷 instanceof

xialuo instanceof Student // true
xialuo instanceof People // true
xialuo instanceof Object // true
[] instanceof Array // true
[] instanceof Object // true
{} instanceof Object // true

原型

// class 實際上是函數,可見是語法糖
typeof People // 'function'
typeof Student // 'function'
// 隱式原型和顯示原型
console.log(xialuo.__proto__) // 隱式原型
console.log(Student.__proto__) // 顯示原型
console.log(xialuo.__proto__ === Student.prototype) // true

原型關係

  • 每個class都有顯示原型prototype
  • 每個實例都有影視原型__proto__
  • 實例的__proto__指向對應class的prototype

示意圖:

JavaScript核心知識點(上篇)

基於原型的執行規則

  • 獲取屬性xialuo.name或執行方法xialuo.sayhi()時
  • 先在自身屬性和方法尋找
  • 如果找不到,則自動__proto__去中查找。

原型鏈

console.log(Student.prototype.__proto__)
console.log(People.prototype)
console.log(People.prototype === Student.prototype.__proto__) // true

示意圖

JavaScript核心知識點(上篇)

JavaScript核心知識點(上篇)

引例解答:

如何判斷一個變量是不是數組?

a instanceof Array

手寫一個簡易的jQuery,考慮插件和擴展性。

class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 擴展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造輪子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 擴展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))

class的原型本質,怎麼理解?

  • 原型和原型鏈的圖示
  • 屬性和方法的執行規則

#作用域和閉包

引例:

  • this的不同應用場景,如何取值?
  • 手寫bind函數
  • 實際開發中閉包的應用場景,舉例說明
  • 創建10個a標籤,點擊的時候彈出對應的序號

知識點:

  • 作用域和自由變量
  • 閉包
  • this

作用域

JavaScript核心知識點(上篇)

分類

  • 全局作用域
  • 函數作用域
  • 塊級作用域(ES6新增)
// ES6 塊級作用域
if (true) {
let x = 100 // 只有使用let或const等ES6特性才會觸發塊級作用域
}
console.log(x) // 會報錯

自由變量

  • 一個變量在當前作用域沒有定義,但被使用了
  • 向上級作用,一層一層依次查找,直到找到為止

閉包

  • 作用域應用的特殊情況,有兩種表現:
  • 若函數作為參數被傳遞
  • 函數作為返回值被返回

函數作為參數

// 函數作為參數被傳遞
function print(fn) {
const a = 200
fn()
}
const a = 100
function fn() {
console.log(a)
}
print(fn) // 100

函數作為返回值

// 函數作為返回值
function create() {
const a = 100
return function () {
console.log(a)
}
}
const fn = create()
const a = 200
fn() // 100

所有的自由變量的查找,是在函數定義的地方,向上級作用域查找,不是在執行的地方!!!

this

  1. 作為普通函數
  2. 使用call、apply、bind
  3. 作為對象方法被調用
  4. 在class方法中調用
  5. 箭頭函數

this 取什麼值,是在函數執行的時候確定的,不是在函數定義的時候確定的。該原則適用於以上5種情況。

例子1:普通函數及call、apply、bind中的this

function fn1() {
console.log(this)
}
fn1() // window
fn1.call({ x: 100 }) // { x :100 }
const fn2 = fn1.bind({ x: 200 })
fn2() // { x : 200 }

注:關於call,apply,bind的區別,見 JavaScript 中 call()、apply()、bind() 的用法

例子2:對象方法中的this

const zhangsan = {
name: '張三',
sayHi() {
// this 即當前對象
console.log(this)
},
wait() {
setTimeout(function () {
// this === window
console.log(this)
})
}
}

例子3:箭頭函數中的this

const lisi = {
name: '李四',
sayHi() {
// this 即當前對象
console.log(this)
},
wait() {
setTimeout(() => {
// this === window
console.log(this)
})
}
}

例子4:class中的this

class People {
constructor(name) {
this.name = name
this.age = 20
}
sayHi() {
console.log(this)
}
}
const zhangsan = new People('張三')
zhangsan.sayHi() // zhangsan 對象

引例解答:

1. this的不同應用場景,如何取值?

  • 作為普通函數
  • 使用call、apply、bind
    1. 作為對象方法被調用
  • 在class方法中調用
    1. 箭頭函數

2. 手寫bind函數

// 模擬 bind
Function.prototype.bind1 = function () {
// 將參數拆解為數組
const args = Array.prototype.slice.call(arguments)
// 獲取 this(數組第一項)
const t = args.shift()
// fn1.bind(...) 中的 fn1
const self = this
// 返回一個函數
return function () {
return self.apply(t, args)
}
}
/* 
fn1.__proto__ === Function.prototype // true
*/
function fn1(a, b, c) {
console.log('this', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.bind1({ x: 100 }, 10, 20, 30)
const res = fn2()
console.log(res)

3. 實際開發中閉包的應用場景,舉例說明

  • 隱藏數據
  • 如做一個簡單的cache工具
// 閉包隱藏數據,只提供 API
function createCache() {
const data = {} // 閉包中的數據,被隱藏,不被外界訪問
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
c.set('a', 100)
console.log(c.get('a'))

4. 創建10個<a>標籤,點擊的時候彈出對應的序號

錯誤示例:❎

// 創建10個a標籤,點擊的時候彈出對應的序號
let i, a
for (i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i) // i 是全局作用域
})
document.body.appendChild(a)
}

正確示例:✅

// 創建10個a標籤,點擊的時候彈出對應的序號
let a
for (let i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i) // i 是塊級作用域
})
document.body.appendChild(a)
}

注:若將上面正確示例中for循環的let i = 0改為var i = 10,則其結果同錯誤示例,因為var定義的變量為全局變量。


(未完待續 …)

#異步


#JS-Web-API-DOM


#JS-Web-API-BOM


#JS-Web-API-事件


#JS-Web-API-Ajax


#JS-Web-API-存儲


#開發環境


#運行環境

參考資料: 初級JavaScript面試

相關文章

Flutter會不會被蘋果限制其發展?

2019與子弈的前端之路(乾貨滿滿)|年度徵文

如何全面出色的回答面試官防抖與節流提問?

2020年了,12道高頻JavaScript手寫面試題及答案