js面试题

178 阅读9分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

1.谈谈对this的理解

this是一个关键字,是函数在运行时自动生成的一个内部对象,只能在函数内部使用。

  1. 给DOM元素进行事件绑定,当事件行为触发,绑定的方法执行,方法的this就是DOM元素本身
  2. 当方法执行,我们看函数前面是否有“点”
  • 有“点”,点前面就是this指向
  • 没有,this就是“window”,“undefined”,非严格模式和严格模式的区别。注意node中全局对象指向global。
  • 匿名函数,自执行函数,回调函数,this一般是window/undefined

图片.png

2.js内置对象

Object是js中所有对象的父对象。

数据封装类对象:Object,Array,Number,String,Bollen

其他对象:Function,Date,Math,RegExp,Error

3.闭包

在内层函数访问外层作用域。

使用场景:1.创建私有变量。2.延长变量的私有周期。

缺点:容易造成内存泄漏。内存泄漏是指不再用到的内存,没有及时的释放。

image.png

应用场景:

图片.png

4.垃圾回收

垃圾回收(GC):收回不使用的内存 方法:

  • 引用计数法:跟踪记录每个对象被引用的次数,被引用+1,引用结束-1,当引用次数为0时回收变量。但是当两个变量之间相互引用时,会造成内存泄漏。
  • 标记清除法:从根部出发,清除无法到达的对象。

5.作用域和作用域链

作用域分为全局作用域和局部作用域。

全局作用域:定义的变量和函数,可以在程序的任意地方访问地到。

局部作用域:分为块级作用域和函数作用域,定义的变量或者函数,都只能在它们内部访问。 PS:局部变量会被垃圾回收干掉,所以想延长变量的私有周期,可以使用闭包。

作用域链:寻找变量的过程。

6.JS事件执行顺序

顺序:捕获=》执行=》冒泡

image.png

true是捕获,false是冒泡,默认是false。

7.原型和原型链

原型:js也是一种基于原型的语言,每个对象拥有一个原型对象。

原型链:寻找公共属性的机制。

当访问一个对象的属性时,如果没有,还会搜索对象的原型及对象原型的原型,依次层层向上搜索,直到找到或者Object.prototype.proto=null.这就是原型链。

原型链的继承:父级函数的实例化对象绑定子级的原型对象。

8.new的过程

  1. 创建新对象
  2. 更改新对象的__proto__
  3. 更改this指向,并执行构造函数
  4. 返回这个新对象
function myNew(fn,...args) {
    const obj={}
    obj.__proto__=fn.prototype
    fn.apply(obj.args)
    return obj
}

9.更改this指向的三个方法

  • call

第一个参数是this指向,后面是实参且个数不受限制。可以调用函数。

  • apply

第一个参数是this指向,第二个是数组。可以调用函数。

  • bind

参数跟call一样,但是使用bind不会调用函数。有一个返回值,返回this的指向。

10.浅拷贝和深拷贝

JS有两大类型:基本类型和引用类型。

基本类型:

  • Boolean
  • Number
  • String
  • null
  • undefined
  • symbol

引用类型:

  • Object
  • Array
  • Function
  • Set

基本类型的数据保存在栈中;引用类型的数据保存在堆中,变量是一个指向堆内存的引用且存在栈中。

浅拷贝

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址。

也就是说,修改一个对象的属性,另一个也发生改变。

三种方式:

  1. 使用Object.assign(),将右边的键值加到左边。
  2. 使用forin循环添加。
  3. 直接赋值添加。
const obj = { name: '浙江', address: '中国' } 
const o = { age: 20 } 

// 第一种,把右边的键值对加到左边 
Object.assign(o, obj) 

// 第二种 
for (let key in obj) { 
    o[key] = obj[key] 
} 

// 第三种 
o.name = obj.name 
o.address = obj.address 

console.log(o);

深拷贝

当对象是引用类型时,开辟一个新栈,两个对象的属性完全相同,但是对应两个完全不同的地址。

修改一个对象的属性,另外一个不会发生改变。

方式:

  • 工作的项目里使用lodash的_cloneDeep()
  • JSON.stringfy()。注意不可以拷贝函数(引用类型Function),undefined,symbol。
  • jq里面的extend()
  • 通过递归来实现

image.png

const obj2=JSON.parse(JSON.stringify(obj1));

11.抛出异常

image.png

12.web常见的攻击方式

  1. SQL注入

恶意的将sql语句插入到表单中,让程序在后台解析。

  1. CSRF

跨站请求伪造:诱导用户登录一个虚假的第三方网站,获取用户的Cookie后,携带Cookie向第三方网站发送跨站请求。

  1. XSS

跨站脚本攻击:允许攻击者将恶意代码植入到提供给其他用户使用的页面中。

12.如何实现上拉加载

上拉加载本质是页面触底,或者快要触底的动作。

判断页面触底,要先了解几个属性:

  • scrolltop:滚动视窗距离window顶部的距离。
  • clientHeight:定值,表示屏幕可视区域的高度。
  • scrollHeight:页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示body所有元素的总长度(包括body元素自身的padding)。
let clientHeight  = document.documentElement.clientHeight; //浏览器高度
let scrollHeight = document.body.scrollHeight;
let scrollTop = document.documentElement.scrollTop;
 
let distance = 50;  //距离视窗还用50的时候,开始触发;

if ((scrollTop + clientHeight) >= (scrollHeight - distance)) {
    console.log("开始加载数据");
}
// 拉到底部后,刷新即可在控制台看到数据!

注意这里的scrollHeight与offsetHeight可以替换,也是能达到相同的效果

13.下拉刷新

页面本身置于顶部时,用户下拉时触发的动作。

三个阶段:

  • 当前手势滑动位置与初始位置的差值大于0的时候,提示正在进行下拉操作。((在scrollTop为0的时候进行判断)
  • 下拉到一定值时,提示松手后的操作。
  • 松手,执行更新回调。

14.大文件如何实现断点上传

拿到文件,保存文件的唯一标识,切割文件,逐一上传,同时根据唯一标识同步上传进度。直到文件全部上传完成。

15.防抖与节流

防抖:当用户频繁触发时,前面所有触发都取消,到达规定时间,执行最后一次触发

节流:当用户频繁触发时,不根据用户频繁程度来触发,而是根据设定好的频率来触发。可以触发多次

image.png 采用lodash防抖

image.png

image.png

16解决js数字精度丢失的问题

为什么0.1+0.2不等于0.3?

因为计算机存储浮点数时,要把十进制转化为二进制,由于存储位数有限(64位),所以某些数字在十进制转化为二进制时会出现精度丢失问题。

解决方案:使用toFixed,toPrecision 凑整并 parseFloat,第三方js库(例如bigInt)。

console.log(Number((0.1+0.2).toFixed(1))===0.3)// true

17事件循环

同步任务在执行栈执行完之后,会去异步队列查看是否有待执行的异步任务的回调函数,有就拿到执行栈执行,执行完之后又去异步队列查看。这个反复查看的过程就是事件循环(eventloop)

18.ES6新增了什么?

  1. 新增声明命令let,const

  2. 模板字符串:``

  3. 函数扩展

    • 默认参数
    • 箭头函数
  4. 对象扩展

    • 属性简写:对象的键值名相同,可以只写一个。
    • 方法简写:省略冒号和function关键字。
    • Object.keys()/Object.values():获取对象的所有键/值,返回一个数组。
    • Object.assign():浅拷贝一个对象。
  5. for of 循环

  6. import、export。模块化的导入导出

  7. Promise

  8. 解构赋值

  9. Set数据结构

  10. 展开运算符

image.png

所有数据是唯一的,都是值。且Set()本身是一个构造函数

19.caller和callee

caller 直接翻译为调用者,callee 翻译为被召者。

caller

返回一个对函数的引用,该函数调用了当前函数。

var a = function () {
  console.log(a.caller);
}
var b = function () {
  a();
}
b(); // [Function: b] 函数b里面调用了函数a
a(); // null

callee

var a = function () {
  console.log(arguments.callee);
  // [Function: a]
}
var b = function () {
  a();
}
b();

20.强制类型转换

  • 转化成字符串toString(),String()
  • 转化成数字Number(),parseInt(),parseFloat()
  • 转换成布尔类型Boolean()

21.typeof和instanceof的区别

typeof:可以准确的判断简单类型数据的类型,但是判断复杂数据类型时,除了Function都返回“object”。

instanceof就可以解决这个问题。

const a=function name() {}
const b=[1,2]
const c={}
console.log(typeof(a)) // function
console.log(typeof(b)) // object
console.log(typeof(c)) // object
console.log(b instanceof Array) // true
console.log(c instanceof Array) // false
console.log(c instanceof Object) // true

22.什么是回调函数?

函数A被当作实参传入函数B中,并在函数B中被调用,(用来完成某种任务),那么函数A被称为回调函数。

23.forin和forof的区别

ES5出来的forin循环主要是用来遍历对象:

const obj={name:'张三',gender:'male',age:30}
for(let key in obj){
  console.log(`这是key:${key}`)
  console.log(`这是value:${obj[key]}`)
}
/* 
这是key:name
这是value:张三
这是key:gender
这是value:male
这是key:age
这是value:30
*/

不用forin来遍历数组是因为会存在一些问题,比如索引index类型不是number而是string:

const arr=[1,2,3]
for(let index in arr){
  console.log(index+1)
}
/* 
01
11
21
*/

所以ES6出了forof循环,专门来解决forin不便于遍历数组的问题。举例:

const arr=[1,2,3]
for(let value of arr){
  console.log(value)
}
/* 
1
2
3
*/

24.split与join的区别

split拆开,join合并。括号里可以写操作目标符号。

const str='arrerr'

const newArr=str.split('')

console.log(newArr) // [ 'a', 'r', 'r', 'e', 'r', 'r' ]

  


const newStr=newArr.join('')

console.log(newStr) // arrerr

25.数组去重

indexof

/**
 * 数组去重:
 * 使用indexOf方法,查看当前元素第一次在数组中出现的下标,
 * 如果和当前元素的下标不同,那么就表明该元素有重复,就去掉当前元素。
 */

const arr=[1,2,3,4,4,6,7,6]
const newarr=[] // [ 1, 2, 3, 4, 6, 7 ]
for(let i=0;i<arr.length;i++){
    if(arr.indexOf(arr[i])===i) newarr.push(arr[i])
}
console.log(newarr)

// !相同思路,使用filter
// let newarr = arr.filter((item,index,self)=>{
//     return self.indexOf(item)==index
// })
// console.log(newarr)

Set

const arr = [1, 2, 3, 3, 5, 5]

console.log(new Set(arr)) // Set(4) { 1, 2, 3, 5 } 是set结构,非伪对象

console.log(Array.from(new Set(arr))) // [ 1, 2, 3, 5 ]

26终止/跳过for循环的三种方式

  1. return
  2. continue
  3. trycatch(这个也是唯一终止foreach的方式)
for(let i=0;i<100;i++){

  // return 循环终止

  // if(i==10)return

  


  // 循环跳过10

  if(i==10){

    continue

  }

  console.log(i)

}

  


// 使用trycatch,这个也是唯一终止foreach的方式