33-JS-Concepts 笔记

357 阅读14分钟

一、数据类型

1.数据类型

原始数据类型:

  • number
  • string
  • boolean
  • bigint
  • symbol
  • undefined
  • null

引用数据类型:

  • object

2.类型判断

常用的类型判断方法有typeof、instanceof、toString。

typeof

对于原始类型,除了null,都会显示正确类型。

对于引用类型,除了函数外,都会显示"object"。

(因为js认为000开头的是对象,而null所有位都是0,所以null会被误认为对象)

typeof "1" // "string"
typeof 1 // "number"
typeof true // "boolean"
typeof 1n // 'bigint
typeof Symbol() // 'symbol'
typeof undefined // "undefined"
typeof null // "object" 👀

typeof [] // "object"
typeof {} // "object"
typeof new Set() // "object"
typeof new Map() // 'object'
typeof function(){} // "function" 👀

instanceof

能判断引用类型,但不能判断基本类型。返回布尔值。

1 instanceof Number // false 👀

[] instanceof Array // true
new Object instanceof Object // true
new Set() instanceof Set // true
new Map() instanceof Map // true
const f = () => {}
f instanceof Function // true

Object.prototype.toString.call

可以判断所有类型。

(因为实例对象有可能会自定义toString方法,会覆盖Object.prototype.toString,所以在使用时,最好加上call)

image.png

Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call('1') // '[object String]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(1n) // '[object BigInt]'
Object.prototype.toString.call(Symbol()) // '[object Symbol]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(() => {}) // '[object Function]'

封装判断

观察Object.prototype.toString.call的返回结果可以想到,截取返回的第二个单词,转为小写即可。

const getType = (target) => {
    return Object.prototype.toString.call(target).slice(8, -1).toLowerCase()
}
getType(1) // 'number'

3.类型转换

显示和隐式

  • 显示转换:Number()、String()、Boolean()、toString()、parseInt()
  • 隐式转换:运算符(+、-、*、/、%)和==

三种转换类型

  • to number:+、-、*、/、%
  • to string:+
  • to boolean:!

to number:

Number('') // 0 空字符串
Number('1') // 1 纯数字字符串
Number(true) // 1
Number(false) // 0
Number(1n) // 1
Number(null) // 0
Number([1]) // 1 单个元素数组
// 其它都是NaN

to string:

String({a: 1}) // "[object Object]" 返回对象类型
String([1, 2, 3]) // "1,2,3" 返回数组中每一项,用逗号连接
// 其它都是本身

to boolean:

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
// 其它都是true,包括[]、{}

==和===

===直接比较值是否相等,==先转换类型,过程如下:

  1. null vs undefined:true
  2. string vs number:string转为数字,然后比较
  3. boolean vs anything:boolean转为数字,然后比较
  4. object vs string|number:object转为原始类型(toPrimitive),然后比较

*总之就是尽量往数字转。

toPrimitive

对象转原始类型,会调用内置的ToPrimitive函数,过程如下:

  1. 调用valueOf(),如果转换为原始类型,则返回
  2. 调用toString(),如果转换为原始类型,则返回
  3. 报错

valueOf一般返回本身,toString如下:

[1].toString()  // '1'
[1, 2, 3].toString()  // '1,2,3'
{}.toString() === '[object object]'

如何让if(a == 1 && a == 2)条件成立?

其实就是上一个问题的应用,触发隐式转换时,让a的valueOf返回的值加一即可:

var a = {
  value: 0,
  valueOf: function() {
    this.value++;
    return this.value;
  }
};
console.log(a == 1 && a == 2);  // true

[] == ![]结果是什么?

[] == ![]  // []布尔值为true,![]为false
↓
[] == false  // boolean vs anything,布尔值转数字,false转为0
↓
[] == 0  // object vs number,调用toPrimitive,[]转为'''' == 0  // string vs number,字符串转数字,''转为00 == 0true

4.数字相关问题

NaN

NaN是数字,但是Nan不等于NaN。

typeof NaN // 'number'
NaN === NaN // false

小数不精确

问题:小数用2的负几次方表示,很多时候除不尽需要四舍五入,所以小数会失去精度。

0.1 + 0.2 // 0.30000000000000004

解决:使用js库,比如math.js。

最大安全数是Number.MAX_SAFE_INTEGER

问题:整数用1位表示符号,11位表示指数,52位表示有效数字,52个1的值为(2⁵³−1)。超过最大安全数以后不能用1和0精确表示,所以可能不精确。

image.png

image.png

Number.MAX_SAFE_INTEGER // 9007199254740991(16位)
2**53 // 9007199254740992
2**53 + 1 // 9007199254740992

解决:使用BigInt

2n**53n // 9007199254740992n
2n**53n + 1n // 9007199254740993n

能表示的最大值是Number.MAX_VALUE

Number.MAX_VALUE // 1.7976931348623157e+308(308位)

无限大Infinity

Infinity // Infinity

5.数组相关问题

数组常用方法

  • push、pop、shift、unshift、slice、splice
  • includes、indexOf、find、findIndex
  • map、forEach、reduce
  • filter、every、some
  • join、concat、sort、reverse

数组中是否包含某个值

includes:参数是元素,返回true/false

const arr = [1, 2, 3]
const result = arr.includes(2)
result  // true

indexOf:参数是元素,返回index

const arr = [1, 2, 3]
const result = arr.indexOf(2)
result  // 1

find:参数是函数,返回元素

const arr = [1, 2, 3]
const result = arr.find(item => {
    return item === 2
})
result  // 2

findIndex:参数是函数,返回index

const arr = [1, 2, 3]
const result = arr.findIndex(item => {
    return item === 2
})
result  // 1

类数组转数组

常见的类数组有:

  • 函数的参数arguments([object Arguments])
  • querySelector的结果([object HTMLCollection])
  • getElementsByTagName/ClassName的结果([object HTMLCollection])

转数组的方式:

Array.from()

const args = Array.from(arguments);

ES6展开运算符

const args = [...arguments]

如何终端forEach循环

在forEach中使用break会报错,而使用return只能跳过一次,不能中断整个循环。

const arr = [1, 2, 3, 4, 5]
arr.forEach(item => {
    if (item === 3) return
    console.log(item)
})
// 1 2 4 5

中断方法:

  • 通过抛出异常中断。
  • 使用for of遍历数组,用break中断。
const arr = [1, 2, 3, 4, 5]
for (const item of arr) {
    if (item === 3) break
    console.log(item)
}
// 1 2

6.对象相关问题

防止修改对象

Object.preventExtensions:不可以增加,可以修改,可以删除。

var obj = {
    id: 42,
}
Object.preventExtensions(obj)
obj.name = "jack"  // {id: 42} ❌
obj.id = 1  // {id: 1} ✔
delete obj.id  // {} ✔

Object.seal:不可以增加,不可以删除,可以修改

var obj = {
    id: 42,
}
Object.seal(obj)
obj.name = "jack"  // {id: 42} ❌
obj.id = 1  // {id: 1} ✔
delete obj.id  // {id: 1} ❌

Object.freeze:不可以增加,不可以修改,不可以删除

var obj = {
    id: 42,
}
Object.seal(obj)
obj.name = "jack"  // {id: 42} ❌
obj.id = 1  // {id: 42} ❌
delete obj.id  // {id: 42} ❌

7.引用类型的赋值、浅拷贝和深拷贝

赋值:值改变的时候,会一起改变。

let arr = [1, 2, {name: 'Jack'}]
let arr2 = arr
arr[2] = 3
arr2 // [1, 2, 3]

浅拷贝:基础类型的值改变,互不影响;引用类型的值改变,会一起改变。

方式:slice、concat、展开运算符([...arr])、Object.assign、展开运算符({...obj})、手写。

let arr = [1, 2, {name: 'Jack'}]
let arr2 = arr.slice()
arr[2].name = 'Mike'
arr2 // [1, 2, {name: 'Mike'}]

深拷贝:值改变的时候,互不影响。

方式:JSON.parse(JSON.stringify())、手写。

let arr = [1, 2, {name: 'Jack'}]
let arr2 = JSON.parse(JSON.stringify(arr))
arr[2].name = 'Mike'
arr2 // [1, 2, {name: 'Jack'}]

👆原因都是:基本类型是拷贝值,引用类型是拷贝地址。

二、面向对象

1.创建一个对象

对象字面量

const person = {
    name: 'Jack',
    say: function() {
        console.log('hello')
    }
}

构造函数

var person = new Object({
    name: 'Jack',
    say: function() {
        console.log('hello')
    }
})

Object.create()

const person = Object.create(null)
person.name = 'Jack'
person.say =  function() {
    console.log('hello')
}

使用new

// 减少了代码量
function Person(name) {
    this.name = name
}
Person.prototype.say = function() {
    console.log('hello')
}
const person = new Person('Jack')

使用class(上面的语法糖)

class Person {
    constructor(name) {
        this.name = name
    }
    say() {
        console.log('hello')
    }
}
const person = new Person('Jack')

Q:Object.create()传入的参数是什么?

A:生成对象的_proto_。(可以用来实现寄生组合继承)

const parent = {name: 'Jack'}
const child = Object.create(parent)
child  // {}
child.name  // Jack

Q:使用new关键字创建一个新对象时,如果没有加new会怎么样?

A:不会报错,只是给窗口命名了,而函数返回默认的undefined。

function Person(name) {
    this.name = name
}
const person = Person('Jack')
person  // undefined
window.name  // 'Jack'

2.继承

使用extends

class Child extends Parent {}

// 不写constructor也会默认执行
class Child extends Parent {
    constructor(...args) {
        super(...args)
    }
}

super:

  • 作为函数调用的时候,是父类的constructor()。
  • 作为对象调用的时候,是父类的原型对象。
class Parent {
    constructor(name) {
        this.name = name
    }
    say() {
        console.log('hello')
    }
}

class Child extends Parent {
    constructor(name, age) {
        super(name)
        this.age = age
    }
    say() {
        super.say()
    }
}

3.原型、构造函数和实例

原型、构造函数和实例之间的关系:

const obj = new Object()
obj.__proto__  // 原型
Object  // 构造函数
obj  // 实例
obj.__proto__ === Object.prototype  // true
obj.__proto__.constructor === Object  // true
obj.__proto__.__proto__ === null  // true

image.png

4.原型链

每个对象都有__proto__属性,指向它的原型。访问一个属性的时候,如果对象本身没有,就会到它的原型去查找,以此类推,构成原型链。原型链的尽头是null。

  • 使用hasOwnProperty()检查对象自身是否包含该属性。
  • 使用in检查原型链上是否有该属性。
const user = Object.create({name: 'Jack'})
user.hasOwnProperty('name')  // false
'name' in user  // true

5.this,call,apply和bind

this指向:

  • 全局调用:指向window。
  • 对象调用:指向最后调用函数的对象。
  • 箭头函数:指向所在执行上下文的this。
  • new:指向创建的新对象。
  • call、apply、bind:指向传入的对象。

call、apply和bind的区别:

  • call:马上调用,参数一个个传入。
  • apply:马上调用,参数用一个数组传入。
  • bind:不会马上调用,参数一个个传入。

三、执行机制

重点是要牢牢记住执行栈的概念。

执行过程:

  • 编译阶段
    • 源代码 -> AST(抽象语法树) + 执行上下文
    • AST -> 字节码
  • 执行阶段执行

Q:为什么生成字节码,而不直接生成执行效率更高的机器码?

A:因为机器码占用内存很多,在内存小的设备上(如手机)性能不好。

image.png

1.执行上下文

执行上下文(Execution Context)的类型:

  • 全局执行上下文
  • 函数执行上下文
  • eval函数执行上下文

在编译阶段和执行阶段:

  • 编译阶段:声明变量和函数(此时var为undefined,let/const为uninitialized)
  • 执行阶段:赋值和执行代码

包含三个部分:

  • 环境记录(EnvironmentRecord):变量和函数
  • 外部环境(outer)
  • this指向(this)

image.png

2.执行栈

执行栈(Execution Stack)用于储存代码执行过程中创建的所有执行上下文调用一个函数的时候,js引擎会编译该函数,为其创建一个执行上下文并压入栈中,执行结束后弹出

image.png

3.变量提升

变量和函数生命作为执行上下文的一部分,在编译阶段,先被放入内存中。

var,let和const的区别:

  • var可以重复声明。
  • let不能重复声明,可以重复赋值。
  • const不能重复赋值,但如果赋值是引用对象,引用对象可以被修改(因为赋值其实是一个地址)。
  • 如果想让引用对象不能被修改,可以使用Object.freeze()。

为什么要引入let和const?

  • 变量容易在不被察觉的情况下被覆盖掉。

4.作用域

作用域:变量和函数的可访问范围。

作用域链:访问一个变量的时候,如果在执行上下文的环境记录里找不到,会到外部环境去找。

外部环境由函数声明的位置决定。

5.闭包

闭包:父函数执行结束后,它的执行上下文会被弹出执行栈,但如果其中有变量被子函数访问,且子函数被返回出去了,那么这些变量会一直存在内存里,称为闭包,子函数一直可以访问到。

闭包的回收:

  • 如果引用闭包的函数是全局变量,闭包会一直存在到页面关闭。
  • 如果引用闭包的函数是局部变量,则会被垃圾回收。

6.垃圾回收

代际假说

  • 大部分对象的存活时间很短。
  • 不死的对象,会活得更久。

垃圾回收:

  • 新生代
    • 存放生存时间短的对象。
    • 使用scavenge(捡破烂)算法。
    • 分为对象区域和空闲区域,新对象存入对象区域,存满后垃圾回收,剩余的有序放入空闲区域,然后两个区域对调。
    • 两次垃圾回收后还存活的对象,放入老生代。
  • 老生代
    • 存放生存时间长的对象。
    • 使用标记-存活算法。
    • 标记所有对象,被引用的对象把标记去除,剩下带标记的对象就可以回收。

*新生代相当于有两个垃圾桶,先往1号扔垃圾,装满以后把可能还有用的放进2号垃圾桶,剩下都倒掉,然后互换位置。

四、异步

1.事件循环

事件循环是指执行一个宏任务,然后清空微任务列表,再执行下一轮宏任务

宏任务

  • js脚本
  • setTimeout回调
  • 用户交互
  • 界面渲染

微任务

  • promise回调
  • precess.nextTick

如果有setTimeout的回调,会放入下一轮宏任务。

先后顺序:同步 -> process.nextTick -> Promise回调 -> setTimeout/setInterval回调

image.png

setTimeout执行过程:

  1. 执行栈中执行setTimeout,回调放入Web API
  2. 计时器到期后,回调放入消息队列
  3. 执行栈为空后,回调放入执行栈

image.png

2.Promise基本概念

Promise对象用于表示一个异步操作的最终结果(完成或失败及其结果值)

image.png

Promise的三种状态

  • pending:等待
  • fulfilled:成功
  • rejected:失败

*一旦状态改变,就不会再变。

Promise会立即执行

作为参数传入Promise的函数(executor)会立即执行

Generator

协程

(todo)

4.async / await

基于Generator实现。

image.png

image.png

五、DOM和BOM

DOM

DOM中的继承关系:

null <- Object <- EventTarget <- Node <- Document

null <- Object <- EventTarget <- Node <- Element <- HTMLElement <- HTMLButtonElement

node

image.png

节点类型:

image.png

每个节点都有三种层级关系:

  • 父节点(parentNode)
  • 子节点(childNodes)
  • 兄弟节点(sibling)

node常用属性:

node.parentNode
node.childNodes
node.previousSibling
node.nextSibling

node常用方法:

node.appendChild()
node.insertBefore()
node.removeChild()
node.replaceChild()
node.contains() ⭐  // 是否包含自身/子节点/后代节点

document

document代表整个文档。

image.png

document常用属性:

document.title
document.cookie
document.URL
document.readyState  // 文档的加载状态

document常用方法:

document.querySelector()
document.querySelectorAll()
document.getElementById()
document.getElementsByTagName()
document.getElementsByClassName()

document.createElement() ⭐
document.createDocumentFragment() ⭐

element

element也就是开发者工具里的Elements那一栏。

image.png

element常用属性:

element.tagName
element.className
element.style
element.innerHTML
element.contentEditable

element.clientHeight
element.clientTop
element.scrollHeight
element.scrollTop
element.offsetHeight
element.offsetTop

element常用方法:

element.querySelector()
element.querySelectorAll()
element.getElementsByClassName()
element.getElementsByTagName()
// 没有element.getElementById()

element.scrollIntoView() ⭐
element.getBoundingClientRect() ⭐
element.click()
element.focus()
element.blur()
element.remove()

BOM

BOM(Browser Object Mode)主要包含以下部分:

  • window:窗口
  • location:url
  • history:浏览历史
  • navigator:浏览器
  • screen:屏幕

BOM中的继承关系:

null <- Object <- Location

null <- Object <- EventTarget <- WindowProperties <- Window

window

window是全局,所以window的方法都可以直接调用:

window.open('http://google.com/')  // 常用的写法
open('http://google.com/') // 与上面等价

window对象的 window 属性指向这个window对象本身:

window === window.window // true

每个标签都拥有自己的window对象。如果一个方法无法恰当地作用于标签,那么它就会作用于整个窗口,如window.resizeTo。

window的常用方法:

  • open、close、stop、focus、blur
  • scrollTo、scroll、scrollBy
  • alert、prompt、confirm

document

用于获取DOM对象。

location

location用于获取当前窗口的URL信息。

image.png

location = "https://www.baidu.com/"  // 跳转到新页面
location.reload()  // 重新加载当前页面

history

history用于获取当前窗口的浏览历史

history.back()  // 后退
history.forward()  // 前进
history.go(-2)  // 后退两次
history.pushState()  // 增加一条历史(会改变当前url,但不刷新页面)
history.replaceState()  // 修改当前历史(会改变当前url,但不刷新页面)

navigator

navigator用于获取浏览器信息。

navigator.userAgent
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36"
// 不建议用userAgent属性识别浏览器。

screen

screen用来获取当前窗口所在的屏幕信息。如screen.width,screen.orientation。

screen.width // 1920
screen.orientation // ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}

Event

event中的继承关系:

null <- Object <- Event <- UIEvent <- MouseEvent <- WheelEvent

事件类型

  • 鼠标事件:click、dblclick、contextmenu、mousedown、mouseup、mouseenter、mouseleave、mousemove、mouseover、mouseout、pointerlockchange、pointerlockerror、select、wheel
  • 键盘事件:keydown、keypress、keyup
  • 剪贴板事件:cut、copy、paste
  • 表单事件:reset、submit
  • 焦点事件:focus、blur
  • 视图事件:fullscreenchange、fullscreenerror、resize、scroll
  • 文本写作事件:compositionstart、compositionupdate、compositionend
  • 网络事件:online、offline
  • 资源事件:error、abort、load、beforeunload、unload
  • WebSocket事件:open、message、error、close
  • 历史会话事件:pagehide、pageshow、popstate
  • CSS动画事件:animationstart、animationend、animationiteration
  • CSS过渡事件:transitionstart、transitioncancel、transitionend、transitionrun
  • 打印事件:boforeprint、afterprint
  • 拖放事件:drag、dragstart、dragend、dragenter、dragleave、dragover、drop
  • 媒体事件:audioprocess、canplay、canplaythrough、complete、durationchange、emptied、ended、loadeddata、pause、play、playing、ratechange、seeked、seeking、stalled、suspend、timeupdate、volumechange、waiting
  • 进度事件:abort、error、load、loadend、loadstart、progress、timeout
  • 其它事件:input、change、hashchange、readystatechange、storage、message等等

eventTarget

window、document、element都是event targets。

eventTarget方法:

target.addEventListener()
target.removeEventListener()
target.dispatchEvent()

事件传播

三个阶段:

  • 捕获阶段:从window传播到目标节点。
  • 目标阶段:在目标节点触发。
  • 冒泡阶段:从目标节点传播回window。

事件监听:因为事件在冒泡阶段会传播到父节点,所以可以在父节点统一监听子节点的事件。

target和currentTarget的区别:

  • target:触发事件的节点
  • cunrentTarget:监听事件的节点

阻止冒泡:

event.stopPropagation()

自定义事件:

new CustomEvent(type, options)

附录:33-js-concepts 分类

数据类型
2.Primitive Types
3.Value Types and Reference Types
4.Implicit, Explicit, Nominal, Structuring and Duck Typing
5.== vs === vs Typeof
24.Collections and Generations

面向对象
14.Factories and Classes
15.this, call, apply and bind
16.new, Constructor, instanceof and Instances
17.Prototype Inheritance and Prototype Chain
18.Object.create and Object.assign
30.Inheritance, Polymorphism and Code Reuse

执行机制
1.Call Stack
6.Function Scope, Block Scope and Lexical Scope
11.JavaScript Engines
21.Closures

异步
9.Message Queue and Event Loop
10.setTimeout, setInterval and requestAnimationFrame
25.Promises
26.async/await

DOM和BOM
13.DOM and Layout Trees

其它
7.Expression vs Statement
8.IIFE, Modules and Namespaces
12.Bitwise Operators, Type Arrays and Array Buffers
19.map, reduce, filter
20.Pure Functions, Side Effects, State Mutation and Event Propagation
22.High Order Funcitons
23.Recursion
27.Data Structure
28.Expensive Operation
31.Design Patterns
32.Partial Apolications, Currying, Compose and Pipe

参考

33-js-concepts
为你保驾护航金三银四,直通大厂(上)
原生JS灵魂之问, 请问你能接得住几个?(上)
浏览器工作原理与实践
JavaScript 标准参考教程

数据类型:
Here is what you need to know about JavaScript’s Number type
Diving Deeper in JavaScripts Objects
JavaScript type coercion explained
== vs === JavaScript: Double Equals and Coercion

面向对象:
Let’s demystify JavaScript’s ‘new’ keyword
Javascript : Prototype vs Class
JavaScript engine fundamentals: optimizing prototypes

执行机制:
🚀⚙️ JavaScript Visualized: the JavaScript Engine
Understanding Execution Context and Execution Stack in Javascript
The Ultimate Guide to Hoisting, Scopes, and Closures in JavaScript

异步:
⭐️🎀JavaScript Visualized: Promises & Async/Await
BAT前端经典面试问题:史上最最最详细的手写Promise教程
剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类

等等