前端知识点总结(#2 JS基础)

131 阅读4分钟

三、JS基础

1. 变量类型和计算

  1. 值类型vs引用类型(区别)
let a = 100
let b = a
console.log(b) // 100
let a = {age: 20}
let b = a
b.age = 21
console.log(a.age) // 21
  1. typeof能判断哪些类型?
  • typeof能识别所有的值类型、函数,能判断是否是引用类型(不可再细分)
  1. 何时使用===,何时使用==?
  • 除了==null之外,其它都一律用===
  1. 手写深拷贝
let result;

const obj1 = {
    age: 20,
    name: 'xxx',
    address: {
        city: 'beijing'
    }
}

const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
console.log(obj1.address.city) // beijing 

function deepClone(obj = {}) {
    if(typeof obj !== 'object' || obj ===null) {
        return obj
    }
    if(obj instanceof Array) {
        result = []
    } else {
        result = {}
    }

    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
            // 保证key不是原型的属性
            if(obj.hasOwn) {
                // 递归
                result[key] = deepClone(obj[key])
            }
        }
    }
    return result;
}
  1. 字符串拼接
const b = 100 + '10' // '10010'
const c = true + '10' // 'true10'
  1. ==运算符(除了==null之外,其它都一律用===)
100 == '100' // true
0 == '' // true
0 == false // true
false = '' // true
null == undefined // true

if(obj.a == null) { } // 相当于if(obj.a === null || obj.a === undefined) { }
  1. truly变量和falsely变量(双重否定表肯定)
  • truly变量: !!a === true 的变量(这不是废话嘛,真诚脸)
  • falsely变量:!!a === false 的变量

2. 原型和原型链

  1. 如何判断一个变量是不是数组?
  • xx instanceof Array // true,则xx是数组
  1. 手写一个简易的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
    }
    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)
        })
    }
}

// 应用jQuery
const $p = new jQuery('p')

$p.get(1) //<p>一段文字</p>

考虑插件:

jQuery.prototype.dialog = function(info) {
    alert(info)
}

// 应用
$p.dialog('abc')

复写机制(造轮子):

class myJQuery extends jQuery {
    constructor(selector) {
        super(selector)
    }
    // 扩展自己的方法
    addClass(className) {}
    style(data) {}
}
  1. class的原型本质,怎么理解?
  • 原型和原型链的图示

  1. 原型关系
  • 每个class都有显示原型prototype
  • 每个实例都有隐式__proto__
  • 实例的__proto__指向对应class的prototype
  1. 基于原型的执行规则
  • 想要获取属性xialuo.name或执行方法xialuo.sayhi()时,先在自身属性和方法寻找,如果找不到则自动去__proto__找

3. 作用域和闭包

  1. 作用域
  • 作用域表示某个变量的合法的使用范围
  • 全局作用域
  • 函数作用域
  • 块级作用域(if, for等的大括号里面的区域)
  1. 自由变量
  • 定义:一个变量在当前作用域没有定义,但被使用了
  • 应该向上级作用域,一层一层依次寻找,直到找到为止
  • 如果到全局作用域都没找到,则报错xx is not defined
  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
  1. this的不同应用场景,如何取值?
  • 使用场景:
    • 作为普通函数:this返回window
    • 使用call apply bind:传入什么,this就返回什么
    • 作为对象方法被调用:this返回对象
    • 在class方法中调用:this返回实例本身
    • 箭头函数:this取值取上级作用域的值
  • this取什么样的值是在函数执行的时候确定的,不是在函数定义的时候确定的
function fn1() {
    console.log(this)
}
fn1() // window

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

const fn2 = fn1.bind({ x: 200 })
fn2() // { x: 200 }
  1. 手写bind函数
function fn1(a, b, c) {
    console.log('this',this)
    console.log(a,b,c)
    return 'this is fn1'
}

const fn2 = fn1.bind({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res) 
// 打印结果
// this {x:100}
// 10 20 30 
// this is fn1
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)
    }
}
  1. 实际开发中闭包的实用场景,举例说明。
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')) // 100
  1. 创建10个,点击弹出序号
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)
}

4. 异步

  1. 单线程和异步
  • JS是单线程语言,本质上只能同时做一件事
  • 浏览器和nodejs已支持JS启动进程,如Web Worker
  • JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
  • 遇到等待(网络请求,定时任务)不能卡住,所以需要异步
  • 异步是基于callback回调函数形式
  1. 同步和异步的区别是什么?
  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行
  1. 前端使用异步的场景有哪些?
  • 网络请求,如ajax图片加载
  • 定时任务,如setTimeout
  1. 手写promise,用promise加载一张图片
const url1 = 'xxx'
const url2 = 'xxxx'

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

loadImg(url1).then(img1 => {
    console.log(img1.width)
    return img1
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2)
}).then(img2 => {
    console.log(img2.width)
    return img2
}).then(img2 => {
    console.log(img2.height)
}).catch(ex=> console.error(ex))

5. 异步进阶

  1. 请描述event loop(事件循环/事件轮询)的机制,可画图
  • event loop是异步回调的实现原理和过程
    1. 同步代码,一行一行放在Call Stack执行;遇到异步,会先“记录”下,等待时机(定时、网络请求等);时机到了,就移动到Callback Queue里面
    2. 如果Call Stack为空(即同步代码执行完),Event Loop开始工作;轮询查找Callback Queue,如有则移动到Call Stack执行;然后继续轮询查找(永动机一样)
  1. DOM事件和event loop

  • DOM事件使用回调,是基于event loop来实现的
  1. 什么是宏任务和微任务,两者有什么区别?
  2. Promise有哪三种状态,如何变化
  • pending: 过程中,不会触发then和catch
  • resolved: 成功,会触发后续的then回调函数
  • rejected: 失败,会触发后续的catch回调函数
  • 变化:pending->resolved; pending -> rejected
  • then和catch改变状态
    • then正常返回resolved,里面有报错则返回rejected
    • catch正常返回resolved,里面有报错则返回rejected
  1. then和catch的链式调用(promise 面试题示例)

第一题

Promise.resolve().then(()=>{ // resolved,不进.catch
    console.log(1)
}).catch(()=>{ // 不执行
    console.log(2)
}).then(()=>{
    console.log(3)
})

// 结果返回1 3

第二题

Promise.resolve().then(()=>{ 
    console.log(1)
    throw new Error('error1') // rejected,进.catch
}).catch(()=>{ // resolved,进.then
    console.log(2)
}).then(()=>{ // resolved
    console.log(3)
})

// 结果返回1 2 3

第三题

Promise.resolve().then(()=>{
    console.log(1)
    throw new Error('error1') // rejected,进.catch
}).catch(()=>{ 
    console.log(2) // resolved,不进.catch
}).catch(()=>{
    console.log(3)
})

// 结果返回1 2
  1. async/await语法
  • 背景:异步 回调 callback hell
  • Promise then catch 的链式调用,可以解决异步问题,但也是基于回调函数
  • async/await是同步语法,彻底代替(消灭)回调函数,但和Promise并不互斥
  1. async/await和Promise的关系
  • 执行async函数,返回的是Promise对象
  • await相当于Promise的then
  • try...catch可捕获异常,代替了Promise的catch
  • 异步的本质
  1. for...of
  • for...in(以及forEach,for)是常规的同步遍历
  • for...of常用于异步的遍历
  1. 宏任务macroTask和微任务microTask
  • 宏任务:setTimeout, setInterval, Ajax, DOM事件
  • 微任务:Promise, async/await
  • 微任务执行时机比宏任务要早(因为宏任务是在DOM渲染后触发,微任务是在DOM渲染前触发)
  • 微任务是ES6语法规定的,宏任务是浏览器规定的
  • 在Call Stack清空后,先执行当前的微任务,然后尝试DOM渲染, 再触发Event Loop,执行宏任务
  1. event loop 和 DOM 渲染
  • 每次Call Stack清空(即每次轮询结束),即同步任务执行完
  • 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
  • 然后再去触发下一次Event Loop