JavaScript问题汇总(上)

1.执行程序['10','10','10','10','10'].map(parseInt)的结果是什么?

上面的代码的真正执行的是下边的这个

['10', '10', '10','10','10'].map((item, index) => {
	return parseInt(item, index)
})

parseInt(string, radix) 解析一个字符串并返回指定基数(这里的基数可以理解为进制)的十进制整数,radix 是 2-36 之间的整数,表示被解析字符串的基数。对于parseInt的参数radix而言,如果是0,不传,null,undefined均会被当做radix=10对待,radix小于2或者大于36的 parseInt 会返回NaN,如果 radix 以 “0x” 或 “0X” 开头,radix=16 。由于,这里的index在执行map循环过程中的值分别为:0,1,2,3,4,由此可以得出上面的代码执行完的结果是:[10,NaN,2,3,4]。

注意:在parseInt方法的实现中,对于被解析的字符串的首字符大于基数的,直接返回NaN。而非首字符大于基数的,将大于基数的那个字符和之后的字符一起去掉。

参考资料:mdn

2.防抖和节流是什么?

背景:在实际开发过程中,我们经常会用到resize,scroll,input,mousemove,drag事件等,但是我们可能会在这些事件的回调函数里执行一些复杂的操作。这个时候我们会发现由于事件触发的频率很高,而那些复杂的程序运行的又比较慢,这可能会造成浏览器某个页签的运存占用升高,从而导致界面卡顿。一提到界面卡顿,或者假死,我猜大家肯定是不想看到的。在这样的背景下,我们就需要想出一个可以降低回调函数执行频率的办法,毕竟我们不能控制用户让他不要连续操作。

因此,经过一定实践,我们有了防抖和节流的办法来解决此类问题。现实生活中类似的场景也有很多。

防抖场景:

  • 运动会赛跑的时候,发令员会倒计时10,9,8,7,6,5,4,3,2,1,但是这个过程中如果有个别运动员在发令员没有计数到1的时候就抢跑,那么发令员就会重新计数。
  • 电梯中的直梯我想大家都不陌生,假如你在等电梯,电梯来了并在你进入电梯后倒计时3,2,1秒后开始关电梯。突然,又来一些人,进入电梯,那么电梯肯定是在等大家都进入电梯后再次重新倒计时3,2,1才会关闭电梯门,这是符合常理的。

节流场景:

  • 公路上堵车,两个车道的车要汇入一个车道才能驶出高速。此时,交警的指挥也是有条不紊,一个车过完后等几秒才会让另一个车道的车汇入并驶出,整个过程也是通过降频来解决拥堵问题的。如果大家都不让行,那么交通肯定是要瘫痪的。

看了上面的场景,那么防抖和节流的大致理解是这样的:

防抖:触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间;

节流:一个连续触发的事件,为了降低执行频率,限制其在 n 秒中只执行一次回调函数的方式;

总的来看,两者的核心都是降低回调执行频率,区别在于回调方法执行的周期有所不同,防抖的执行周期是不确定的,完全取决于事件触发的频率。而节流的执行周期则是提前设置好的,到了那个时间点,回调一定会被执行。这里有极限的情况,当设置的时间足够小,那么两者从执行结果来看就几乎没有差别了。

3.Set、Map、WeakSet 和 WeakMap的区别是什么?

  • Set 是一种叫做集合的数据结构,类似于数组,但成员可以是任何类型的唯一的值,集合是无序的;
  • WeakSet与Set类似,也是不重复值的集合。但是它与Set的主要区别在于:WeakSet的成员只能是对象,且对象都是弱引用;
  • Map 是一种叫做字典的数据结构。存储数据的方式是 [key, value] 的形式,key可以是任何类型的数据;
  • WeakMap结构与Map结构类似,也是用于生成键值对的集合,但是它与Map的主要区别在于:WeakMap只接受对象作为键名,且对象都是弱引用

注意:弱引用指的是不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被垃圾回收器回收。

4.Js中的原型链是什么?

JS是一个面向对象的编程语言,它通过设置某个方法的prototype 属性,来指定这个方法的原型对象,而通过new 这个方法创建出来的对象实例都将继承那个特定方法原型对象上的属性,我们可以通过实例的__proto__属性来进行原型查找,直到找到某个特定的属性,而对于那个特定的方法的原型对象,它本质也是对象,他也会有__proto__属性,依此类推就构成了JS的原型链。原型链是一种继承的实现方式,对于JS来讲原型链的最顶端则是null类型

5.prototype 和 __proto__ 的区别?
prototype(显式原型):作用是给方法绑定原型对象,从而使通用的属性和方法可以被实例共享;
__proto__(隐式原型):作用是给原型链查找机制中某个目标(属性,方法)提供一个寻址方向;
JS中一切皆对象,而__proto__ 是所有对象都有的属性,普通对象的__proto__指向创建该对象实例的构造函数的原型对象,任何函数的__proto__指向Function.prototype

6.实现继承的常见方式?

  • 原型链继承:子类的原型对象指向父类实例;
  • 构造函数继承:子类的构造器中通过call,或者apply调用父类的构造器;

  • 组合继承:原型链继承 + 构造函数继承 (ES5中最常用的继承方式);

  • 寄生式继承:创建一个用于封装继承过程的函数,该函数在内部以某种方式来增强对象;

  • 寄生组合式继承:原型链继承 + 构造函数继承 + 寄生式继承;

  • 使用ES6中的Class实现继承

7.ES5/ES6 的继承除了写法以外还有什么区别?

ES6中子类可以直接通过 __proto__ 寻址到父类,而在ES5中通过__proto__找到的是Function.prototype

8.什么是暂时性死区(TDZ)?
ES6规定,在代码块内,如果存在使用let和const命令声明的变量,它所声明的变量就“绑定”了这个区域,不再受外部影响。在此声明之前,该变量都是不可用的。这在语法上称为“暂时性死区”,简称:“TDZ”。

参考资料:阮一峰ES6

9.ES6模块规范是什么?
ES6导入遵循的是静态导入,既“编译时加载”或者称为“静态加载”,编译时就可以确定模块的依赖关系,以及输入和输出的变量。因此,可以进行静态分析,并实现代码的静态优化,类似的工具有很多,比如:eslint。ES6的模块机制效率要高于Nodejs的CommonJS规范,后者采用的是“运行时加载”的方式,只能在运行时确定这些信息。
ES6导入模块通过“import”,导出模块“export或者export default”

10.前端模块化发展历程简介

模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程。主要优势是解决了命名冲突,依赖管理等问题。在发展过程中主要有以下几种实现方式

  1. IIFE: 使用立即执行函数来编写模块,特点:在一个单独的函数作用域中执行代码,避免变量冲突;
  2. AMD: 使用requireJS 来编写模块,特点:依赖必须提前声明好;
  3. CMD: 使用seaJS 来编写模块,特点:支持动态引入依赖文件;
  4. CommonJS: nodejs 中默认的模块化规范;
  5. UMD:兼容AMD,CommonJS 模块化语法
  6. ES Modules: ES6 的模块化规范

11.下面的代码输出什么?

var b = 10;
(function b(){
    b = 20;
    console.log(b); 
})();

这里是非严格模式,输出

function b(){
    b = 20;
    console.log(b); 
}

严格模式下,输出

Uncaught TypeError: Assignment to constant variable

分析:IIFE(立即执行函数表达式)的方法名只有能在函数内部可被访问到,并且方法名被声明成了一个常量,类似于ES6中通过const声明的函数。在非严格模式下,b = 20 这行代码执行的时候默认是失败的,但是不会报错。严格模式下,会直接报错。

12.下面代码输出什么?

var a = 10;
(function () {
    console.log(a)
    a = 5
    console.log(window.a)
    var a = 20;
    console.log(a)
})()

分析:代码第一行定义了全局变量 a,这个变量是挂载到window对象上边的。后面的代码是一个IIFE表达式,内部形成了一个独立的块级作用域。由于var a = 20;这行代码存在变量提升,实际上变量的声明var a会被提升到所在作用域的顶部,且还未被初始化,值还是undefined,因此第一个console.log(a)输出的值是undefined。而第二个打印语句访问的是window.a,因此输出10,第三个打印语句之前a被赋值为了20,因此输出20。

结果:undefined -> 10 -> 20

13.Array的sort方法怎么实现排序?

sort方法采用的是原地算法对数据的元素进行排序,并返回数组。sort方法有一个参数,参数类型是Function类型,可以用来改变sort的默认排序规则(*默认排序规则是将元素转换成字符串后,按每个字符的unicode位点进行排序)。假定比较函数是compareData(a,b),它有两个参数,a代表参与比较的前一个元素,b代表后一个元素

  • compareData(a,b)返回值等于0,那么a,b的位置保持不变;
  • compareData(a,b)返回值小于0,那么a会排到b之前;

14.分析下面的代码,写出输出的结果?

let obj = {
    length: 0,
    push: Array.prototype.push,
};
obj.push(0);
obj.push(0);
console.log(obj.length);

分析:push方法具有通用性,可用在类似数组的对象(ArrayLike)上,push方法根据length属性的值决定从哪里插入给定的值,如果length不能被转成一个数值,或者length不存在时,则新插入的元素索引为0,当length属性不存在时,将会创建它。

结果:2

15.call和apply方法的区别?

call 和 apply 方法相同点在于可以改变某个方法执行的上下文(方法的 this 指向),且第一个参数都是 context 。区别在于 call 方法除第一个参数外接受的是一个参数列表,而 apply 除第一个参数外接受的是一个包含多个参数的数组。其次,通过执行同一个执行单元,call 方法耗时要小于apply 方法,因此 call 执行效率要大于 apply 方法

16.分析下面的代码

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x) 	
console.log(b.x)

分析:前两行代码完成的事情就是在堆内存中创建对象{n:1}并且,变量 a,变量 b 的值为该对象的指针,第三行代码中涉及到一个知识点:运算符的优先级问题,“.” 的优先级要大于 “=” 赋值运算符。因此,先执行a.x,由于在a指向的对象中没有x属性,那么js引擎会给对象创建x属性,并给其绑定值 undefined。由于“=” 具有右结合性,随后执行 a = {n:2} , 此时a指向的指针变成了 {n:2} 的指针。由于之前赋值给 a 的那个对象仍然被 b 变量引用,因此执行完表达式后 b 的值为 {n:1,x:{n:2}} 。a的值是{n:2}

结果:undefined,{n:2}

17.谈谈对javascript作用域的理解?

作用域就是指变量和函数的可访问范围。ES6之前只有全局作用域(Global)和函数作用域(Local)。ES6引入了块作用域(Block)的概念,ES6中的作用域被分为:

  • 全局作用域(Global):通常Window对象代指全局作用域,全局作用域下通过var声明的变量都会挂载到window下边,而通过let和const声明的全局变量则会被放到Script作用域中

  • 根作用域(Script):运行那些被let和const声明的全局变量,Script作用域是为了区别普通的块级作用域而创造的全局块级作用域

  • 块级作用域(Block):块级作用域通常由一对花括号 “{}” 声明,但是对象的花括号不会创建块级作用域。通过let,const声明的变量只能在声明它们的那个块级作用域下被访问。

  • 函数作用域(Local):函数内部定义的变量,其作用范围被称为函数作用域

18.作用域链是什么?

通过作用域查找变量的链条称为作用域链。实际上,在每个执行上下文环境中都会存在一个外部引用outer,而outer正是作用域形成作用域链的关键。

19.什么是词法作用域?

由函数声明的位置层级决定的作用域被称为词法作用域,词法作用域是静态作用域,由它产生的作用域链称为词法作用域链,它可以用来确定代码查询标识符的范围和路线。

20.谈谈对闭包的理解?

在JS中,根据词法作用域规则,内部函数总是可以访问其外部函数中声明的变量,当通过一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们把这些变量的集合称为闭包(closure)。假如外部函数的名称为test,那么这些变量的集合就称为test函数的闭包。由于闭包会导致闭包内的变量或者方法不能被JS的垃圾回收机制回收,从而可能有内存泄漏的风险。因此在使用闭包的时候要结合场景适当使用。如果想要闭包一直可以被访问,那么就把他挂载到全局变量上,如果想要在某个时期内可以被访问,那么就把它挂载到局部变量上。

21.谈谈对this机制的理解?

this机制是为了实现在对象内部的方法中访问对象内部的属性。使用this,一般需要注意以下几点:在全局执行上下文中调用一个全局函数,函数内部的this指向的是全局变量window(如果是严格模式那么是undefined)。通过一个对象调用其内部的方法,该方法的执行上下文中的this指向对象本身。

一句话总结:this指向调用方法的那个对象

22.箭头函数的特点是什么?

  • 箭头函数中的this继承自父作用域中的this对象,而普通函数的this则指向调用这个方法的那个对象
  • 箭头函数中不存在arguments,如果想要实现类似arguments的效果,可以使用ES6提供的可变参数

  • 不可以使用 new 命令和 yield 命令

23.如何使用javascript模拟new的执行过程?

 function newFunc(constructor, ...args) {
            let _newObj = Object.create(null) //1.创建一个空对象
            _newObj.__proto__ = constructor.prototype; //2.把这个空对象的__proto__属性指向构造函数的prototype对象
            constructor.apply(_newObj, args); //3.调用构造器
            return _newObj; //4.返回创建的对象
 }

24.JS中的调用栈是什么?

栈是一种数据结构,遵从的原则是后入先出。当我们在JS代码中调用某个函数,JS引擎会为其创建执行上下文,并把该执行上下文压入到调用栈中,然后JS引擎开始执行函数代码。如果在一个函数A中调用了另外一个函数B,那么JS引擎会为B函数创建执行上下文,并将B函数的执行上下文压入栈顶。当前函数执行完成后,JS引擎会将该函数的执行上下文从调用栈中弹出。

25.谈谈对变量提升和函数提升的理解?

变量提升和函数提升,指的是JS代码在编译的过程中,JS引擎把变量的声明部分和函数的声明部分提升到执行上下文顶部的“行为”。而对于变量声明而言,JS引擎会给其一个默认值undefined。JS代码在执行过程中一般会经过两个阶段,如下:

编译阶段:一段JS代码经过编译后,会生成“执行上下文”和“可执行代码”,前者是JS代码的运行环境,它由两部分组成,分别是:

  • 变量环境:该环境中保存了变量提升后的内容,
  • 词法环境:用来跟踪标识符和特定变量之间的映射关系,及管理作用域。而可执行代码是JS代码经过JS引擎编译后生成的可执行代码,可执行代码中除了变量声明被存储在栈里,剩余的代码均会被编译为字节码并被存储起来。

执行阶段:JS引擎开始按顺序一行一行的执行由编译阶段生成的“可执行代码”,当碰到变量时,就在变量环境中查询变量对应的值。当碰到函数时,就在变量环境中查找该函数引用对应的函数并执行。

在实际开发过程中,也会碰到同名函数,同名的方法和变量这种情况,JS引擎在编译的时候碰到同名的变量声明和函数,那么会忽略掉变量声明,而保留函数声明。如果是同名函数,那么最后声明的函数会覆盖前边声明的函数。

26.<script>标签使用属性defer和async,及默认不使用前者这三种情况有什么区别?

  • 默认:解析到script标签后直接加载对应的js脚本内容,会阻塞Dom渲染;
  • async:异步执行脚本中的代码,异步脚本不能保证按照它们在页面中出现的次序执行,脚本中的代码具体执行时机由 JS 引擎决定,通常是脚本下载完就执行,会阻塞Dom渲染;
  • defer:异步执行脚本中的代码,对比async的好处在于其总会在Dom解析完成后(即document.readyState的值为 ' interactive ' ),DOMContentLoaded 事件触发之前执行脚本代码,不会阻塞Dom渲染;

27.下面的代码输出什么?

 async function async1() {
       console.log('A');
       await async2();
       console.log('B');
 }

 async function async2() {
       console.log('C');
 }

 console.log('D');

 setTimeout(() => {
      console.log('E');
 }, 0)

 async1();

 new Promise((resolve) => {
     console.log('F');
     resolve();
 }).then(() => {
     console.log('G');
 })
 console.log('H');

这个知识点用文字描述太过繁琐,主要考察的知识点也就是事件循环、任务队列、宏任务、微任务。我直接上图了,也帮助大家更加方便的记忆和理解。

 实际上,宏任务队列实际上可以有多个,而微任务队列只有一个。任务管理器会把同步任务优先压入执行栈中。碰到异步任务后,会判断其类型,如果是微任务就被扔到微任务队列中去排队,如果是宏任务就被扔到宏任务中去排队,这个过程就是为了分类。但最终都会在单次事件循环中优先将排好队的微任务队列中的任务依次扔到任务执行队列中,随后再将宏任务队列中的任务依次扔到任务执行队列中。由于Event Loop在不停的工作,当进入本次事件循环后,依次执行任务执行队列中的任务,并将其压入执行栈中,整个过程实际上是在一个事件循环中完成的。

28.JS阻塞问题有哪些?

在JS脚本中可能会存在操作DOM,修改CSS的操作,因此为了避免异常情况的产生,在JS引擎加载JS的时候会阻塞DOM解析,因此也会阻塞浏览器渲染。

但是有些时候,我们知道将要加载的JS文件中不存在操作DOM的行为,为了满足这种场景,W3C添加了defer和async这两个script标签属性。defer和async均可以使得脚本加载和DOM解析并行进行,但是defer要在DOM解析完成和DOMContentLoaded事件触发之前执行,而async则是在加载完js脚本之后就执行脚本。

优化方式:因此可以根据具体应用场景适当使用defer和async,从而达到优化效果。

29.使用cookie,sessionStorage,localStorage存储数据的区别?

  • cookie:可通过document.cookie来设置,键值使用“=”号拼接,不设置expires过期时间,那么当浏览器挂关闭时,数据会被清除;

  • sessionStorage:可通过它的setItem方法存储数据,当浏览器窗口关闭时,数据会被清除;

  • localStorage:可通过它的setItem存储数据,清除数据只能通过其removeItem或clear方法;

注意:浏览器刷新的时候,以上这三种数据存储方式都不会丢失数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值