前端面试题(JavaScript基础篇)

时间:2025-03-08 10:31:20

前端面试题,JavaScript基础篇共收录面试题57道。

1、介绍JavaScript的基本数据类型?

  • 基本数据类型:Number、String、Boolean、Null、Undefined
  • object是JavaScript中所有对象的父对象
  • 数据封装类对象:object、Array、Boolean、Number、String
  • 其它对象:Function、Arguments、Math、Date、Error、RegExp
  • 其它数据类型:Symbol

2、浅谈JavaScript中变量和函数声明的提升?

  • 在JavaScript中变量和函数的声明会提升到最顶部执行
  • 函数的提升高于变量的提升
  • 函数内部如果用var声明了相同名称的外部变量,函数将不会向上寻找
  • 匿名函数不会提升
  • 不同<script>块中的函数互不影响

3、什么是闭包,闭包有什么特性?

  • 闭包就是能够读取其它函数内部变量的函数
  • 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包最常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用域链
  • 闭包的特性
    • 函数内再嵌套函数
    • 内部函数可以引用外部的参数或变量
    • 参数和变量不会被垃圾回收机制回收

4、说说对闭包的理解和闭包的作用

  • 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,增大内存使用量,使用不当很容易造成内存泄漏。在JS中,函数即闭包,只有函数才会产生作用域的概念
  • 闭包的最大用处有2个,一个是可以读取函数内部的变量,另一个就是可以让这些变量始终保持在内存中
  • 闭包的另一个用处是封装对象的私有属性和方法
  • 好处:能够实现封装和缓存
  • 坏处:就是消耗内存、不正当使用会造成内存溢出的问题

   使用闭包的注意点

  • 由于闭包会使得函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏。
  • 解决方法是,在退出函数之前,将不使用的局部变量全部删除。

5、说说对This对象的理解

  • this总是指向函数的直接调用者,而非间接调用者
  • 如果有new关键字,this指向new出来的那个对象
  • 在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象window

6、说说对事件模型的理解

  • W3C中定义时间的发生精力三个阶段
    • 捕获阶段
    • 目标阶段
    • 冒泡阶段
  • 冒泡型事件:当你使用事件冒泡时,子元素先触发,父元素后触发
  • 捕获型事件:当你使用事件捕获时,父元素先触发,子元素后触发
  • DOM事件流:同时支持两种事件模型,捕获型事件和冒泡型事件
  • 阻止冒泡:在W3C中,使用stopPropagation()方法;在IE下设置cancelBubble = true
  • 阻止捕获:阻止事件的默认行为,例如click - <a>后的跳转。在W3C中,使用preventDefault()方法,在IE下设置 = false

7、new 操作符具体干了什么?

  • 创建一个空对象,并且this变量引用该对象,同时还继承了该函数的原型
  • 属性和方法被加入到this引用的对象中
  • 新创建的对象由this所引用,并且最后隐式的返回this

8、说说栈和堆的理解,以及它们的区别?

  • 栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命后期都很短
  • 堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆内存中,堆内存的都是放的实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也不会消失,还可以使用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,有垃圾回收机制不定时的收取

   栈和堆的区别:

  • 栈内存存储的是局部变量,而堆内存存储的是实体
  • 栈内存更新速度要快于堆内存,因为局部变量的生命周期都很短
  • 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收

9、JS数组和对象的遍历方式,以及几种方式的比较

  • for in 循环
  • for 循环
  • forEach 循环
    • 这里的forEach回调中两个参数分别为:value , index
    • forEach无法遍历对象
    • IE不支持该方法;FireFox和Chrome支持
    • forEach无法使用break ,continue跳出循环,且使用return是跳过本次循环
    • for-in 需要分析出array的每个属性,这个操作性能开销很大。用在key已知的数组上是不划算的。所以尽量不要用for-in,除非你不清楚要处理哪些属性,例如JSON对象这样的情况
    • for循环每进行一次,就要检查一下数组长度。读取属性(数组长度)要比读局部变量慢,尤其是当array里存放的都是DOM元素,因为每次读取都会扫描一遍页面上的选择器相关元素,速度会大大降低

10、map和forEach的区别

  • forEach方法,是最基本的方法,就是遍历与循环,默认有3个参数:分别是遍历的数组内容item、数组索引index、当前遍历的数组Array
  • map方法,基本用法与forEach一致,但不同的是,它会返回一个新的数组,所以callback需要有return值,如果没有,会返回undefined

11、谈一谈箭头函数与普通函数的区别?

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不可以当做构造函数,也就是不可以使用new命令,否则会抛出一个错误 
  • 不可以使用arguments对象,该对象在函数体内不存在,如果要用,可以使用Rest参数代替
  • 不可以使用yield命令,因为箭头函数不可以用作Generator函数(遍历器函数)

12、JavaScript定义类的4中方法

  • 工厂方法
    function creatPerson(name, age) {
        var obj = new Object();
         = name;
         = age;
         = function () {
            alert();
        }
        return obj;
    }
  • 构造函数方法
    function Person(name, age) {
         = name;
         = age;
         = function () {
            alert();    
        }
    }
  • 原型方法
    function Person () {};
     = {
        constructor: Person,
        name: "ZhangSan",
        age: "20",
        sayName: function () {
            alert();
        }
    }
  • 组合使用构造函数和原型方法
    function Person(name, age) {
         = name;
         = age;
    }
     = {
        constructor: Person,
        sayName: function () {
            alert();
        }
    }

13、JavaScript实现继承的3中方法

  • 借用构造函数法
    function SuperType(name) {
         = name;
         = function () {
            alert();
        }
    }
    
    function SubType(name, age) {
        (this, name); // 这里借用了父类的构造函数
         = age;
    }
  • 对象冒充
    function SuperType(name) {
         = name;
         = function() {
            alert();
        }
    }
    
    function SubType(name, age) {
         = SuperType; // 在这里使用了对象冒充
        (name);
         = age;
    }
  • 组合继承
    function SuperType(name) {
         = name;
    }
    
     = {
        sayName: function () {
            alert();
        }
    }    
    
    function SubType(name, age) {
        (this, name); // 在这里继承了属性
         = age;
    }
    
     = new SuperType(); // 这里继承方法

14、对原生Javascript了解程度

  • 数据类型、运算、对象、Function、继承、闭包、作用域、原型链、时间、RegExp、JSON、Ajax、DOM、BOM、内存泄漏、跨域、异步装载、模板引擎、前端MVC、路由、模块化、Canvas、ECMAScript

15、JS动画与CSS动画区别及相应实现

  • CSS3动画的优点
    • 在性能上会烧毁好些,浏览器会对CSS3的动画做一些优化
    • 代码相对简单
  • CSS3动画的缺点
    • 在动画控制上不够灵活
    • 兼容性不好
  • JavaScript的动画正好弥补了这两个缺点,控制能力很强,可以单帧的控制、变换,同时写得好完全可以兼容IE6,并且功能强大。对于一些复杂的控制动画,使用JavaScript会比较靠谱。而在实现一些小的交互动效的时候,可以考虑CSS。

16、谈一谈你理解的函数式编程

  • 简单说,“函数式编程”是一种“编程范式”,也就是如何编写程序的方法论
  • 它具有以下特性:闭包和高阶函数、惰性计算、递归、函数是“第一等公民”、只用“表达式”

17、说说你对作用域链的理解

  • 作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向*问,访问到window对象即会终止,作用域链向下访问是不被允许的
  • 作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期

18、什么是JavaScript原型,原型链?有什么特点?

  • 每个对象都在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
  • 关系: = instance.__proto__
  • JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
  • 当我们需要一个属性时,JavaScript引擎会先看当前对象中是否有这个属性,如果没有就会找他的prototype中是否有这个属性,如此递推下去,直到Object内建对象

19、说说什么是事件代理?

  • 事件代理,又称之为事件委托。是JavaScript中绑定事件的常用技巧。顾名思义,“事件代理”就是把原本需要监听的事件委托给父元素,由父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。使用事件地理的好处是可以提高性能。
  • 可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒
  • 可以实现新增子对象时无需再次对其绑定

20、说说Ajax原理?

  • Ajax的原理简单来说实在用户和服务器之间加了一个中间层(AJAX引擎),由XMLHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
  • Ajax的过程只涉及JavaScript、XMLHttpRequest和DOM。XMLHttpRequest是Ajax的核心机制
  • Ajax的优点:
    • 通过异步模式提升了用户体验
    • 优化了浏览器和服务器之间的传输,减少了不必要的数据往返,减少了带宽占用
    • Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载
    • Ajax可以实现局部刷新
  • Ajax的缺点:
    • 安全问题,Ajax暴露了与服务器的交互细节
    • 对搜索引擎的支持比较弱
  • Ajax的请求过程:
    // 1、创建连接
    var xhr = null;
    xhr = new XMLHttpRequest();
    // 2、连接服务器
    ('get', url, true);
    // 3、发送请求
    (null);
    // 4、接受请求
     = function () {
        if ( == 4) {
            if ( == 200) {
                success();
            } else {
                // false
                fail && fail();
            }
        }
    }

21、说说如何解决跨域问题?

  • 首先了解下浏览器的同源策略,他是一种约定,是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,浏览器会很容易收到XSS、CSFR等攻击。所谓同源策略是值“协议+域名+端口”三者相同,即便两个不同的域名指向同一个IP地址,也非同源
  •  通过jsonp跨域
  • + iframe跨域
  • nginx代理跨域
  • nodejs中间件代理跨域
  • 后端在头部信息里面设置安全域名解决跨域

22、异步加载JS的方式有哪些?

  • defer,只支持IE
  • async
  • 创建script,插入到DOM中,加载完毕后callback
    // 异步加载地图
    export default function MapLoader() {
        return new Promise((resolve, reject) => {
            if () {
                resolve();
            } else {
                var script = ('script');
                 = 'text/javascript';
                 = ture;
                 = '';
                 = reject;
                (script);
            }
             = () => {
                resolve();
            }
        })
    }

23、哪些操作会造成内存泄漏?

  • 内存泄漏指任何对象在您不再拥有或需要它之后仍然存在
  • setTimeout的第一个参数使用字符而非函数的话,会引发内存泄漏
  • 闭包使用不当

24、介绍JS有哪些内置对象?

  • Object是JavaScript中所有对象的父对象
  • 数据封装类对象:Object、Array、Boolean、Number、String
  • 其它对象:Function、Arguments、Math、Date、RegExp、Error

25、说几条写JavaScript的基本规范

  • 不要在同一行声明多个变量
  • 请使用 === / !== 号来比较 true / false 或者数值
  • 使用对象字面量替代 new Array这种形式
  • 不要使用全局函数
  • Switch 语句必须带有 default 分支
  • if 语句必须使用大括号
  • for-in 循环中的变量应该使用var关键字明确限定作用域,从而避免作用域污染

26、eval是做什么的?

  • 它的功能是把对应的字符串解析成JS代码并运行
  • 应该避免使用eval,不安全,非常耗性能(2次,一次解析成JS语句,一次执行)
  • 有JSON字符串转换为JSON对象的时候可以用eval
    var obj = eval('(' + str + ')');

27、null 和 undefined 的区别

  • undefined表示不存在这个值
  • undefined是一个表示“无”的原始值或者表示“缺少值”,就是此处应该有一个值,但是还没定义。当尝试读取时就会返回undefined
  • 例如变量被声明了,但没有赋值时,就等于undefined
  • null表示一个对象被定义了,值为“空值”
  • null是一个对象,一个空对象,没有任何属性和方法
  • 例如作为函数的参数,表示该函数的参数不是对象
  • 在验证null时,一定要使用===,因为==无法分辨null和undefined

28、说说同步和异步的区别?

  • 同步:指的是同一时间只会执行一个任务,只有当前任务执行结束才会执行下一个任务
  • 异步:指的是多个任务按照你编码的顺序执行任务,但是任务的完成顺序不一定和开始的顺序相同,也就是在此期间可以进行其它操作,常用的异步实现方法有回调、Promise

29、defer 和 async 并行加载js文件的区别?

  • defer 并行加载 js 文件,会按照页面上 script 标签的顺序执行
  • async 并行加载js文件,下载完成立即执行,不会按照页面上 script 标签的顺序执行

30、["1", "2", "3"].map(parseInt)  答案是多少?

  • 答案是 [1,NaN,NaN],因为parseInt需要两个参数(val, radix),其中radix表示解析时用的基数,radix是一个介于2-36之间的整数,返回解析后的整数值。如果被解析参数的第一个字符无法被转化成数值类型,则返回NaN。
  • map 传了 3 个 (element, index, array),对应的radix不合法导致解析失败
  • 解析过程:
    parseInt('1', 0); // radix为0时,使用默认的10进制。
    parseInt('2', 1); // radix值在2-36,无法解析,返回NaN
    parseInt('3', 2); // 基数为2,2进制数表示的数中,最大值小于3,无法解析,返回NaN

31、use strict的理解和作用?

  • use strict 是一种ECMAscript5 添加的运行模式,这种模式是的JavaScript在更严格的条件下运行,使JS编码更加规范化的模式,清除JavaScript语法的一些不合理、不严谨之处,减少一些怪异行为

32、说说严格模式的限制

  • 变量必须声明后再使用

  • 函数的参数不能有同名属性,否则报错

  • 不能使用with语句

  • 禁止this指向全局对象

33、说说ES6新增了哪些特性

  • 新增模板字符串,为JavaScript提供了简单的字符串插值功能
  • 箭头函数
  • for-of,用来遍历数据,例如数组中的值
  • arguments对象可被不定参数和默认参数完美替代
  • ES6将Promise对象纳入规范,提供了原生的Promise对象
  • 增加了let和const命令,用来什么变量
  • 增加了块级作用域,let命令实际上就增加了块级作用域
  • 还有就是引入了module模块的概念

34、说说对JSON的了解?

  • JSON是一种轻量级的数据交换格式
  • 他是基于JavaScript的一个子集。数据格式简单,易于读写,占用带宽小
  • JSON字符串转换为JSON对象:
    var obj = eval('(' + str + ')');
    var obj = ();
    var obj = (str);
  • JSON对象转换为JSON字符串:
    var last = ();
    var last = (obj);

35、说说JS延时加载的方式有哪些?

  • defer 和 async、动态创建DOM的方式(用的最多)、按需异步载入js

36、说说attribute 和 property 的区别是什么?

  • attribute 是dom元素在文档中作为html标签拥有的属性
  • property 是dom元素在js中作为对象用户的属性
  • 对于html的标准属性来说,attribute 和 property 是同步的,是会自动更新的
  • 但是对于自定义的属性来说,他们是不同步的

37、说说let的区别是什么?

  • let 命令不存在变量提升,如果再let前使用,会导致报错
  • 如果块区中使用let 和 const 命令,就会形成封闭作用域
  • 不允许重复声明,因此,不允许在函数内部重复声明参数

38、如何通过JS判断一个数组?

  • instanceof方法
    // instanceof运算符是用来测试一个对象是否在其原型链原型构造函数的属性
    var arr = [];
    arr instanceof Array; // true
  • constructor方法
    // constructor属性返回对创建此对象的数组函数的引用,就是返回对象相对应的构造函数
    var arr = [];
     == Array; // true
  • isArray()方法
    var a = new Array(123);
    var b = new Date();
    ((a)); // true
    ((b)); // false

39、说说var、let、const的区别

  • var 
    • 支持变量声明与解析
    • 存在变量提升
    • 不支持块级作用域
    • 允许重复声明 
  • let
    • 不支持变量声明与解析
    • 不存在变量提升
    • 支持块级作用域
    • 不允许重复声明
  • const
    • 不支持变量声明与解析
    • 不存在变量提升
    • 支持块级作用域
    • 不允许重复声明,声明变量必须赋值,一旦确定不允许被修改,但如果是复合类型时,只改变某个value项是可以的

40、JavaScript 中 callee 和 caller 的作用

  • caller 是返回一个对函数的引用,该函数调用了当前函数
  • callee 是返回正在被执行的 function 函数,也就是所指定的 function 对象的正文

41、说说 和 $(document).ready 的区别

  • ()方法是必须等到页面包括图片的所有元素加载完毕后才能执行
  • $(document).ready() 是 DOM 结构绘制完毕后执行,不必等到加载完毕

42、JavaScript 数组去重方法

  • 利用for嵌套for,然后splice去重(ES5中常用)
    function unique(arr) {
        for (var i = 0; i < ; i++) {
            for (var j = i + 1; j < ; j++) {
                if (arr[i] === arr[j]) { // 第一个等同于第二个,删除第二个
                    (j, 1);
                    j--;
                }
            }
        }
        return arr;
    }
    var arr = [1, 1, 'true', 'true', true, true, 15, false, undefined, undefined, null];
    (unique(arr));
  • 利用ES6 Set去重(ES6中常用)
    function unique(arr) {
        return (new Set(arr));
    }
    var arr = [1, 1, 'true', 'true', true, true, 15, false, undefined, undefined, null];
    (unique(arr));
  • 利用indexOf去重
    function unique(arr) {
        if (!(arr)) {
            ('type error');
            return;
        }
        var array = [];
        for (var i = 0; i < ; i++) {
            if ((arr[i]) === -1) {
                (arr[i]);
            }
        }
        return array;
    }
    var arr = [1, 1, 'true', 'true', true, true, 15, false, undefined, undefined, null];
    (unique(arr));
  • 利用soft()去重
    function unique(arr) {
        if (!(arr)) {
            ('type error');
            return;
        }
        arr = ();
        var array = [arr[0]];
        for (var i = 1; i < ; i++) {
            if (arr[i] !== arr[i - 1]) {
                (arr[i]);
            }
        }
        return array;
    }
    var arr = [1, 1, 'true', 'true', true, true, 15, false, undefined, undefined, null];
    (unique(arr));
  • 利用对象的属性不能相同的特点进行去重
    function unique(arr) {
        if (!(arr)) {
            ('type error');
            return;
        }
        var array = [];
        var obj = {};
        for (var i = 0; i < ; i++) {
            if (!obj[arr[i]]) {
                (arr[i]);
                obj[arr[i]] = 1;
            } else {
                obj[arr[i]]++;
            }
        }
        return array;
    }
    var arr = [1, 1, 'true', 'true', true, true, 15, false, undefined, undefined, null];
    (unique(arr));

43、浏览器缓存

  • 浏览器缓存分为强缓存和协商缓存,当客户端请求某个资源时,获取缓存的流程如下
    • 先根据这个资源的一些http header 判断它是否命中强缓存,如果命中,则直接从本地获取缓存资源,不会发请求到服务器
    •  强缓存没有命中时,客户端会发送请求到服务器,服务器通过另一些 request header 验证这个资源是否命中协商缓存,称为 http 再验证,如果命中,服务器将请求返回,但不返回资源,而是告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源
    • 强缓存和协商缓存共同之处在于,如果命中缓存,服务器都不会返回资源;区别是,强缓存不会发送请求到服务器,协商缓存会
    • 当协商缓存也没有命中时,服务器就会将资源发送回客户端
    • 当 ctrl+f5 强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存
    • 当 f5 刷新网页时,跳过强缓存,但是会检查协商缓存

        强缓存       

  • Expires 该字段是 http1.0 时的规范,值为一个绝对时间的 GMT 格式的时间字串,代表缓存资源的过期时间
  • Cache-Control:max-age 该字段是http1.1 的规范,强缓存利用其max-age 值来判断缓存资源的最大生命周期,它的值单位为秒

        协商缓存

  • Last-Modified 值为资源最后更新时间,随服务器response返回
  • If-Modified-Since 通过比较两个时间来判断资源在两次请求期间是否有过修改,如果没有修改,则命中协商缓存
  • ETag 表示资源内容的唯一标识,随服务器response 返回
  • If-None-Match 服务器通过比较请求头部的 If-None-Match 与当前资源的 ETag 是否一致来判断资源是否在两次请求直接有过修改,如果没有修改,则命中协商缓存

44、防抖/节流的理解

  • 防抖:在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。可以通过函数防抖来实现
    • 开始一个定时器,只要我定时器还在,不管你怎么点击都不会执行回调函数。一旦定时器结束并设置为null,就可以再次点击了
    • 对于延时执行函数来说的实现:每次调用防抖函数都会判断本次调用和之前的时间间隔,如果小于需要的时间间隔,就会重新创建一个定时器,并且定时器的延时为设定时间减去之前的时间间隔。一旦时间到了,就会执行相应的回调函数。
// *使用 underscore 的源码来解释防抖动
// *underscore 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func才会执行
// *@param { function } func 回调函数
// *@param { number } wait 表示时间窗口的间隔
// *@param { boolean } immediate 设置为true时,是否立即调用函数
// *@param { function } 返回客户调用函数

_.debounce = function (func, wait, immediate) {
    var timeout, args, context, timestamp, result;
    var later = function () {
        // 现在和上一次时间戳比较
        var last = _.now() - timestamp;
        // 如果当前时间间隔少于设定时间且大于0就重新设置定时器
        if (last < wait && last >= 0) {
            timeout = setTimeout(later, wait - last);
        } else {
            // 否则的话就是时间到了,执行回调函数
            timeout = null;
            if (!immediate) {
                result = (context, args);
                if (!timeout) context = args = null;
            }
        }
    };

    return function () {
        context = this;
        args = arguments;
        // 获得时间戳
        timestamp = _.now();
        // 如果定时器不存在且立即执行函数
        var callNow = immediate && !timeout;
        // 如果定时器不存在就创建一个
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
            // 如果需要立即执行函数的话,通过 apply 执行
            result = (context, args);
            context = args = null;
        }
        return result;
    }
}
  • 节流:防抖动和节流本质是不一样的。防抖动是将多次执行变成最后一次执行,节流是将多次执行变成每隔一段时间执行
/**
* underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
* @param { function } func 回调函数
* @param { number } wait 表示时间窗口的间隔
* @param { object } options 如果想忽略开始函数的的调用,传入{ leading: false }。
* 如果想忽略结尾函数的调用,传入{ trailing: false }
* 两者不能共存,否则函数不能执行
* @return { function } 返回客户调用函数
**/

_.throttle = function (func, wait, options) {
    var context, args, result; var timeout = null; var previous = 0;
    // 如果 options 没传则设为空对象 if (!options) options = {}; 
    // 定时器回调函数
    var later = function () {
        // 如果设置了 leading,就将 previous 设为 0 
        // 用于下面函数的第一个 if 判断
        previous =  === false ? 0 : _.now();
        result = (context, args);
        if (!timeout) context = args = null;
    };
    return function () {
        var now = _.now();
        // 首次进入前者肯定为 true
        // 如果需要第一次不执行函数
        // 就将上次时间戳设为当前的
        // 这样在接下来计算 remaining 的值时会大于 0  if (!previous &&  === false) previous = now;  
        // 计算剩余时间
        var remaining = wait - (now - previous);
        context = this; args = arguments;
        // 如果当前调用已经大于上次调用时间 + wait
        // 如果设置了 trailing,只会进入这个条件
        // 如果没有设置 leading,那么第一次会进入这个条件
        // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
        // 其实还是会进入的,因为定时器的延时
        // 并不是准确的时间,很可能你设置了 2 秒  
        // 但是他需要 2.2 秒才触发,这时候就会进入这个条件
        if (remaining <= 0 || remaining > wait) {
            // 如果存在定时器就清理掉否则会调用二次回调
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            } previous = now; result = (context, args);
            if (!timeout) context = args = null;
        } else if (!timeout &&  !== false) {
            // 判断是否设置了定时器和 trailing
            // 没有的话就开启一个定时器
            // 并且不能,不能同时设置 leading 和 trailing
            timeout = setTimeout(later, remaining);
        } return result;
    };
};

45、JavaScript 变量提升

  • 当执行JS代码时,会生成执行环境,只要代码不是写在函数中的,就是在全局执行环境中,函数中的代码会产生函数执行环境
  • 因为函数和变量提升的原因。通常提升的解释是说将声明的代码移动到了顶部,这其实没有什么错误,方便大家理解。但是更准确的解释应该是:在生成执行环境时,会有两个阶段。第一个阶段是创建的阶段,JS解释器会找出需要提升的变量和函数,并且给他们提前在内存中开辟好空间,函数的话会将整个函数存入到内存中,变量只什么并且赋值为undefined。所以在第二阶段,也就是代码执行阶段,我们可以直接提前使用。

46、实现Storage,使得该对象为单例,以及使用方式

var instance = null;
class Storage {
    static getInstance() {
        if (!instance) {
            instance = new Storage();
        }
        return instance;
    }
    setItem = (key, value) => (key, value);
    getItem = key => (key);
}

47、说说你对事件流的理解

  • 事件流分为两种:捕获事件流和冒泡事件流
  • 捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找到执行目标节点
  • 冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,知道查到根节点

48、说说从输入URL到看到页面发生的全过程

  • 首先浏览器主线程接管,开了一个下载线程
  • 然后进行HTTP请求(DNS查询、IP寻址等),中间会有三次握手,等待响应,开始下载,响应报文
  • 将下载完的内容转交给Renderer进程管理
  • Renderer进程开始解析css rule tree 和 dom tree,这两个过程是并行的,所以一般我们把link标签放在页面顶部
  • 解析绘制过程中,当浏览器遇到link标签或者script、img等标签,浏览器就会去下载这些内容,遇到适用缓存的使用缓存,不使用缓存的重新下载资源
  • css rule tee 和 dom tree 生成完了之后,开始合成render tree,这个时候浏览器会进行layout,开始计算每一个节点的位置,然后进行绘制
  • 绘制结束后,关闭TCP连接,过程又四次挥手

49、做一个Dialog组件,说说你的设计思路?它应该有什么功能?

  • 该组件需要提供 hook 指定渲染位置,默认渲染在body下面
  • 然后该组件可以指定外层样式,如宽度等
  • 组件外层还需要一层mask来遮住底层内容,点击mask可以执行传进来的onCancel函数关闭Dialog
  • 另外组件是可控的,需要传入visible来表示是否可见
  • 然后Dialog可以能要自定义头部和底部,默认有头部和底部,底部有一个确认按钮和取消按钮,按钮文字可以传入,按钮可以传入是否显示。确认按钮会执行外部传进来的onOk事件,取消按钮会执行外部传进来的onCancel事件
  • 当组件的visible为true的时候,设置body的overflow为hidden,隐藏body的滚动条,反之显示滚动条
  • 组件高度可能大于页面高度,组件内部需要滚动条
  • 只有当组件的visible有变化且为true时,才重新渲染组件内的所有内容

50、说说Ajax、fetch、axios之间的区别

  • Ajax请求
    • 本身是针对MVC的编程,不合符现在前端的MVVM浪潮
    • 基于原生的XHR开发,XHR本身的架构不清晰,已经有了fetch的替代方案
    • JQuery整个项目太大,单纯使用Ajax却要引入整个JQuery非常的不合理(采取个性打包的方案又不能享受CDN服务)
  • fetch请求
    • fetch只对网络请求报错,对400,500都当做是请求成功,需要封装去处理
    • fetch默认不会带cookie,需要添加配置项
    • fetch不支持abort,不支持超时控制,使用setTimeout及实现超时控制并不能阻止请求过程继续在后台运行,造成了资源的浪费
    • fetch没有办法监测请求的进度,而XHR可以
  • axios请求
    • 从浏览器中创建XMLHttpRequest
    • 从发出http请求
    • 支持 Promise
    • 支持拦截请求和响应
    • 支持转换请求和响应数据
    • 支持取消请求
    • 自动转换JSON数据
    • 客户端防止CSRF/XSRF

51、说说内存泄漏

  • 定义:程序中动态分配的堆内存由于某种原因程序未释放或无法释放引发的各种问题
  • JavaScript中可能出现内存泄漏的情况,结果:变慢、崩溃、延迟大等
  • JavaScript中可能出现内存泄漏的原因:
    • 全局变量
    • DOM清空时,还存在引用
    • IE中使用闭包
    • 定时器未清除
    • 子元素存在引起的内存泄漏

52、JavaScript自定义事件

  • () 创建事件模型
  • () 初始化事件
  • () 触发事件

53、JavaScript数组排序的几种方式?

  • 冒泡排序:每次比较相邻的两个数,如果后一个比前一个小,换位置
    var arr = [3,1,5,6,8,2,9,7,4];
    function bubbleSort(arr) {
        for (var i = 0; i < ; i++) {
            for (var j = 0; j <  - i - 1; j++) {
                if (arr[j + 1] < arr[j]) {
                    var temp;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
        return arr;
    }
    (bubbleSort(arr));

54、JavaScript数组一行代码去重方法?

  • Set方法去重
    var arr = [1, 1, 'true', 'true', true, true, 15, false, undefined, undefined, null];
     = function () {
        return [...new Set(this)];
    }
    ((arr));

55、JavaScript如何判断一个对象是否为数组?

// 方法一
function isArray(arg) {
    if (typeof arg === 'object') {
        return (arg) === '[object Array]';
    }
    return false;
}

// 方法二
function isArray2(arg) {
    return (arg);
}

56、script 的引入方式

  • html静态引入<script>
  • js 动态插入 <script>
  • <script defer>:异步加载,元素解析完成之后执行
  • <script async>: 异步加载,但执行时会阻塞元素渲染

57、什么是变量对象

  • 变量对象,是执行上下文中的一部分,可以抽象为一种数据作用域,其实也可以理解为就是一个简单的对象,它存储着该执行文件上下文中所有的变量和函数声明(不包含函数表达式)
  • 活动对象(AO):当变量对象所处的上下文为 active EC 是,成为活动对象

有错请指正,侵删

查看全部面试题