JS基础面试知识点

152 阅读6分钟

1、JS值类型与引用类型的区别

typeof运算符?

· 识别所有的值类型

let a;             typeof a        // 'underfined'
let str = 'ass';   typeof str      // 'string'
let n = 100;       typeof n        // 'number'
let flag = true    typeof flag     //  'boolean'
const s = Symbol('s')  typeof s    //  'symbol' 

· 识别函数

  typeof console.log('aaa');     // 'function'
  typeof funciton(){};           //  'function'

· 判断是否是引用类型(不可再细分)

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

2、深拷贝

手写一个 JS 深拷贝

· 注意判断值类型和引用类型

· 注意判读是否是数组还是对象

· 递归

function deepClone(obj = {}) {
    // 如果不是对象直接返回
    if (typeof obj !== 'object' || 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;
}

问题: 如何用堆栈解释深拷贝?

3、类型计算

字符串拼接

const a = 100 + 10     //110      
const b = 100 + '10'        // '10010'
改为这种形式就可以计算数值:
const b = 100 + parseInt('10')     // 110
cosnt c = true + '10'     // 'true10'

==

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

如果有 == 来判断的话,以上都为true, == 会先把等号两边的类型转换为一致,然后再进行判断。

除了判断是否为空之外( xxx == null ),其他的一律用 ===, 例如: const obj = {a: '111'} if(obj.a == null) {}

== 判断是否为null, 与 === 同时判断是否为null 和是否为 undefined,效果一样。 例如: if(obj.a == null) {} 相当于 if(obj.a === null || obj.a === undefined) {}

if语句和逻辑运算

truely 变量: !!a === true 的变量; falsely变量: !!b === false 以下是falsely变量,除此之外都是truely变量: !!0 === false; !!'' === false; !!fasle === false; !!NaN === false; !!null === false; !!undefined === false;

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

4、原型和原型链 (重)

JS本身基于原型来继承

class 和继承

class

· constructor

· 属性

· 方法

// 类
class Student {
    constructor(name, number) {
        this.name = name
        this.number = number
        // this.gender = 'male'
    }
    sayHi() {
        console.log(
            `姓名 ${this.name} ,学号 ${this.number}`
        )
    }
}

// 通过类 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()

原型和原型链

class 实际上是函数,可见是语法糖:

tyoepf Person //'function' tyoeof Stydent // 'function'

隐式原型 和 显式原型:

console.log(xialuo.__proto__); console.log(Student.prototype);console.log(xialuo.__proto__ === Student.prototype)

原型

Student
prototype
Student.prototype
sayHai()函数
xailuo
name‘夏洛’
age20
proto

原型关系

每个class都有一个显式原型,每个实例都有一个隐式原型__proto__;

基于原型的执行规则:

获取属性 xialuo.name 或者执行方法 xialuo.sayHai(), 先在自身属性或方法中查找, 如果找不到则自动去 proto 中查找

原型链

image.png

image.png

image.png

类型判断instanceof

instanceof 是基于原型链实现的

面试题

1、如何准确判断一个变量是数组

a instanceof Array

2、class 的原型本质

· 原型和原型链的本质

· 属性和方法的执行规则

3、手写简易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'))

5、作用域和闭包(重)

作用域和自由变量

作用域

· 全局作用域

· 函数作用域

· 块级作用域(es6新增)

image.png

自由变量

· 一个变量在当前作用域没有定义,但被使用了

· 向上级作用域,一层一层依次寻找,知道找到为止

· 如果到全局作用域都没有找到,则报错: xxx is not defined

· 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方

image.png

闭包

· 闭包是作用域应用的特殊情况,有两种表现形式:

· 函数作为参数被传递

· 函数作为返回值被返回

image.png [JS 闭包经典使用场景和含闭包必刷题](# juejin.cn/post/693746…)

this

this 在各个场景中取什么样的值,是在函数执行的时候确定的,不实在定义的时候确定的

1、作为普通函数被调用

image.png

2、使用call(直接调用call方法)、 bind(返回一个新的函数)、apply被调用

image.png

3、作为对象方法被调用

image.png

4、在class方法中被调用

image.png

5、箭头函数

箭头函数取值永远取它上级作用域的值,包括 this

image.png

# 详解箭头函数和普通函数的区别以及箭头函数的注意事项、不适用场景

面试题

1、this的不同应用场景,应该如何取值?

应用场景:

· 作为普通函数被调用

· 使用 call、bind、 apply

· 作为对象方法调用

· 在class方法中调用

· 箭头函数

this的取值

· 全局环境下,this 只想 window

· 一般函数的 this

全局环境下 this 指向 window; node 环境下 this 指向 global;

严格模式下, this 指向 underfined

· 作为对象方法的函数

· 构造器中的 this

2、手写bind函数?

Function.prototype.bind1 = function () {
    // 将参数解析为数组
    const args = Array.prototype.slice.call(arguments)
    // 获取 this (取出数组第一项,数组剩余的就是传递的参数)
    const t = args.shift()
    const self = this // 当前函数
    // 返回一个函数
    return function () {
        // 执行原函数,并返回结果
        return self.apply(t, args)
    }
}

3、实际开发中闭包的应用场景,举例说明?

· 隐藏数据

· 如做一个简单的 catch 工具

// 闭包隐藏数据,只提供 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 标签, 点击弹出序号

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)
    })
    document.body.appendChild(a)
}

6、同步和异步

· 基于 JS 单线程语言

· 异步不会阻塞代码执行

· 同步会阻塞代码执行

单线程和异步

· JS是单线程语言,智能同时做一件事情

· 浏览器和nodejs已经支持 JS 启动进程,如 web socker

· JS 和 Dom渲染共用同一个线程,因为 JS 可修改 DOM 结构

· 遇到等待(网络请求,定时任务),不能卡住

· 需要异步

· 异步表现形式:回调 callback 函数形式

image.png

异步的应用场景

网络请求,如 ajax 图片加载

image.png image.png

定时任务,setTimeout

image.png

callback hell 和 Promise

image.png

Promise解决了 callback hell 嵌套的问题

image.png image.png

面试题

1、同步和异步的区别

同上

2、手写用Promise加载一张图片

function loadImg(src) {
    const p = new Promise(
        (resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`图片加载失败 ${src}`)
                reject(err)
            }
            img.src = src
        }
    )
    return p
}

// const url = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
// loadImg(url).then(img => {
//     console.log(img.width)
//     return img
// }).then(img => {
//     console.log(img.height)
// }).catch(ex => console.error(ex))

const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'

loadImg(url1).then(img1 => {
    console.log(img1.width)
    return img1 // 普通对象
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2) // promise 实例
}).then(img2 => {
    console.log(img2.width)
    return img2
}).then(img2 => {
    console.log(img2.height)
}).catch(ex => console.error(ex))

3、前端使用异步的场景有哪些?

同上

image.png

答案: 1、3、5、4、2

7、JS异步(进阶)

event loop (时间循环 / 事件轮询)

· JS 是单线程运行的

· 异步要基于回调来实现

· event loop 就是异步回调的实现原理

JS 是如何执行的?

· 从前到后,一行一行的执行

· 如果有一行代码执行错误,则停下下面代码的执行

· 先把同步代码执行完,再执行异步的代码

代码例子:

image.png

图示:

image.png

总结 event loop 过程

1、同步代码,一行一行放在 Call Stack 中执行

2、遇到异步,会先‘记录’下,等待时机(定时、网络请求等)

3、时机到了,就移动到 Callback Queue

4、如果 Callstack 为空(即同步代码执行完),Event Loop 开始工作

5、轮询查找 Callback Queue, 如果有则移动到 Call Stack 中

6、然后继续轮询查找(永动机一样)

DOM 事件和 event loop

image.png

· JS 是单线程

· 异步(setTimeout, ajax等)使用回调,基于 event loop

· DOM 事件也使用回调, 基于 event loop

Promise

三种状态

· pending、resolved、rejected

· pending => resolved 或者 pending => rejected

· 变化不可逆

状态的表现和变化

状态的表现

· pedding 状态,不会触发 then 和 catch

· resolved 状态,会触发后续的 then 回调函数

· rejected 状态,会触发后续的 catch 回调函数

then 和 catch 对状态的影响 (常考)

then 正常返回resolved, 里面有报错则返回 rejected

catch 正常返回resolved, 里面有报错则返回 rejected

image.png 结果打印: 1、3

解释: resolved 状态会执行then回调,所以首先会打印出 1 ,只有rejected状态才会执行 catch回调,所以不会打印 2 , 然后第一个then回调执行完后,没有报错还是 resolved 状态, 所以接着执行第二个 then 回调,打印出 3。

image.png

结果打印:1、2、3

解释: 首先执行then回调,打印 1,但是then回调中有报错,peomise状态变为 rejected 状态,rejected 状态会执行 catch 回调,打印 2,catch中没有报错,所以 Promise 是 resolved 状态,接着执行 then 回调,打印 3 。

image.png

结果打印: 1、2

解释: 首先执行 then 回调,打印 1,但是then回调中报错,promise状态变为rejected 状态,然后执行catch回调,打印出 2,catch中没有报错,还是resolved 状态,所以不会执行最后一个 catch回调。

async / await

· 异步回调 callback hell

· Promise then catch 链式调用,但也是基于回调函数

· async / await 是同步语法,彻底消灭回调函数

async/await 和 Promise 的关系

· async / await 是消灭异步回调的终极武器

· 但是和 Promise 并不互斥

· 反而,两者相辅相成

· 执行 async 函数,返回的是 Promise 对象

· await 相当于 Promise 的 then

· await 后面的代码会进入微任务队列

· try...catch可捕获异常,替代了 Promise 的 catch

异步的本质

· async / await 是消灭异步回调的终极武器

· JS 还是单线程,还是有异步,还是基于 event loop

· async / await 只是语法糖,但是真香

image.png

for ... of

· for ... in (以及forEach for)是常规的同步遍历

· for ... of 常用于异步的遍历

宏任务 macroTask 和 微任务 microTask

什么是宏任务? 什么是微任务

· 宏任务: setTimeout、setInterval、ajax 和 DOM 事件

· 微任务: Promise、async/await

· 微任务执行时机要比宏任务要早(先记住)

event loop 和 DOM 渲染

· 再次回顾一遍event loop 的过程

· JS 是单线程的,并且和 DOM 渲染共用一个线程

· JS 执行的时候,需要留一些时机供 DOM 渲染 image.png

· 每次 Call Stack 清空(即每次轮询结束),即同步任务执行完

· 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染

· 然后再去触发下一次 Event Loop

微任务和宏任务的区别

· 宏任务: DOM 渲染后触发,如setTimeout

· 微任务: DOM 渲染后触发,如 Promise

为什么微任务比宏任务的执行时间早?

· 微任务是 ES6 与法规定的

· 宏任务是浏览器规定的

image.png

问题

· 宏任务有哪些? 微任务有哪些? 微任务触发时机更早

· 微任务、宏任务和 DOM 渲染的关系?

· 微任务、宏任务和 DOM 渲染,在event loop中的过程 ?

面试题

描述event loop 的机制(可画图)?

image.png

什么是宏任务、什么是微任务?两者区别?

Promise 的三种状态,以及如何变化?

执行顺序的问题

async function async1 () {
  console.log('async1 start')  // 2
  await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行  
  console.log('async1 end') // 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务)   // 6
}

async function async2 () {
  console.log('async2')  // 3
}

console.log('script start')  // 1

setTimeout(function () { // 异步,宏任务
  console.log('setTimeout')  // 8
}, 0)

async1()

new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码
  console.log('promise1') // Promise 的函数体会立刻执行  // 4
  resolve()
}).then (function () { // 异步,微任务
  console.log('promise2')   // 7
})

console.log('script end')   // 5

// 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序
// 1. async1 函数中 await 后面的内容 —— 微任务
// 2. setTimeout —— 宏任务
// 3. then —— 微任务