一些面试题

138 阅读6分钟

javascript

为什么typeof null是object?

最初的js是运行在32位机器上,为了性能方面考虑选择了低位存储变量的信息,000开头就表示是对象,而null表示全是零

如果判断对象内某个key是自身创建而非继承获得

const c = {a:123}
c.hasOwnProperty('a')
c.hasOwnProperty('toString')

obj.child = obj = {num: 2} 输出什么

var obj = {num: 1}
obj.child = obj = { num: 2 }
console.log(obj.child)

image.png

['1', '2', '3'].map(parseInt)输出什么

[1, NaN, NaN],

[].map(function(val, index, arr){})

上述代码会经历以下阶段

  • parseInt('1', 0, arr)
  • parseInt('2', 1, arr)
  • parseInt('3', 2, arr)

parseInt接收两个参数,一个字符串,一个radix,

  1. radix为0/null/undefined时候,默认输出1,
  2. radix为1时候,因为radix只接收2-36的数值,导致无效,返回NaN
  3. radix为2时候,只会生成二进制0/2,所以仍会返回NaN

关于radix,

如果parseInt第一个参数是0开头的,radix会被默认设置成8进制或者10进制,具体根据浏览器决定,在ESMAScript5中 被确定,应该使用10进制

跨窗口交互方案有哪些

juejin.cn/post/707746…

以下代码中a,b,c会输出什么

function A() {
  this.a = 'a';
}
A.prototype.say = () => {
  return 'b'
}
const a = new A();
function B() {
  return 'b';
}
B.prototype.say = () => {
  return 'b'
}
const b = new B();
function C() {
  return {
    name: 'c'
  };
}
C.prototype.say = () => {
  return 'c'
}
const c = new C();

实现结构对象const [a, b] = {a:1,b:2}

根据可迭代协议,为对象设置迭代函数

Object.prototype[Symbol.iterator] = function*() {
    return yield *Object.values(this)
}
Object.prototype[Symbol.iterator] = function() {
    return Object.values(this)[Symbol.iterator]()
}
Object.prototype[Symbol.iterator] = function*() {
    for(const key in this) {
        yield this[key]
    }
}
Object.prototype[Symbol.iterator] = function() {
    const values = Object.values(this);
    return {
        next() {
            const val = values.shift();
            return {
                value: val,
                done: !val
            }
        }
    }
}
const obj = {
    a: 1,
    b: 2,
}

const [a, b] = obj;
console.log(a /* a = 1 */,b, /* b = 2*/);

for(const val of obj) {
    console.log('----', val);
}

typeof和instanceof的区别

typeof和instanceof都可以用来判断数据的类型

  1. typeof用来判断数据的基本数据类型
  2. instanceof用来判断数据的引用类型

实现instanceof

class C1 extends Array {}

const _c1 = new C1();


function instance_of(current, Status) {
    let res = current.__proto__;
    const StatusProt = Status.prototype;
    while (res) {
        if (res === StatusProt) return true;
        res = res.__proto__;
    }
    return false
}


console.log(instance_of(_c1, Array));

类数组转换

const list = document.getElementsByTagName('div');

// 利用原型链
Array.prototype.slice.call(list)
// es6方法
Array.from(list)
// 解构
[...list]

为什么有时候发送post请求时候会预先发送一个option请求,这个请求是干什么的

简单描述:预请求 面试描述:下文解析 解析[juejin.cn/post/726995…]

最简方式实现代码深拷贝,Object.assign是深拷贝还是浅拷贝?

const data = {
    a: 1,
    b: 2
}

const cloneEs6 = (value) => {
    return new Promise(resolve => {
        const message = new MessageChannel();
        message.port1.onmessage = e => {
            resolve(e.data);
        }
        message.port2.postMessage(value)
    })
}

const d2 = await cloneEs6(data);

手动实现call/apply/bind

const apply = (fn, $this, args) => {
    const a = Object.assign({}, $this, { fn });
    return a.fn(...args);
}
const call = (fn, $this, ...args) => {
    const a = Object.assign({}, $this, { fn });
    return a.fn(...args);
}
const bind = (fn, $this) => {
    const a = Object.assign({}, $this, { fn });
    return (...args) => a.fn(...args);
}
const getSchool = function(a, b) {
    return `${this.name}_${a},_${b}`;
}

const a1 = apply(getSchool, { name: 'tomg'}, ['a1', 'b1']);
const a2 = call(getSchool, { name: 'tomg'}, 'a2', 'b2');
const a3 = bind(getSchool, { name: 'tomg'});

script标签defer和async的区别

  • 设置defer,脚本异步加载,加载完之后,在$(body).ready/DOMContentLoaded事件触发之前执行
  • 设置async,脚本异步加载,加载完立即解析,会阻碍dom渲染

Promise输出结果

 console.log('1');
    function promiseFn() {
      return new Promise((resolve, reject) => {
        setTimeout(()=> {
          console.log('2');
        })
        resolve('3');
        console.log('4')
      })
    }
    
    promiseFn().then(res => {
      console.log(res);
    });

答案:1 4 3 2

一维数组转三维数组

[1,2,3,4,5,6,7,8,9] => [[1,2,3], [4,5,6], [7,8,9]]

答案

const convert = (list, length, newList = []) => {
    let idx = 0;
    while (idx < list.length) {
        newList.push(list.slice(idx,idx + length))
        idx += length;
    }
    return newList;
}
console.log(convert([1,2,3,4,5,6,7,8,9], 3))

实现a == 1 && a == 2 && a == 3

// 实现1
const a = {
    _a: 1,
    valueOf() {
        return this._a++;
    },
}
// 实现2
const a = {
    _a: 1,
    toString() {
        return this._a++;
    },
}

console.log(a == 1 && a == 2 && a == 3);
// 实现3
const a = {
    _a: 1,
    [Symbol.toPrimitive]() {
        return this._a++;
    },
}

console.log(a == 1 && a == 2 && a == 3);

假设一个函数返回一个对象,在不直接修改返回对象的情况下,怎么修改返回对象里的值

function getInfo() {

    return {
        name: '张三',
        age: 14,
    }
}

const info = getInfo();

/**
 * 禁止使用info.name = '李四'
 */
// 代码------start

// 代码------end


console.log(info.name === '李四')

答案

Object.prototype.__update = function() {
    this.name = '李四'
}
info.__update();
delete Object.prototype.__update;

针对上述问题,如何实现一个安全的沙盒系统(web平台)

要求,这个禁止修改主环境内全局对象,防止出现上述问题

document.abc = '123123'
const code = `
    const name = 'tom';
    Object.prototype.name = 12323;
    console.log(123, document.abc, Object.prototype.name)
`
run(code);
console.log(Object.prototype.name)

答案

class createSandbox {
    constructor() {
        this.init();
        this.proxy();
    }
    $iframe;
    $context;
    init() {
        this.$iframe = document.createElement('iframe');
        document.body.appendChild(this.$iframe)
    }
    code(code) {
        const Fn = new Function(`window`, `with(window) {
            ${code}
        }`)
        Fn(this.$context)
    }
    blacklist = ['Math'];
    proxy() {
        const that = this;
        this.$context = new Proxy(this.$iframe.contentWindow, {
            set(target, key, value) {
                return Reflect.set(targeet, key, value)
            },
            get(target, key) {
                if (that.blacklist.includes(key)) {
                    return undefined
                }
                return Reflect.get(target, key);
            }
        })
    }
}

document.abc = '123123'
const sanbox = new createSandbox();
sanbox.code(`
    const name = 'tom';
    Object.prototype.name = 12323;
    console.log(123, document.abc, Math, Object.prototype.name)
`)

Promise都有哪些方法,并且都是什么作用

  • Promise.all
    • 只要有一个reject就会走catch
  • Promise.allSettled
    • 无论resolve还是reject,都返回给then
  • Promise.race
    • 如果有一个resolve或者reject了,就传给then/catch
  • Promise.any
    • 只要有一个resolve了,就传给then
  • Promise.resolve
    • 创建一个resolve promise
  • Promise.reject
    • 创建一个reject promise

React

useCallback的作用

可以用来缓存函数

如果子组件接收一个函数,并且子组件是用memo包裹的情况下,可以优化减小组件的渲染次数

子组件里可以用useEffect来监听 useCallback包裹的函数发生变化

react captrue value的特性原理

segmentfault.com/a/119000001…

下面的代码有问题有问题吗,点击三次按钮,页面num显示是多少

import { useEffect, useRef } from 'react';
import { flushSync } from 'react-dom';

const Zhuce = () => {
    const $el = useRef(null as any as HTMLDivElement);
    const [num, setNum] = useState(0);

    useEffect(() => {
        var div = document.createElement('button');
        div.onclick = () => {
            setNum(num+1);
        }
        div.textContent = 'aaaa';
        $el.current.appendChild(div)
    }, [])

    return <div ref={$el}>
        {num}
    </div>

}


export default Zhuce;

上述问题是什么原因导致?

...

如何解决上述问题?

const add = useRef(() => setNum(num+1));
add.current = useMemo(() => () => setNum(num+1), [num])

useEffect(() => {
    var div = document.createElement('button');
    div.onclick = () => {
        // setNum(num+1);
        add.current();
    }
    div.textContent = 'aaaa';
    $el.current.appendChild(div)
}, [])

优化

如何提高h5首屏打开速度

  • 网络层次
    • http2协议访问
    • headers文件缓存
    • 域名分片
    • cdn
    • 减少不必要301/302
  • 代码层次
    • 懒加载(路由页面懒加载/页面内部模块懒加载)
    • 非必要自定义字体
    • script资源async/defer
    • localStorage缓存必要数据,等接口返回刷新数据
    • 与主功能无关元素延迟加载,例如广告/埋点等
    • 大量数据渲染,采用分段渲染防止页面卡住
    • 避免img src空,会浪费额外请求
    • 骨架屏
    • 预取回(prefetch)/预加载(preload)/预连接(preconnect)/dns预解析(dns-prefetch)
  • 静态文件
    • 图片雪碧图/base64
    • 优先webp格式图片

其他

什么是cdn

构建在现有基础网络之上的虚拟智能网络,依托于各地的边缘服务器,通过中心服务器的调度负载均衡等功能,让用户可以就近访问所需内容,降低网络阻塞,提供访问速度。

cdn首次请求返还的是一个cname而非id,通过cname转到实际请求的ip地址

当用到一个需要实时数据更新的图表页面时候如何进行数据获取

  1. 长链接 (兼容性最好,但是需要不断的请求)
  2. websocket (创建一个长链接与服务器实时沟通,使用了新的协议)
  3. sse (不考虑客户端向服务端推送情况下,当向数据接收,基于的http协议,兼容性好)

关于http缓存

http请求头中,cache-control都有哪些常用值?

  1. max-age 强缓存,设置最大有效期,单位秒
  2. public 客户端以及代理服务器都可以被缓存
  3. private 客户端可以被缓存
  4. no-cache 走协商缓存
  5. no-store 禁止任何缓存

如何设置协商缓存?

  1. last-modified,服务端返回的资源最后修改时间,当下次请求时候,http请求头里会带上这个字段传给服务端(if-Modified-Since),服务端会根据此字段进行对比是否需要进行资源更新
  2. etag,服务端生成的资源唯一表示,服务端会根据此字段进行比较,如果不同则更新资源

为什么有了last-modified,还会有etag?

一个文件如果在里面添加了一些内容,之后有删除了这些内容,这个文件本质没有发生任何变化,但是它的修改时间变了,如果紧依赖于last-modified,就会触发文件更新流程,

max-age(强缓存) > expires(强缓存) > etag(协商缓存) > last-modified(协商缓存)