irpas技术客

「2022」打算跳槽涨薪,必问面试题及答案 -- JavaScript 篇_前端人

网络投稿 3783

Hi,我是前端人,今日与君共勉!

1、深浅拷贝的区别有哪些?

要说 js 的深浅拷贝,就不得不提 js 的两大数据类型:基本数据类型和引用类型。基本数据类型的变量名和值都存储在栈中,对于引用类型的变量名存储在栈中,而值存储在堆中。由于存储方式不同,所以导致了他们复制的时候方式不同。

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精准拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另外一个对象。

深拷贝是将一个对象从内存中完整的拷贝一份出来,从内存堆中存放一个新的对象。这是两个对象,所以修改其中一个,另外一个不会受影响。

深浅拷贝主要针对的是引用类型,简单数据类型不受影响。

相关笔试题

var person = { name:"前端人", hobby:['学习','敲代码','潜水'] } function copy(source){ var newObj = new Object() for(var i in source){ if(source.hasOwnProperty(i)){ newObj[i] = source[i] } } return newObj } var p1 = copy(person); p1.name = "Web Person" console.log(person.name) console.log(p1.name) p1.hobby = ["内卷"] console.info(person.hobby) console.info(p1.hobby) /*运行结果: 前端人 Web Person ["学习", "敲代码", "潜水"] ["内卷"] */ 2、js 数据类型有哪些?

js 数据类型一共有 8 种,分为两大类:基本类型和引用类型。

它们的数据类型分别为:

基本类型:string、number、boolean、null、undefined、symbol、bigint

引用类型:object

相关面试题

// 注意:其他类型与数值进行相加时,其他类型的转为 number 类型 console.log( true+1 ) // 2 console.log( undefined +1 ) // NaN console.log( null ) //object console.log( undefined ) // undefined 3、延迟加载 js 的方式有哪些?有什么区别呢?

共有 6 种方式,分别为:

asyncdeferjs 最后加载利用 setTimeout动态创建 DOM 的方式使用 jQuery 的 getScript 方法

它们的区别介绍:

1、async:为 <script>标签定义了 async 属性。async 和 html 解析是同步的,不是顺次执行 js 脚本,谁先加载完成先执行谁。

<script async type="text/javascript" src="demo1.js" ></script> <script async type="text/javascript" src="demo2.js" ></script>

2、defer 会等到 html 解析完成之后再执行 js 代码,如果有多个脚本时,会按照顺序依次执行脚本。

<script defer type="text/javascript" src="demo1.js" ></script>

3、js 最后加载

把 js 外部引入的文件放置在页面的底部,让 js 最后加载,从而加快页面加载速度。

4、利用 setTimeout

5、动态创建 DOM 的方式

var element = document.createElement("script"); element.src = "box.js"; document.body.appendChild(element);

这种方式通过操作动态加载 js 文件,不触发的时候不加载,减少页面文件大小,加快加载速度。

6、使用 jQuery 的 getScript 方法

$.getScript( "box.js",function(){//回调函数,成功获取文件后执行的函数 console.log("脚本加载完成") });

相关面试题:

<!doctype html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <script type="text/javascript" src="box.js"></script> </head> <body> <div id="box"></div> </body> </html> //box.js 代码如下 console.log( document.getElementById('box') ) // null

box.js 想正常获取元素 box ,并进行一系列操作应该如何延迟加载 js 文件呢?

4、你对作用域的认识有多少?

作用域通俗地讲,就是指一个变量的作用范围。分为全局作用域和函数作用域。

全局作用域

页面打开时被创建,页面关闭时被销毁。编写在 script 标签下的变量和函数,作用域为全局,页面的任意位置都可以访问有全局对象 window ,代表浏览器窗口,全局作用下的变量和函数作为 window 的属性和方法

函数作用域(局部)

函数是被调用时创建的,执行完毕之后销毁。函数每调用一次,变量和函数就会重新创建一次,它们之间是相互独立的在函数作用域内可以访问到全局变量或函数,但是在函数外无法访问函数作用域内的变量函数作用域内访问变量,会在自身作用域内寻找,若没有则会向上一级作用域内查找,一直到全局作用域。

函数在被调用的时候会先进行预编译:

全局作用域预编译:

创建上下文 GO 对象。找变量声明,将变量名作为 GO 对象的属性名,值为 undefined找函数式声明,将值赋予函数体

函数作用域预编译:

创建上下文 AO 对象将形参和实参作为 AO 对象的属性,赋值为 undefined实参和形参相统一在函数体内找函数声明,将值赋予函数体。

相关面试题:

<script type="text/javascript"> function fn(a,c){ console.log(a) var a = 12 console.log(a) console.log(c) function a(){ } if(false){ var d = 34 } console.log(d) console.log(b) var b = function(){} console.log(b) function c(){} console.log(c) } fn(1,2) </script> // 运行结果: /* function a(){} 12 function c(){} undefined undefined function (){} function c(){} */ 5、null 和 undefined 的区别。

null 和 undefined 两个都表示无的值。

作者设计 js 的时候,借鉴的 java 语言先设计的 null 。null 使用的时候会被隐式转化成 0,不容易发现错误。

console.log( number(null) ) //0

undefined 是为了填补 null 的坑。所以后来又新增了 undefined 。

console.log( number(undefined) ) //NaN 6、new 操作符具体做了什么? 创建了一个空对象。将空对象的原型指向于构造函数的原型。将空对象作为构造函数的上下文。对构造函数有返回值的处理判断。

实现 new 操作符的方法:

function create( fn,...args ){ var obj={} Object.setPrototypeOf( obj,fn.prototype ) var resault = fn.apply(obj,args) return (resault instanceof Object) ? result : obj } 7、为什么会有闭包?它解决了什么问题?

7.1、什么是闭包?

闭包就是函数嵌套函数,通过函数内的函数访问变量的规则,实现外部访问函数内的变量。

7.2、闭包的特点:

函数嵌套函数。函数内部可以引用函数外部的参数和变量。参数和变量不会被垃圾回收机制回收。

实例3:闭包解决问题

var liArr = document.getElementsByTagName('li') for(var i=0;i<liArr.length;i++){ (function(i){ liArr[i].onclick = function(){ console.log('点击元素',liArr[i]) } })(i) }

7.3、闭包优点:

保护变量安全,实现封装,防止变量声明冲突和全局污染。在内存当中维持一个变量,可以做缓存。匿名函数自执行函数可以减少内存消耗。

防抖和节流就是闭包的经典应用。

7.4、闭包缺点:

变量会驻留在内存中,造成内存损耗问题。解决办法:把闭包函数设置为 null 。内存泄漏 8、防抖和节流,你了解多少?

8.1、什么是防抖函数?

当持续触发事件,一定时间内没有再触发事件,事件处理函数才会执行一次,如果在设定的时间到来之前又触发了事件,就会重新计时。

防抖函数常见的实际应用:使用 echart 的时候,浏览器 resize 时,需要重新绘制图表大小,还有典型的输入框搜索应用。

8.2、节流函数是什么?

当持续触发事件的时候,保证一段时间内只调用一次事件处理函数,一段时间内,只允许做一件事情。

防抖和节流主要是用来限制触发频率较高的事件,再不影响效果的前提条件下,降低事件触发频率,减小浏览器或服务器的压力,提升用户体验效果。

9、数组去重有几种方法?

方法1: new set()

return Array.from(new Set(arr)) // 或 return [...new Set(arr)]

方法2:使用两次循环

for(var i=0,len=arr.length;i<len;i++){ for(var j=i+1,len=arr.length;j<len;j++){ if( arr[i]===arr[j] ){ arr.splice(i,1) j--; len-- } } } return arr

方法3:indexOf 实现

let arr1 = [] for(var i=0;i<arr.length;i++){ if( arr1.indexOf(arr[i]) === -1 ){ arr1.push(arr[i]) } } return arr1

方法4:includes 实现

let arr1 = [] for(var i=0;i<arr.length;i++){ if( !arr1.includes(arr[i]) ){ arr1.push(arr[i]) } } return arr1

方法5:filter 实现

array.indexOf(item,start) start 表示开始检索的位置。

return arr.filter(( item, index )=>{ return arr.indexOf( item, 0 ) == index }) 10、call、bind 和 apply 的区别

三者都是改变函数执行的上下文,即改变 this 指向。

它们之间的区别为:

call 和 apply 会立即执行,bind 返回的是一个函数,需调用后执行。第二参数是传入要执行的方法中的参数,call 和 bind 是独立传递参数,apply 是以数组传递参数的

使用场景: 1、需要改变某个函数的this指向时 2、当参数较少时可以使用call,参数较多可以使用apply以数组的方式传递 3、当需要重复调用时,可以使用bind新定义一个方法

11、js 判断变量是不是数组,你能写出几种方法?

方法1:isArray

var arr = [1,2,3] console.log(Array.isArray(arr))

方法2:instanceof

var arr = [1,2,3] console.log( arr instanceof Array ) console.log( arr instanceof Object )

该方法不够严谨。

方法3:prototype

console.log( Object.prototype.toString.call(arr).indexOf('Array')>-1 )

方法4:isPrototypeOf

console.log( Array.prototype.isPrototypeOf( arr ) )

方法5:constructor

console.log(arr.constructor.toString().indexOf('Array')>-1 ) 12、slice 是干嘛的? splice 是否会改变原数组?

slice 是用来截取字符串的,返回一个新数组,但不会影响原数组。

使用语法:

arr.slice( start , end )

截取 arr 数组,从 start 开始到 end 结束,第二个参数是可选参数,没有时从 start 开始截取到结尾。

如果 start 参数是负数时,就会从 arr.lengtn + start 开始截取到结束。

var arr = ['a','b','c','d','e'] console.log( arr.slice(-3) ) // ["c", "d", "e"] console.log(arr) //["a", "b", "c", "d", "e"]

splice 是一个更强大的方法,可以添加、删除、替换数组元素,返回的是被删除元素,它的操作会改变原数组。

使用语法:

splice( start, n, new )

从 start 开始,删除 n 个元素,然后把 new 添加到 start 元素之后。第三个参数为可选参数

n 为 0 且第三个参数不为空时,表示添加新元素到 start 之后。n 不为 0 且第三个参数不为空时,表示把 start 之后的 n 个元素替换成 new 。n 不为 0 且第三个参数为空时,表示删除 start 后的 n 个元素。 var arr = ['a','b','c','d','e'] var ar = arr.splice( 1, 1 ,'f','g') console.log('ar',ar) // ["b"] console.log('arr',arr) // ["a", "f", "g", "c", "d", "e"] 13、== 和 === 有什么不同?

== 比较的是值,=== 除了比较值,还比较类型。

console.log( [1,2]=='1,2' ) // true console.log( [1,2] === '1,2' ) //false

valueOf 方法返回 Math 对象的原始值,通常由 javascript 在后台自动调用,并不显示的出现在代码中。

console.log([1,2].valueOf()) //[1,2] console.log('1,2'.valueOf()) //[1,2] // 所以 console.log( [1,2]=='1,2' ) // true

不管是字符串和数字比较,还是布尔值和数字比较,都会使用 valueOf 隐式转换。

总结:== 需要使用 valueOf() 进行隐式转换,所以性能差。 === 会避开一些不必要的麻烦。

14、this 的指向

大厂笔试题:

var name = 'window name' var p1 = { name:'p1 name', showName:function(){ console.info(this.name) } } var fn = p1.showName fn() p1.showName() var p2 = { name:'p2 name', showName:function(fun){ fun() } } p2.showName(p1.showName) p2.showName = p1.showName p2.showName() /* 运行结果: window name p1 name window name p2 name */

这是一道关于 this 指向的面试题,接下来我们就说说 this 是如何指向的?

this 对象是运行时基于函数的执行环境绑定的:

在全局函数中,this 等于 window 。函数上下文调用,严格模式下 this 为 undefined ,非严格模式下,this 指向 window 。当函数被作为某个对象的方法被调用时,this 等于那个对象。如果使用 call apply 改变当前 this 时,将会指向为传递过来的那个 this 。匿名函数的执行环境具有全局性,因此 this 指向 window。构造函数内的 this 指向创建的实例对象。dom 事件处理函数,this 指向触发该事件的元素。setTimeout 和 setInterval 中的 this 指向全局变量 window 15、js 中的继承有哪些方式呢?

第 1 种:原型链继承

function Parent(){ this.name = "前端人" } Parent.prototype.showName = function(){ console.log(this.name) } function Child(){} //原型链继承 Child.prototype = new Parent() var p = new Child() console.dir(p.name) //前端人

特点:

实例的是子类的实例,也是父类的实例。父类新增原型方法和属性,子类都能访问到。简单,方便实现

第 2 种:借用构造函数

function Animal (name) { this.name = name || 'Animal'; this.sleep = function(){ console.log(this.name + '正在睡觉!'); } } Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); }; function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true

特点:

创建子类时,可以向父类传递参数。可以实现多继承,call 多个父类对象。解决方法1中,子类实例共享父类引用属性的问题。

还有组合式继承、ES6 的继承 和 寄生组合继承等等。每种继承方式都有各自的特点和缺点。

16、严格模式与非严格模式的区别,你了解多少?

JavaScript 语言是一门弱类型语言,存在许多类型错误,因此 ES6 引入了严格模式概念。

如果不加 ‘use strict’ 常规模式下就是属于非严格模式。

严格模式

在 js 文件顶部添加 ‘use strict’ 就属于严格模式,严格模式也可以指定在函数内部。

<script> 'use strict' //或者函数内部 (function(){ 'use strict' })() </script>

严格模式,是为 js 定义来了一种不同的解析与执行模型,在严格模式下,ECMAScipt 3 中一些不解和不确定的行为将得到处理,而且会对不安全的操作会抛出异常。‘use strict’ 会告诉浏览器引擎可以切换到严格模式执行。

严格模式与非严格模式区别

严格模式

非严格模式

变量必须声明才能赋值

变量不进行声明,可直接赋值

不能使用 delete 字符删除变量或对象

可以使用 delete 删除

函数参数变量名不允许重复

变量名重复,获取最后最后那个值

普通函数内的 this 为 undefined

普通函数内的 this 为 window

不允许使用八进制

允许任意进制

eval 和 arguments 当做关键字,不能被赋值和用作变量名

可以使用 eval 、arguments 作为变量名

call、apply 传入 null undefined 保持原样不被转为window

默认转为 window 对象

限制对调用栈的检测能力,访问 arguments.callee 会抛出异常

arguments.callee 运行正常

17、隐式转化相关面试题 console.log( '2'>10 ) //false console.log( '2'>'10' ) //true console.log( 'abc'>'b' ) //false console.log( 'abc'>'aab' ) //true console.log( undefined == null ) //true console.log( NaN == NaN )//false console.log( [] == 0 ) //true console.log( ![] == 0 ) //true console.log( [] == [] ) //false console.log( {} == {} ) //false console.log( {} == !{} ) //false 18、事件循环机制相关面试题。

阿里面试题1:

<script type="text/javascript"> var p =new Promise(resolve=>{ console.log(4) resolve(5) }) function f1(){ console.log(1) } function f2(){ setTimeout(()=>{ console.log(2) },0) f1() console.log(3) p.then(res=>{ console.log(res) }) } f2() </script> // 运行结果 4 1 3 5 2 // 如果已经了解事件运行机制,就可以跳过该问题了

事件循环机制,event-loop 。包含三部分:调用栈、消息队列、微任务队列。

事件循环开始的时候,会从全局一行一行的执行代码,遇到函数调用的时候,就会压入调用栈中,当函数执行完成之后,弹出调用栈。

// 如:代码会一行一行执行,函数全部调用完成之后清空调用栈 function f1(){ console.log(1) } function f2(){ f1() console.log(2) } f2() // 执行结果 1 2

如果遇到 fetch、setInterval、setTimeout 异步操作时,函数调用压入调用栈时,异步执行内容会被加入消息队列中,消息队列中的内容会等到调用栈清空之后才会执行。

// 如: function f1(){ console.log(1) } function f2(){ setTimeout(()=>{ console.log(2) },0) f1() console.log(3) } f2() // 执行结果 :1 3 2

遇到 promise、async、await 异步操作时,执行内容会被加入微任务队列中,会在调用栈清空之后立即执行。

调用栈加入的微任务队列会立即执行。

如 let p =new Promise(resolve=>{ console.log('立即执行') resolve(1) //在 then 调用中执行 })

微任务队列中内容优先执行,所以比消息队列中的内容执行得早。

了解这些知识后,再试一下最前面的那道面试题,应该就没什么问题了。

20、前端领域内,你比较擅长什么?

这个问题就留给读到最后,能够坚持学习的人,问问我们自己有什么是我们擅长的?在哪块领域是我们占据竞争优势的?

?


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #2022打算跳槽涨薪 #必问面试题及答案 #JavaScript # #js #的深浅拷贝就不得不提