前端常考面试题二:js基础篇

一、JS基础

变量类型和计算

常考题

1.值类型和引用类型区别

值类型
在这里插入图片描述
引用类型
在这里插入图片描述
在这里插入图片描述
题目在这里插入图片描述

2.typeof能判断哪些类型
  • 识别所有值类型:number,boolean,string,function(函数),object(NULL,数组,对象),undefined
  • 识别函数(function)
  • 判断是否是引用类型(object 不可再细分)
3.手写深拷贝
  • 注意判断值类型和引用类型
  • 注意判断是数组还是对象
  • 递归
 /**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */
function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        return obj
    }
    // 初始返回结果
    let result
    if (Array.isArray(obj)) {
        result = []
    } else {
        result = {}
    }

    // 遍历对象或数组(一个个拷贝值)
    for (let key in obj) {
     	// 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {       
            // 递归调用!!!
            result[key] = deepClone(obj[key]) 
        }
    }
    return result
 }

结果验证

 const obj1 = {
    age: 20,
    name: 'xxx',
    address: {
        city: 'beijing'
    },
    arr: ['a', 'b', 'c']
}

const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
console.log(obj1.address.city) // 'beijing'
console.log(obj2.address.city) // 'shanghai'
4. 何时使用 === 何时使用 ==

在这里插入图片描述在这里插入图片描述

5. truly和falsely变量

truly变量: !!a === true的变量
falsely变量: !!a === false的变量
在这里插入图片描述

  • 注意: !![]=true !!{}=true
  • 拓展:如何判断对象或数组是否为空呢?
    • 数组:判断arr.length == 0
    • 对象:Object.keys(obj) – 返回对象中属性名组成的数组,再判断这个数组的长度
      var data = {};
      var arr = Object.keys(data);
      console.log(arr.length == 0); //true 为空, false 不为空
      

原型和原型链

知识点补充

1.继承(extends、super、扩展或重写方法)
2.类型判断- instanceof

在这里插入图片描述

3.原型

在这里插入图片描述
原型关系

  • 每个class都有显示原型prototype
  • 每个实例都有隐式原型_proto__
  • 实例的__proto__指向对应class的prototype

基于原型的执行规则

  • 获取属性xialuo.name或执行方法xialuo.sayhi()时
  • 先在自身属性和方法寻找
  • 如果找不到则自动去__proto__中查找
4.原型链

在这里插入图片描述

常考题

1.如何准确判断一个变量是不是数组
  • a instanceof Array
  • Array.isArray(a)
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'))

作用域和闭包

知识点补充

1.作用域和自由变量

作用域
在这里插入图片描述

  • 全局作用域
  • 函数作用域
  • 块级作用域(if、for、while…)

自由变量

  • 一个变量在当前作用域没有定义,但被使用了
  • 向上级作用域,一层一层依次寻找,直至找到为止
  • 如果到全局作用域都没找到,则报错xx is not defined
2.闭包

闭包就是能够读取其他函数内部变量的函数,是将函数内部和函数外部连接起来的一座桥梁。
2.1、作用域应用的特殊情况,有两种表现∶

  • 函数作为返回值被返回
// 函数作为返回值
function create() {
    const a = 100
    return function () {
        console.log(a); // 在定义的作用域中向上级寻找
    }
}

const fn = create()
const a = 200
fn() // 100
  • 函数作为参数被传递
// 函数作为参数
function print(fn) {
    const a = 200
    fn()
}
const a = 100
function fn() {
    console.log(a); // 在定义的作用域中向上级寻找
}

print(fn) //100
  • 结论
    • 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
    • 不是在执行的地方!!

2.2、优缺点
闭包的特性
①封闭性:外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外 界提供访问接口;(隐藏变量,避免全局污染; 可以读取其它函数内部的变量)
②持久性:一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调 用之后,闭包结构依然保存在;

负面影响:
使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等。(上级作用域内变量的生命周期,因为在下级作用域内被引用,而没有被释放。)

内存泄漏:是指程序中已动态分配的堆内存,由于某种原因,程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

闭包是一个非常强大的特性,但是人们对其也有诸多误解。一种耸人听闻的说法是闭包会造成内存泄漏,所以要尽量减少闭包的作用。
局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去。
从这个意义上看,闭包的确会使一些数据无法被及时销毁,使用闭包的一部分原因室我们选择主动把一些变量封闭在闭包中,因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,这里并不能说成是内存泄漏。
如果在将来需要回收这些变量,我们可以手动把这些变量设为null,将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。

2.3、例子
需求:实现变量a 自增

1、通过全局变量,可以实现,但会污染其他程序

var a = 10;
function Add(){
    a++;
    console.log(a);
}
Add();
Add();
Add();

2、定义一个局部变量,不污染全局,但是实现不了递增

var a = 10;
function Add2(){
    var a = 10;
    a++;
    console.log(a);
}
Add2();
Add2();
Add2();
console.log(a);

3、通过闭包,可以使函数内部局部变量递增,不会影响全部变量,完美!!

var a  = 10;
function Add3(){
    var a = 10;
    return function(){
        a++;
        return a;
    };
};
var cc =  Add3();
console.log(cc());
console.log(cc());
console.log(cc());
console.log(a);

常考题

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

this取什么值,是在函数执行的时候确认,不是在函数定义的时候确认

  • 作为普通函数—指向window
// 普通函数
function fn1() {
    console.log(this);
}
fn1() // window
  • 作为对象方法被调用 – 指向当前对象
  • setTimeout(function (){})-- 指向window
    setTimeout(()=>{})-- 取它上级作用域的this
// 作为对象方法被调用
const zhangsan = {
    name: '张三',
    sayHi() {
        // this指向当前对象
        // {name: "张三", sayHi: ƒ, wait: ƒ}
        console.log(this);
    },
    wait() {
        setTimeout(function () {
            // this指向window
            console.log(this);
        })
    }
}

  • 在class方法中调用 – 指向它的实例对象
class People {
    constructor(name) {
        this.name = name
        this.age = 20
    }
    sayHi() {
        console.log(this);
    }
}
const zhangsan = new People('张三')
zhangsan.sayHi() // this指向zhnagsan对象
  • 箭头函数 – 永远是取它上级作用域的this
// 箭头函数
const zhangsan = {
    name: '张三',
    sayHi() {
        // this指向当前对象
        // {name: "张三", sayHi: ƒ, wait: ƒ}
        console.log(this);
    },
    wait() {
        setTimeout(() => {
            // this指向当前对象
            // {name: "张三", sayHi: ƒ, wait: ƒ}
            console.log(this);
        })
    }
}
  • 使用call apply bind(改变this指向)
// 普通函数
function fn1() {
    console.log(this);
}
fn1() // window

// 使用call
fn1.call({ x: 100 }) // {x: 100}

// 使用apply
fn1.apply({ x: 100 }) // {x: 100}

// 使用bind
const fn2 = fn1.bind({ x: 200 })
fn2() // {x: 200}

call 、apply、bind 这三个函数的第一个参数是 this 的指向对象,后面的参数都为传递给原函数的参数

原函数是否需要传参\调用方法callapplybind总结
无参数function fn1() {}fn1.call({ x: 100 })fn1.apply({ x: 100 })fn1.bind ({ x: 100 })()bind 返回的是一个新的函数,必须调用,它才会被执行
有参数function fn1(a,b) {}fn1.call({ x: 100 },‘成都’,‘上海’)fn1.apply({ x: 100 },[‘成都’,‘上海’])fn1.bind ({ x: 100 },‘成都’,‘上海’)()apply 的所有参数都必须放在一个数组里面传进去
2.创建10个a标签,点击的时候弹出对应的序号

问题:点击每个a标签,弹出来的都是10
原因:i是全局作用域

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

解决方式:把i放到块级作用域

        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)
        }
3.手写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)
    }
}

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);
4.实际开发中闭包的应用场景,举例说明
  • 隐藏数据
    如做一个简单的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') )

异步

知识点补充

1.单线程和异步

异步产生

  • JS是单线程语言
    异步的设计主要是因为js是一个单线程的语言,单线程只允许同时做一件事情,如果同时需要做多个,那其他的需要去旁边排队去
  • 浏览器和nodejs已支持JS启动进程,如Web Worker
  • JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
  • 遇到等待(网络请求,定时任务)不能卡住

异步特点

  • 回调callback函数形式
  • 基于JS是单线程语言
  • 异步不会阻塞代码执行(callback)
  • 同步会阻塞代码执行
2.异步应用场景
  • 网络请求,如ajax图片加载
  • 定时任务,如setTimeout
3.Promise解决callback hell

嵌套
在这里插入图片描述
链式
在这里插入图片描述

常考题

1.同步和异步的区别是什么?
  • 基于JS是单线程语言
  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行
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 url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'

loadImg(url1).then(img1 => { // 接收resolve传递的参数
    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.前端异步的使用场景有哪些?
  • 网络请求,如ajax图片加载
  • 定时任务,如setTimeout
4.setTimeout笔试题

在这里插入图片描述
答案:1 3 5 4 2

二、JS异步进阶

知识补充

1.event loop(事件循环/事件轮询)

①JS是单线程运行的,异步要基于回调来实现,event loop就是异步回调的实现原理
②JS如何执行

  • 从前到后,一行一行执行
  • 如果某一行执行报错,则停止下面代码的执行
  • 先把同步代码执行完,再执行异步

③event loop过程
在这里插入图片描述
在这里插入图片描述

  • 同步代码,一行一行放在Call Stack(调用栈) 执行
  • 遇到异步,会先放在一边,等待时机(定时、网络请求等)
  • 时机到了,就移动到Callback Queue(回调函数队列)
  • 当Call Stack为空(即同步代码执行完),开始启动Event Loop(事件轮询)机制
  • 轮询查找Callback Queue中有没有函数 ,如有则移动到Call Stack 执行
  • 然后继续轮询查找(永动机一样)

由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环( event loop)。

2.promise进阶

三种状态

  • pending resolved rejected
  • pending —> resolved(成功) 或 pending —> rejected(失败)
  • 变化不可逆

状态的表现和变化

  • pending 状态,不会触发then和catch
  • resolved状态,会触发后续的then回调函数
  • rejected 状态,会触发后续的catch回调函数

then和catch 对状态的影响

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

3.async/await

背景

  • 异步回调callback hell
  • Promise then catch 链式调用,但也是基于回调函数
  • async/await是同步语法,彻底消灭回调函数

async/await和 Promise的关系!!!

  • async/await是消灭异步回调的终极武器,但和Promise并不互斥,反而,两者相辅相成
  • 执行async函数,返回的是 Promise对象
  • await 相当于Promise的then
  • try…catch 可捕获异常,代替了Promise的catch

异步的本质

  • await 是同步写法,但本质还是异步调用
  • JS还是单线程,还得是有异步,还得是基于event loop
  • async/await只是一个语法糖
async function async1 () {
  console.log('async1 start') // 2
  await async2() 
    
  // await的后面,都可以看做是 callback 里的内容,即异步
  console.log('async1 end') // 5
  await async3()
  
  // 下面一行是异步回调的内容
  console.log('async1 end2') // 7
}

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

async function async3 () {
  console.log('async3') // 6
}

console.log('script start') // 1
async1()
console.log('script end') // 4
// 同步代码执行完 event loop

for … of

  • for … in(以及forEach for )是常规的同步遍历
  • for … of 常用于异步的遍历
// 定时算乘法
function multi(num) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000)
    })
}

// // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
function test1 () {
    const nums = [1, 2, 3];
    nums.forEach(async x => {
        const res = await multi(x);
        console.log(res);
    })
}
test1();

// 使用 for...of ,可以让计算挨个串行执行
async function test2 () {
    const nums = [1, 2, 3];
    for (let x of nums) {
        // 在 for...of 循环体的内部,遇到 await 会挨个串行计算
        const res = await multi(x)
        console.log(res)
    }
}
test2()

4.宏任务macroTask / 微任务microTask

什么是宏任务,什么是微任务

  • 宏任务: setTimeout , setInterval , Ajax ,DOM事件
  • 微任务:Promise async/await
  • 微任务执行时机比宏任务要早

event loop和 DOM渲染

  • JS是单线程的,而且和DOM渲染共用一个线程
  • JS执行的时候,得留一些时机供DOM渲染
  • DOM渲染时机
    • 每次Call Stack清空(即同步任务执行完/每次轮询结束)
    • 尝试DOM渲染,DOM结构如有改变则重新渲染
    • 然后再去触发下一次Event Loop

微任务和宏任务的区别

  • 微任务:DOM渲染前触发,如Promise

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

  • 微任务是ES6语法规定的,宏任务是由浏览器规定的

  • 微任务放 micro task queue在这里插入图片描述

  • 执行顺序在这里插入图片描述

常考题

1.描述event loop机制(可画图)

三方面

  • event loop 的基础过程
  • 和DOM渲染的关系
  • 微任务和宏任务在event loop过程中的不同处理

2.什么是宏任务和微任务,两者区别

  • 宏任务: setTimeout , setInterval , Ajax,DOM事件
  • 微任务: Promise , async/await
  • 区别:微任务执行时机比宏任务要早

3.Promise的三种状态,如何变化

  • pending resolved rejected
  • pending —> resolved 或 pending -> rejected
  • resolved会触发then后面的回调,rejected会触发catch后面的回调
  • 变化不可逆

4.场景题- promise then和catch的连接

// 第一题
Promise.resolve().then(() => {
    console.log(1)
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})
// 输出:1 3

// 第二题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1)
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2)
}).then(() => {
    console.log(3)
})
// 输出:1 2 3

// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1)
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2)
}).catch(() => {
    console.log(3)
})
// 输出:1 2 

5.场景题- async/await语法

题1

// 执行完毕,会打印出哪些内容
async function fn() {
    return 100
}

(async function () {
    const a = fn()
    console.log('a', a)
    const b = await fn()
    console.log('b', b)
})()

打印结果:
在这里插入图片描述

题2

// 执行完毕,会打印出哪些内容
(async function () {
    console.log('start')
    const a = await 100
    console.log('a', a)
    const b = await Promise.resolve(200)
    console.log('b', b)
    const c = await Promise.reject(300)
    console.log('c', c)
    console.log('end')
})()

打印结果:
在这里插入图片描述

6.场景题- promise和 setTimeout的顺序

console.log(100)
setTimeout(() => {
    console.log(200)
})
Promise.resolve().then(() => {
    console.log(300)
})
console.log(400)

打印结果:100 400 300 200

7.场景题–外加async/await的顺序问题

async function async1() {
    console.log('async1 start') // 2
    await async2()

    // await后面的都作为回调内容 —— 微任务
    console.log('async1 end') // 6
}

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

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

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

async1()

// 初始化 Promise 时,传入的函数会立刻被执行
new Promise(function (resolve) {
    console.log('promise1') // 4
    resolve()
}).then(function () { // 微任务
    console.log('promise2') // 7
})

console.log('script end') // 5
// 同步代码执行完毕(event loop - call stack 被清空)
// 执行微任务
// 尝试触发 DOM 渲染
// 触发 Event Loop,执行宏任务

打印结果:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值