0121面试题——模板字符串、原型链、promise、for...in

160 阅读4分钟

javascript

场景题

  • 模板字符串处理
    • 如果对应的键值不存在则不处理
    • |后面跟着的是filter函数,均在filters中存在
    • 如果结果是对象则对接过进行 JSON.stringify()
    • 如果结果是函数则进行 toString()
var str = `
a
{{obj.a | filter | filter2}
b
{obj.b.c}
c
{obj.c.d}
`
var obj = {
    a:function(){},
    b:{c:{e:123}},
    c:{}
}
var g = {
    filter(str) {return 'aaa' + str },
    filter2(str) {return str + 'bbb'}
}

// 实现
function parseTemplate(str,obj,g){
    // 将输入的字符串按行切割成流
    let strList = str.split('\n')
    // 保存转换完成的结果
    let result = []
    
    // 寻找花括号对,返回一个二维数组,每个一维数组包含花括号左右的索引
    function findBrackets (s) {
        let result = []
        let item = []
        for (let i=0; i<s.length; i++) {
            if (s[i] === '{') {
                item[0] = i
            } else if (s[i] === '}') {
                item[1] = i
            }

            if (item[0]!==undefined && item[1]!==undefined && item[0] < item[1]) {
                result.push(item)
                item = []
            }
        }
        return result
    }
    // 对模版字符串进行处理
    function cope(s, obj, g) {
    	// 按 | 分割,并去除两头的空白字符
        let item = s.split('|').map((d)=>d.trim())
        // 取出对象的属性链
        let properties = item[0].split('.').slice(1)
        // 取出filters函数链
        let filters = item.slice(1)
        
        // 随属性链依次递进得到属性值
        while (properties.length) {
            let key = properties.shift()
            obj = obj[key]
            // 如果不存在,则不处理
            if (!obj) {
                return "{" + item[0] + "}"
            }
        }
        // 转换为字符串
        if (Object.prototype.toString.call(obj) === "[object Object]") {
            obj = JSON.stringify(obj)
        } else if (typeof obj === 'function') {
            obj = obj.toString()
        }
        // 经过filters函数链处理返回最终结果
        return filters.reduce((pre, cur) => {
            return g[cur](pre)
        }, obj)
    }
    // 处理每一条输入流
    while (strList.length) {
        let line = strList.shift()
        let brackets = findBrackets(line)
        if (!brackets.length) {
            result.push(line)
        } else {
            let temp = ''
            let start = 0
            // 对每一对花括号中的内容进行转换
            while (brackets.length) {
                let bracket = brackets.shift()
                temp += line.substring(start, bracket[0])
                let s = line.substring(bracket[0]+1, bracket[1])
                temp += cope(s, obj, g)
                start = bracket[1] + 1
            }
            temp += line.substring(start)
            result.push(temp)
        }
    }
    return result.join("\n")
}
parseTemplate(str,obj,g)

替换后的结果为

// a
// {aaafunction () { }bbb
// b
// {"e":123}
// c
// {obj.c.d}

理论

  1. 什么是原型,什么是原型链
  • 原型
    • 每个函数(除了箭头函数)在定义后会自动生成一个伴生对象prototype
    • 以这个函数为构造函数生成的实例,会在prototype上寻找属性跟方法
  • 原型链
    • 原型是一个对象,拥有__proto__隐藏属性,指向其构造函数的原型,这样依次链接,直到最顶层的Object的原型为止的整个线性结构
  1. 剔除数组首元素的方法有哪些
x = [1, 2, 3]

x.shift()
x.splice(0, 1)
x = x.slice(1)
[, ...x] = x

  1. promise的状态有哪些
/**
* 待定	pending
* 完成	fulfilled
* 拒绝	rejected
* 同一时间只能存在一种状态,且状态一旦改变就不能再变
**/
  1. promise有什么特点
    • 内部需要使用resolve或者reject函数来改变promise的状态
    • 拥有两个常用方法then跟catch,当结果成功返回执行then回调,否则执行catch回调
    • 回调函数并不需要在状态改变前就绑定,状态变化之后绑定的回调也能得到执行
    • then、catch方法的返回值是promise对象(不是则包装成promise对象)
    • 可能对then、catch进行链式调用,将回调地域的深度写法转换为长度写法
  2. 什么是回调地域
    • 在某些场景下,异步任务必须按次序执行,例如AJAX请求,这样就必须将一个请求写在另一个请求成功的回调中,如果请求比较多,那么回调就会嵌套的越来越深,对代码阅读和维护都会造成影响
  3. for in 遍历数组有哪些问题
    • 遍历的是数组的索引,而不是值
    • 遍历到的索引是string类型
    • 会遍历到给数组添加的属性的键
    • 会遍历到原型链上添加的属性
    • 因此最好用传统的for方式来遍历数组
  4. 观看示例,输出结果是什么,并阐明理由
    • 例1
    const promise = new Promise((resolve, reject) => {
        console.log(1)
        resolve()
        console.log(2)
    })
    promise.then(() => {
        console.log(3)
    })
    console.log(4)
    // 1,2,4,3
    // new Promise回调立即执行,打印1,遇到resolve改变promise状态,但并不让出线程,继续向下执行输出2,
    // 第二句,给promise绑定then回调,第三句,打印4,同步代码执行完毕,再执行回调打印3
    
    • 例2
    function fn(){
        for (let i = 0; i < 4; i++) {
            setTimeout(function(){
                console.log(i)
            },1000)
        }
    }
    fn()
    // 0, 1, 2, 3
    // 使用 let 声明的变量拥有块级作用域,定时器回调引用变量i使得生存周期延长,并且每个回调引用的i不是同一个变量
    
    • 例3
    let a = 0
    let b = async () => {
        a = a + await 10
        console.log('2', a)
    }
    b()
    a++
    console.log('1', a)
    // 1, 1
    // 2, 10
    // 执行b时,执行到a = a + await 10,让出线程,表达式执行顺序从左到右,因此在让出线程之前a的值已经确定,即0
    // b 让出后,a++,并打印出,1,1。回到await处继续执行,0 + 10 = 0,再打印2, 10
    // 如果新的表达式的顺序(await 10) + a,则打印2, 11