面试自备

113 阅读6分钟

1、常见操作数组的方法

*    * push - 向数组末尾添加元素,返回新增后的数组长度
    * unshift - 向数组前方添加元素,返回新数组的长度
    * splice - 接收三个参数,分别为起始位置,替换元素的数量,替换后的元素,返回被替换的元素组成的数组
    * concat - 创建一个数组的副本,接受新的元素参数,与副本数组组成新的数组,不会改变原数组
*    * shift - 删除数组第一位的元素,返回被删除的项
    * pop - 删除数组的最后一位元素,返回被删除的项
    * splice - 接收两个参数,分别为起始位置,删除元素的数量,返回删除元素组成的数组
    * slice - 接收两个参数,起始位置,结束位置(不包含结束位置元素),返回截取元素组成的数组,不会改变原数组
*    * splice
*    * indexOf - 查找元素位于数组的位置,未查到返回 -1
    * includes - 查询元素是否在数组中,返回true或者false
    * find - 接收一个查找函数,返回第一个符合函数需求的元素
* 排序方法
    * reverse - 数组反转
    * sort - 接收一个比较函数,用于判断哪个数值排在前面,比较函数接收两个参数,根据两个参数比较的结果,也就是返回的 -1或者1来判断排序是升序还是降序,前者大于后者,返回-1,这时候是降序;前者大于后者,返回1,这时候是升序
* 转换方法
    * join - 接收一个分隔符,使用此分隔符将数组元素连接成为一个字符串
* 迭代方法
    * filter - 接收一个删选条件的函数,返回一个符合条件的元素组成的数组
    * some - 判断数组内部是否存在符合条件的元素,有则为true,否则为false
    * every - 判断数组每一项是不是符合条件,都符合为true,否则为false
    * forEach - 对数组的每一项运行传入的函数,没有返回值
    * map - 对数组的每一项运行传入的函数,返回每次调用结果构成的一个新数组

2、如何区分数组和对象

* 通过Array.isArray()
    * {} instanceof Array // false
* 通过instanceof
    * [] instanceof Array // true
* 通过constructor
    * [].constructor // [Function: Array]
    * {}.constructor // [Function: Object]
* 通过Object.prototype.toString.call()
    * Object.prototype.toString.call([]) //[object Array]
    * Object.prototype.toString.call({}) //[object Object]

3、js异步编程 - 因为是单线程,需要通过异步来提高效率,但是本质还是使用回调处理异步任务

* 回调函数 - 容易形成回调地狱
* promise - 书写繁复
* generator - 星号*加上yield,通过next方法获取,返回由一个value与done组成的对象
* async await - es7提出,await会中断当前执行,等待异步返回

4、promise.catch后面的.then还会执行吗?

* 会执行,catch(onRejected)从内部调用了then(undefined, onRejected)

5、确保构造函数只会被new调用,不会被当做普通函数执行

* 借助 instanceofnew 绑定的原理,适用于低版本浏览器
* 借助 new.target 属性,可与 class 配合定义抽象类
* 面向对象编程使用 ES6 class——最佳方案

6、获取实例对象的原型对象

* 从构造函数获取 - Fnc.prototype
* 从对象实例获取
    * 实例.__proto__
    * Object.getPrototypeOf(实例)

7、防抖与节流

* 防抖 - n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
    function debounce(fn, delay){
        let timeout;
        return function () {
            let context = this; // 保存this指向
            let args = arguments; // 拿到event对象
            clearTimeout(timeout)
            timeout = setTimeout(function(){
                fn.apply(context, args)
            }, delay);
        }
    }
* 节流 - n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
    function throttle(fn, delay){
        let timer = null;
        return function(...args){
            if(!timer){
                timer = setTimeout(function(){
                    fn.apply(this, args);
                    timer = null
                }, delay)
            }
        }
    }
* 常见的应用场景
    * 防抖在连续的事件,只需触发一次回调的场景有:
        搜索框搜索输入。只需用户最后一次输入完,再发送请求
        手机号、邮箱验证输入检测
        窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染
    * 节流在间隔一段时间执行一次回调的场景有:
        滚动加载,加载更多或滚到底部监听
        搜索框,搜索联想功能

8、图片懒加载

* 直接在img标签上添加 loading="lazy" // 存在兼容问题
* 监听滚动事件,判断图片是否到达可视区域
    * 拿到所有的图片 dom
    * 遍历每个图片判断当前图片是否到了可视区范围内
    * 如果到了就设置图片的 src 属性,可以将图片的src属性存储在 data-src 属性中
    * 绑定 window 的 scroll 事件,对其进行事件监听
    * 实现:
        function lazyload() {
            let viewHeight = document.body.clientHeight //获取可视区高度
            let imgs = document.querySelectorAll('img[data-src]')
            imgs.forEach((item, index) => {
                if (item.dataset.src === '') return
                // 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
                let rect = item.getBoundingClientRect()
                if (rect.bottom >= 0 && rect.top < viewHeight) {
                    item.src = item.dataset.src
                    item.removeAttribute('data-src')
                }
            })
        }
        function throttle(fn, delay) {
            let timer
            let prevTime
            return function (...args) {
                const currTime = Date.now()
                const context = this
                if (!prevTime) prevTime = currTime
                clearTimeout(timer)
                if (currTime - prevTime > delay) {
                    prevTime = currTime
                    fn.apply(context, args)
                    clearTimeout(timer)
                    return
                }
                timer = setTimeout(function () {
                    prevTime = Date.now()
                    timer = null
                    fn.apply(context, args)
                }, delay)
            }
        }
        lazyload();
        window.addEventListener('scroll', throttle(lazyload, 200))
* 监听元素显示
        const imgs = document.querySelectorAll('img[data-src]')
        const config = {
            rootMargin: '0px',
            threshold: 0,
        }
        let observer = new IntersectionObserver((entries, self) => {
            entries.forEach((entry) => {
                if (entry.isIntersecting) {
                    let img = entry.target
                    let src = img.dataset.src
                    if (src) {
                        img.src = src
                        img.removeAttribute('data-src')
                    }
                    // 解除观察
                    self.unobserve(entry.target)
                }
            })
        }, config)
        imgs.forEach((image) => {
            observer.observe(image)
        })

9、监听页面相关的耗时

* 使用window.performance
* 使用方法以及常用的耗时操作
    var e = window.performance;
    var t = e.getEntriesByType("navigation")[0];
    r=0;
    t || (r = (t = e.timing).navigationStart);
    <!-- 网页重定向耗时 -->
    t.redirectEnd - t.redirectStart
    <!-- 检查本地缓存的耗时 -->
    t.domainLookupStart - t.fetchStart
    <!-- DNS查询的耗时 -->
    t.domainLookupEnd - t.domainLookupStart
    <!-- TCP链接的耗时 -->
    t.connectEnd - t.connectStart
    <!-- 从客户端发起请求到接收到响应的时间 / Time To First Byte -->
    t.responseStart - t.requestStart
    <!-- 下载服务端返回数据的时间 -->
    t.responseEnd - t.responseStart
    <!-- http请求总耗时 -->
    t.responseEnd - t.requestStart
    <!-- dom加载完成的时间 -->
    t.domContentLoadedEventEnd - r
    <!-- 页面load的总耗时 -->
    t.loadEventEnd - r

10、对象方法

* Object.assign(target, source1, source2, ...)
    * 将多个对象的可枚举属性复制到目标元素,只拷贝源对象的自身属性,不拷贝继承的属性
    * 进行的是浅拷贝,如果source对象中存在复杂数据结构,那么在拷贝后也只是拷贝的地址
    * 只进行值的拷贝
    * 如果拷贝数组,那么会把数组当做对象
* Object.create(prototype, [propertiesObject])
    * 使用指定的原型对象创建一个新的对象
    * 新的属性包含的可选属性:
        * configurable - 是否可以被delete删除
        * enumerable - 是否可以被for-in循环
        * writable - 是否可以写入修改
        * value - 属性值
* Object.defineProperties(obj, props)
    * 在一个对象上定义新的属性或者修改新的属性,并返回该对象
* Object.defineProperty(obj, prop, descriptor)
    * 可同时修改、新增多个属性
    * 修改对象上的指定属性、或者新增一个属性
* Object.keys(obj)
    * 返回由索引组成的数组
    * 如果obj为数组,返回由下标组成的数组;如果是对象,返回由键值组成的数组
    * 顺序同for-in循环遍历的相同
    * 会忽略Symbol属性
* Object.values(obj)
    * 返回由属性值组成的数组
    * 会忽略Symbol属性的值
* Object.entries(obj)
    * 返回由对象可枚举属性的键值对组成的数组
    * 同样会忽略Symbol属性
* obj.hasOwnProperty(name)
    * 检测对象自身属性是否包含某个属性
    * 只在自身具有某个属性时才会返回true,属性在原型或者继承而来都会返回false
* Object.getOwnPropertyDescriptor(obj, prop)
    * 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
    * 如果指定的属性存在于对象上,则返回其属性描述符对象(property descriptor),否则返回 undefined
* Object.getOwnPropertyDescriptors(obj)
    * 获取一个对象的所有自身属性的描述符
* Object.getOwnPropertyNames()
    * 返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组
* Object.getOwnPropertySymbols()
    * 返回一个给定对象自身的所有 Symbol 属性的数组
* Object.getPrototypeOf()
    * 返回指定对象的原型