前端面试题之 JavaScript

JavaScript

  1. js中有几种数据类型,分别是什么?

    • number,string,boolean,null,undefined,object
  2. call()apply()bind()的异同点。

    • 相同点:
      • 三者的相同点:都是用来改变this的指向
      • call() 和 apply() 的相同点:都是调用一个对象的一个方法,用另一个对象替换当前对象(功能相同)
        • 例如:
          • B.call(A, args1,args2);即A对象调用B对象的方法
          • F.apply(G, arguments);即G对象应用F对象的方法
      • call()和 bind() 的相同点:都是用来改变this的指向
    • 不同点:
      • call() 和 apply() 的不同点:参数书写方式不同
        • call() 的第一个参数是 this 要指向的对象,后面传入的是参数列表,参数可以是任意类型,当第一个参数为 null、undefined 的时候,默认指向 window
        • apply() 的第一个参数是 this 要指向的对象,第二个参数是数组
      • call()和 bind() 的不同点:
        • call() 改过 this 的指向后,会再执行函数
        • bind()改过 this 后,不执行函数,会返回一个绑定新 this 的函数
  3. 如何准确判断变量是什么类型?

    • type of 判断
      • typeof 检测基本类型是没有问题的,如字符串、数值、布尔和 undefined,但是如果检测对象或null的话都会返回'object';
    • instanceof 判断
      • 此方法后面一定是数据类型:console.log(data instanceof Array),通常我们并不是想知道某个值是对象,而是想知道它是什么类型的对象,如上:变量 data 是 Array 类型的对象?
      • 所有引用类型值都是 object 的实例,所以在检测一个引用类型值和 Object 构造函数时,instanceof 操作符会一直返回 true。如用 instanceof 操作符检测基本类型值时,会一直返回false,原因很简单,因为基本类型不是对象
    • constructor 判断
      • console.log(data.constructor === Array)
    • Object.prototype.toString.call(data)
      • 此种方式可以准确的判断出数据类型,console.log(Object.prototype.toString.call(data))
  4. 请解释一下事件冒泡机制,并说明如何阻止冒泡。

    • 事件冒泡机制
      • 在事件开始时,由最具体的元素(文档中最底层的那个节点)接受,然后逐级向上传播到最不具体的节点(文档)
      • 比如说,在一个页面有一个div元素,加上一个点击事件,在点击事件触发的时候,它的传递过程就是由 div→body→html→document(老版本暂且不谈),它会沿着 DOM 树逐级的传递
    • 阻止事件冒泡
      • event.stopPropagation() // 不支持IE低版本
      • cancelBubble = true
  5. new一个对象的过程是什么?

    1. 创建空对象
      • var obj = {}
    2. 设置新对象的constructor属性为构造函数的名称,设置新对象的proto属性指向构造函数的prototype对象
      • obj.proto = ClassA.prototype
    3. 使用新对象调用函数,函数中的this被指向新实例对象:
      • ClassA.call(obj);  //{}.构造函数()
    4. 将初始化完毕的新对象地址,保存到等号左边的变量中
      • 注意:
        • 若构造函数中返回this或返回值是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象
        • 若返回值是引用类型的值,则实际返回值为这个引用类型
  6. this 的指向有哪几种情况,分别是什么?

    • 作为函数调用,非严格模式下,this指向window,严格模式下,this指向undefined;
    • 作为某对象的方法调用,this通常指向调用的对象。
    • 使用apply、call、bind 可以绑定this的指向。
    • 在构造函数中,this指向新创建的对象
    • 箭头函数没有单独的this值,this在箭头函数创建时确定,它与声明所在的上下文相同
  7. 谈谈对作用域和作用域链的理解。

    • 变量的作用域有两种:全局变量和局部变量
      • 全局作用域:
        • 最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的
      • 局部作用域:
        • 和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部
      • 注意:
        • 函数内部声明变量的时候,一定要使用var命令。如果不用的话,实际上声明了一个全局变量
    • 作用域链:
      • 全局作用域和局部作用域中变量的访问权限,其实是由作用域链决定的
      • 执行环境决定了变量的生命周期,以及哪部分代码可以访问其中变量
      • 执行环境有全局执行环境(全局环境)和局部执行环境之分
      • 每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链
      • 函数的局部环境可以访问函数作用域中的变量和函数,也可以访问其父环境,乃至全局环境中的变量和环境
      • 全局环境只能访问全局环境中定义的变量和函数,不能直接访问局部环境中的任何数据
      • 变量的执行环境有助于确定应该合适释放内存
  8. 谈谈对原型和原型链的理解。

    • 原型
      • 原型就是 prototype 对象,用来表示类型之间的关系
      • 原型的作用:
        • 数据共享 节约内存内存空间
        • 实现继承
      • 注意:函数也是一个对象,对象不一定是函数(对象有proto属性,函数有prototype属性)
    • 原型链
      • 对象和对象之间是有联系的,通过prototype对象指向父类对象,在指向Object对象。而通过的方式就是proto连接,而proto最终指向null
      • 图例:
        • 图例
  9. 列举 js 中继承的几种方式。

    • 原型链继承
      • 核心: 将父类的实例作为子类的原型
    • 构造继承
      • 核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
    • 实例继承
      • 核心:为父类实例添加新特性,作为子类实例返回
    • 拷贝继承
    • 组合继承
      • 核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
    • 寄生组合继承
      • 核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
  10. 谈谈对闭包的理解,并说明其优缺点。

    • 使用闭包主要是为了设计私有的方法和变量
    • 在js中,函数即闭包,只有函数才会产生作用域的概念
    • 闭包有三个特性:
      • 函数嵌套函数
      • 函数内部可以引用外部的参数和变量
      • 参数和变量不会被垃圾回收机制回收
    • 优点:
      • 可以避免全局变量的污染
    • 缺点:
      • 闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露
  11. 谈谈 js 中的异步(事件循环[Event Loop]机制)。

    • 浏览器中的 Event Loop
      • Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行
    • JS调用栈
      • JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空
    • 同步任务和异步任务
      • Javascript单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行
      • [图片上传失败...(image-5b483b-1585842119691)]
      • 任务队列 Task Queue,即队列,是一种先进先出的一种数据结构
  12. 列举 js 中数组的常用方法并说明用途。

    • push() (末尾添加元素)
      • 作用:向数组的末尾添加n个元素
      • 参数:参数时新增的元素,可以串多个
      • 返回值:新数组的数组成员的个数
      • 原数组发生变化
    • pop() (删除最后一项)
      • 作用:删除数组的最后一项
      • 参数:不需要传参数
      • 返回值:被删除的那一项
      • 原数组发生改变
    • unshift() (开始位置添加一项)
      • 作用:向数组的开始位置添加n个元素
      • 参数:参数是新增的元素,可以是多个
      • 返回值:新数组的长度
      • 原数组发生改变
    • shift() (开始位置删除一项)
      • 作用:删除数组的第一项
      • 参数:不需要传参数
      • 返回值:被删除的那一项
      • 原数组发生改变
    • splice() (删除、添加、修改元素)
      • 作用:删除元素,并向数组添加新元素
      • 参数:
        • splice(m,n);从索引m开始,删除n项
        • splice(m);从索引m开始删除到末尾
        • splice(m,n,x,…);从索引m开始删除n项,并将x添加到原来位置;添加项可以是多个(如果删除项为0个,那么添加是在m元素前面)
      • 返回值:是个数组,数组中是被删除的项
      • 原数组发生变化
    • indexOf()(从左向右查询)不兼容IE低版本浏览器:IE6,7,8
      • 作用:检测数组中是否存在某个元素
      • 参数:被查询的元素(m,n)从索引n开始,m第一次出现的索引位置
      • 返回值:
        • 返回数组中第一次匹配到的元素的索引
        • 如果数组中没有匹配项返回-1
      • 原数组不发生变化
    • lastIndexOf()(从左到右查询)不兼容IE低版本浏览器:IE6,7,8
      • 作用:检测数组中是否存在某个元素
      • 参数:被检测的元素
      • 返回值:
        • 返回数组中最后一次匹配到的元素的索引
        • 如果数组中没有匹配项返回-1
      • 原数组不发生变化
    • slice()(截取)
      • 作用:按照起始和结束位置的索引截取数组
      • 参数:
        • 有两个参数slice(m,n):从索引m开始,截取到索引n;(包前不包后)
        • 有一个参数slice(m):从索引m开始截取到末尾;
        • 没有参数:数组的克隆;(slice(0)也是数组的克隆);
        • 以上情况参数都支持负数,负数情况会默认被加上数组的长度,处理成正数
      • 返回值:截取的数组
      • 原数组不发生变化
    • sort()(排序)
      • 作用:对数组的元素进行排序
      • 参数:
        • 没有参数sort():只能排序数组成员项是相同位数的数字
        • sort(function(a,b){return a-b}):从小到大排序
        • sort(function(a,b){return b-a}):从大到小排序
      • 返回值:排序之后的数组
      • 原数组发生变化
    • reverse()(倒序)
      • 作用:使数组中元素倒序
      • 参数:不需要参数
      • 返回值:成员倒序的数组
      • 原数组发生变化
    • concat()(拼接数组)
      • 作用:拼接两个或多个数组
      • 参数:
        • 不传参数:数组的克隆
        • 传参数:将传入的参数拼接到数组中、可以传多个
      • 返回值:拼接之后的新数组
      • 原数组不发生变化
    • join()(数组拼接成字符串)
      • 作用:将数组的成员项通过制定字符拼接成字符串
      • 参数:
        • 不传参数:会默认按照逗号拼接
        • 传参数:会按照参数字符拼接
      • 返回值:拼接之后的字符串
      • 原数组不发生变化
    • map()(映射)
      • 作用:方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值
      • 参数:参数是一个回调函数;数组有几项,回调函数就执行多少次;在回调函数中处理数组中的每项
      • 返回值:映射的新数组;
      • 原数组不发生变化;
    • forEach()(遍历数组)
      • 作用:遍历数组;
      • 参数:参数是一个回调函数;数组有几项,回调函数就执行多少次
      • 返回值:没有返回值;undefined
      • 原数组不发生变化
    • ary.forEach()和ary.map()的区别
      • ary.forEach()是和for循环一样,是代替for。ary.map()是修改数组其中的数据,并返回新的数据
      • ary.forEach() 没有return ary.map() 有return
      • map有映射,forEach没有映射。
    • toString()(数组转字符串)
      • 作用:数组转字符串
      • 参数:不需要参数
      • 返回值:一个字符串,由数组的每项组成,数组的每一项用逗号隔开
      • 原数组不发生变化;
    • find(查找)
      • 从左到右依次进行查找,找到符合条件的那一项,直接返回,不再进行查找;如果找不到,那么返回undefined; 返回true,说明就找到了
      • find会根据回调函数的返回值,判断是否要继续向右查找;
    • filter(过滤)
      • 过滤;原数组不发生改变;返回一个过滤后的新数组
    • every
      • 每一个都是true则返回true,如果有一个是false,那么直接返回false;只要找到false,直接结束,不再继续向下查找;返回值是布尔值
    • some
      • 返回一个布尔值;只要有一个符合条件就返回true;找到true,就不再向右进行查找;
    • includes
      • 返回一个布尔值;如果有就返回true;,没有就返回false;
    • reduce
      • 迭代数组的所有项,累加器,数组中的每个值(从左到右)合并,最终计算为一个值
      • reduce回调函数后面可以传一个参数
  13. js 如何实现深拷贝与浅拷贝

    • 浅拷贝和深拷贝都只针对于引用数据类型
    • 浅拷贝
      • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
    • 深拷贝
      • 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
    • 区别:
      • 浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制
  14. 谈一谈对面向对象的认识。

    • 面向对象是向现实世界模型的自然延伸,这是一种"万物皆对象"的编程思想
    • 面向对象有三大特性,封装、继承和多态
      • 封装
        • 封装是为了使得代码的复用性更高
        • 封装是将一类事物的属性和行为抽象成一个类,使其属性私有化,行为公开化,提高了数据的隐秘性的同时,使代码模块化
      • 继承
        • 继承是为了扩展了已存在的代码块,进一步提高了代码的复用性
        • 继承是进一步将一类事物共有的属性和行为抽象成一个父类,而每一个子类是一个特殊的父类--有父类的行为和属性,也有自己特有的行为和属性
      • 多态
        • 多态是为了实现接口重用
        • 多态的一大作用就是为了解耦--为了解除父子类继承的耦合度。如果说继承中父子类的关系式IS-A的关系,那么接口和实现类之之间的关系式HAS-A。简单来说,多态就是允许父类引用(或接口)指向子类(或实现类)对象。很多的设计模式都是基于面向对象的多态性设计的
    • 如果说封装和继承是面向对象的基础,那么多态则是面向对象最精髓的理论。掌握多态必先了解接口,只有充分理解接口才能更好的应用多态
  15. js 的高阶函数有哪些?说明其用处。

    • 高阶函数
      • 一个函数接受一个或多个函数作为参数,或者可以返回一个参数
    • 常见的高阶函数
      • setInterval , setTimeout , sort ,一般用于函数回调
      • map:应用于数组,对数组进行操作
      • reduce 归纳的意思,作用是对数组的每个值求和积等操作
      • filter 数组过滤
  16. 什么是防抖和节流?有什么区别?如何实现?

    • 防抖
      • 触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间
      • 思路:
        • 每次触发事件时都取消之前的延时调用方法
      • 代码:
        •  function debounce(fn) {
               let timeout = null; // 创建一个标记用来存放定时器的返回值
               return function () {
                   clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
                   timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
                   fn.apply(this, arguments);
                   }, 500);
               };
           }
           function sayHi() {
               console.log('防抖成功');
           }
          
           var inp = document.getElementById('inp');
           inp.addEventListener('input', debounce(sayHi)); // 防抖 
          
    • 节流
      • 高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率
      • 思路:
        • 每次触发事件时都判断当前是否有等待执行的延时函数
      • 代码:
        •  function throttle(fn) {
               let canRun = true; // 通过闭包保存一个标记
               return function () {
                   if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
                   canRun = false; // 立即设置为 false
                   setTimeout(() => { // 将外部传入的函数的执行放在 setTimeout 中
                       fn.apply(this, arguments);
                       // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
                       canRun = true;
                   }, 500);
               };
           }
           function sayHi(e) {
               console.log(e.target.innerWidth, e.target.innerHeight);
           }
           window.addEventListener('resize', throttle(sayHi));
          
          
  17. 编写一段代码:实现数组去重

    • Set
      •   var newArr = Array.from(new Set(arr));//不改变原数组
        
    • 扩展运算符 […]
      •   var resultarr = [...new Set(arr)]; //不改变原数组
        
    • filter
      •   let newArr = arr.filter((item,key,self) => self.indexOf(item) == key);
        
  18. 编写一段代码:实现数组扁平化

    • 数组扁平化
      • 是指将一个多维数组变为一维数组
        •   [1, [2, 3, [4, 5]]]  ------>    [1, 2, 3, 4, 5]
          
      • 思路:
        • 遍历数组 arr,若 arr[i] 为数组则递归遍历,直至 arr[i] 不为数组然后与之前的结果 concat
          • concat() 方法用于连接两个或多个数组
          • 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本
    • 实现方式
      • reduce
        • 遍历数组每一项,若值为数组则递归遍历,否则concat
        •   function flatten(arr) {  
                return arr.reduce((result, item)=> {
                    return result.concat(Array.isArray(item) ? flatten(item) : item);
                }, []);
            }
          
        • reduce 是数组的一种方法,它接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值
        • reduce包含两个参数:回调函数,传给total的初始值
      • toString & split
        • 调用数组的 toString 方法,将数组变为字符串然后再用 split 分割还原为数组
        • 因为split分割后形成的数组的每一项值为字符串,所以需要用一个map方法遍历数组将其每一项转换为数值型
        •   function flatten(arr) {
                return arr.toString().split(',').map(function(item) {
                    return Number(item);
                })
            } 
          
      • join & split
        • 和上面的toString一样,join也可以将数组转换为字符串
        •   function flatten(arr) {
                return arr.join(',').split(',').map(function(item) {
                    return parseInt(item);
                })                                
            }   
          
      • 递归
        • 递归的遍历每一项,若为数组则继续遍历,否则concat
        •   function flatten(arr) {
                var res = [];
                arr.map(item => {
                    if(Array.isArray(item)) {
                        res = res.concat(flatten(item));
                    } else {
                        res.push(item);
                    }                
                });                
                return res;
            }
          
      • 扩展运算符
        • es6的扩展运算符能将二维数组变为一维
        • 可以做一个遍历,若arr中含有数组则使用一次扩展运算符,直至没有为止
        •   function flatten(arr) { 
                while(arr.some(item=>Array.isArray(item))) {
                    arr = [].concat(...arr);
                } 
                return arr;
            }
          
          
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容