2023前端面试题及答案整理(JavaScript)
Posted suli77
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023前端面试题及答案整理(JavaScript)相关的知识,希望对你有一定的参考价值。
JS类型
string,number,boolean,undefined,null,symbol(es6),BigInt(es10),object
值类型和引用类型的区别
两种类型的区别是:存储位置不同;
- 值类型存储在栈(stack)中,占空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用类型存储在堆(heap)中,占据空间大、大小不固定。如果存在栈中,影响程序运行性能;引用类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
JS的类型检测
- typeof (判断一个变量是什么类型)undefined object function boolean string number symbol
- instanceof (判断当前对象是不是某个类型)
<!-- 要检测的对象 instanceof 某个构造函数 --> function Car(make, model, year) this.make = make; this.model = model; var auto = new Car('Honda', 'Accord'); console.log(auto instanceof Car); // expected output: true console.log(auto instanceof Object); // expected output: true
- Object.prototype.toString.call()(检测一个对象的类型)
console.log(Object.prototype.toString.call("Lance"));//[object String]
=== 和 == 的区别
==
在允许强制转换的条件下检查值的等价性,而 ===
是在不允许强制转换的条件下检查值的等价性;
因此 ===
常被称为「严格等价」。(“55” == 55 true, “55” === 55 false。p.s. 把字符串转为数值)
哪些非 boolean 值被强制转换为一个 boolean 时,它是 false ?
""
(空字符串)0
,-0
,NaN
(非法的number
)null
,undefined
和 [] 的 valueOf 和 toString 的结果是什么?
的 valueOf 结果为 ,toString 的结果为 “[object Object]”
[] 的 valueOf 结果为 [] ,toString 的结果为 “”
null,undefined 的区别?
-
null 表示一个对象是「没有值」的值,也就是值为 “空”;
-
undefined 表示一个变量声明了没有初始化(赋值);
-
undefined 的类型(typeof)是 undefined ;
-
null 的类型(typeof)是 object ;
-
javascript 将未赋值的变量默认值设为undefined;
-
JavaScript 从来不会将变量设为 null 。它是用来让程序员表明某个用 var 声明的变量时没有值的。
在验证 null 时,一定要使用 === ,因为 == 无法分别 null 和 undefined
null == undefined // true
null === undefined // false
DOM
DOM 操作——怎样添加、移除、移动、复制、创建和查找节点?
(1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
(2)添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点
(3)查找
querySelector("ul") / querySelectorAll("ul li") // 查找单个元素 / 多个元素
getElementsByTagName("div")
getElementsByClassName()
getElementById()
DOM树操作
// 添加新节点
var p1 = document.createElement('p')
p1.innerhtml = 'this is p1'
div1.appendChild(p1) // 添加新创建的元素
// 移动已有节点。注意,这里是“移动”,并不是拷贝
var p2 = document.getElementById('p2')
div1.appendChild(p2)
// 获取父元素
var div1 = document.getElementById('div1')
var parent = div1.parentElement
// 获取子元素
var div1 = document.getElementById('div1')
var child = div1.childNodes
// 删除节点
var div1 = document.getElementById('div1')
var child = div1.childNodes
div1.removeChild(child[0])
offsetWidth/offsetHeight,clientWidth/clientHeight 与 scrollWidth/scrollHeight 的区别
- offsetWidth/offsetHeight 返回值包含 content + padding + border,效果与 e.getBoundingClientRect() 相同;
- clientWidth/clientHeight 返回值只包含 content + padding,如果有滚动条,也不包含滚动条;
- scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸。
什么是 Virtual DOM,为何要用 Virtual DOM?
Virtual DOM 的概念有很多解释,分别是:一个对象,两个前提,三个步骤。
一个对象指的是 Virtual DOM 是一个基本的 JavaScript 对象,也是整个 Virtual DOM 树的基本。
两个前提分别是 JavaScript 很快和直接操作 DOM 很慢,这是 Virtual DOM 得以实现的两个基本前提。
得益于 V8 引擎的出现,让 JavaScript 可以高效地运行,在性能上有了极大的提高。
直接操作 DOM 的低效和 JavaScript 的高效相对比,为 Virtual DOM 的产生提供了大前提。
三个步骤指的是 Virtual DOM 的三个重要步骤,分别是:生成 Virtual DOM 树、对比两棵树的差异、更新视图。
1.生成 Virtual DOM 树:
DOM 是前端工程师最常接触的内容之一,一个 DOM 节点包含了很多的内容,但是抽象出一个 DOM 节点却只需要三部分:节点类型,节点属性、子节点。所以围绕这三个部分,我们可以使用 JavaScript 简单地实现一棵 DOM 树,然后给节点实现渲染方法,就可以实现虚拟节点到真实 DOM 的转化。
2.对比两棵树的差异:
比较两棵 DOM 树的差异是 Virtual DOM 算法最核心的部分,这也是我们常说的的 Virtual DOM 的 diff 算法。在比较的过程中,我们只比较同级的节点,非同级的节点不在我们的比较范围内,这样既可以满足我们的需求,又可以简化算法实现。
比较“树”的差异,首先是要对树进行遍历,常用的有两种遍历算法,分别是深度优先遍历和广度优先遍历,一般的 diff 算法中都采用的是深度优先遍历。对新旧两棵树进行一次深度优先的遍历,这样每个节点都会有一个唯一的标记。在遍历的时候,每遍历到一个节点就把该节点和新的树的同一个位置的节点进行对比,如果有差异的话就记录到一个对象里面。
例如,上面的 div 和新的 div 有差异,当前的标记是 0,那么:patches[0] = [difference, difference, …]。同理 p 是 patches[1],ul 是 patches[3],以此类推。这样当遍历完整棵树的时候,就可以获得一个完整的差异对象。
在这个差异对象中记录了有改变的节点,每一个发生改变的内容也不尽相同,但也是有迹可循,常见的差异包括四种,分别是:
- 替换节点
- 增加/删除子节点
- 修改节点属性
- 改变文本内容
对象的原生方法
Object.assign()
copy 对象的可枚举属性
语法:Object.assign(target, …sources)
参数:目标对象, …源对象
返回值:目标对象
const obj = a: 1 ;
const copy = Object.assign(, obj);
console.log(copy); // a: 1
Object.create()
创建新对象
语法:Object.create(proto, [ propertiesObject ])
参数:新创建对象的原型对象, 用于指定创建对象的一些属性,(eg:是否可读、是否可写,是否可以枚举etc)
Object.is()
用来判断两个值是否是同一个值
Object.is('haorooms', 'haorooms'); // true
Object.is(window, window); // true
Object.is('foo', 'bar'); // false
Object.is([], []); // false
var test = a: 1 ;
Object.is(test, test); // true
Object.is(null, null); // true
// 特例
Object.is(0, -0); // false
Object.is(-0, -0); // true
Object.is(NaN, 0/0); // true
Object.keys / Object.values
返回给定对象的自身可枚举属性 / 值 的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致
// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']
// array like object
var obj = 0: 'a', 1: 'b', 2: 'c' ;
console.log(Object.keys(obj)); // console: ['0', '1', '2']
var obj = foo: 'bar', baz: 42 ;
console.log(Object.values(obj)); // ['bar', 42]
// array like object
var obj = 0: 'a', 1: 'b', 2: 'c' ;
console.log(Object.values(obj)); // ['a', 'b', 'c']
var obj = ['e', 's', '8']; // 等同于 0: 'e', 1: 's', 2: '8' ;
Object.values(obj); // ['e', 's', '8']
//当把数字当做对象的键的时候,返回的数组以键的值升序排序
var obj = 10: 'xxx', 1: 'yyy', 3: 'zzz' ;
Object.values(obj); // ['yyy', 'zzz', 'xxx']
Object.values('es8'); // ['e', 's', '8']
Object.entries()
Object.entries()
方法返回对象自身可枚举属性的键值对数组,其排列与使用 for...in
循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。
const obj = foo: 'bar', baz: 42 ;
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]
// iterate through key-value gracefully
const obj = a: 5, b: 7, c: 9 ;
for (const [key, value] of Object.entries(obj))
console.log(`$key $value`); // "a 5", "b 7", "c 9"
const obj2 = 10: 'xxx', 1: 'yyy', 3: 'zzz' ;
Object.entries(obj2); // [['1', 'yyy'], ['3', 'zzz'], ['10', 'xxx']]
Object.entries('es8'); // [['0', 'e'], ['1', 's'], ['2', '8']]
数组
数组的遍历方法
- 标准for循环
- forEach((当前值, 当前索引,当前数组)=>)
- 无法中途退出循环,只能用
return
退出本次回调,进行下一次回调。 - 它总是返回 undefined 值,即使你 return 了一个值。
- 无法中途退出循环,只能用
- for-in(不推荐)会把继承链的对象属性都会遍历一遍,而且数组遍历不一定按次序
- for-in 循环返回的是所有能通过对象访问的、可枚举的属性。
- for (variable of iterable)(ES6)可迭代 Array ,Map,Set,String 等(迭代的是值 value )
- 在
for-of
中如果遍历中途要退出,可以使用break
退出循环。
- 在
ES5
-
map (不改变原数组) 会给原数组中的每个元素都按顺序调用一次 callback 函数
-
reduce (不改变原数组) 数组中的前项和后项做某种计算,并累计最终值。
// arr.reduce(function(total, currentValue, currentIndex, arr), initialValue) // callback 参数 // (累积器, 当前元素, 当前元素索引, 当前数组) // initialValue:指定第一次回调 的第一个参数 var wallets = [4, 7.8, 3] var totalMoney = wallets.reduce(function (countedMoney, curMoney) return countedMoney + curMoney; , 0)
-
filter(不改变原数组)
var arr = [2, 3, 4, 5, 6] var morearr = arr.filter(function (number) return number > 3 ) // [4,5,6]
-
every(不改变原数组)测试数组的所有元素是否都通过了指定函数的测试
- 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
- 如果所有元素都满足条件,则返回 true 。
var arr = [1,2,3,4,5] var result = arr.every(function (item, index) return item > 2 ) // false
-
some(不改变原数组)测试是否至少有一个元素通过 callback 中的条件.对于放在空数组上的任何条件,此方法返回 false 。
- 如果有一个元素满足条件,则表达式返回 true , 剩余的元素不会再执行检测。
- 如果没有满足条件的元素,则返回 false 。
// some(callback, thisArg) // callback: // (当前元素, 当前索引, 调用some的数组) var arr = [1,2,3,4,5] var result = arr.some(function (item,index) return item > 3 ) // true
ES6
-
find() & findIndex() 根据条件找到数组成员
- find() 定义:用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回 undefined 。
- findIndex() 定义:返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回 -1 。
<!-- 语法 --> let new_array = arr.find(function(currentValue, index, arr), thisArg) let new_array = arr.findIndex(function(currentValue, index, arr), thisArg) <!-- 这两个方法都可以识别NaN,弥补了indexOf的不足 --> <!-- find --> let a = [1, 4, -5, 10].find((n) => n < 0); <!-- 返回元素-5 --> let b = [1, 4, -5, 10,NaN].find((n) => Object.is(NaN, n)); <!-- 返回元素NaN --> <!-- findIndex --> let a = [1, 4, -5, 10].findIndex((n) => n < 0); <!-- 返回索引2 --> let b = [1, 4, -5, 10,NaN].findIndex((n) => Object.is(NaN, n)); <!-- 返回索引4 -->
-
keys() & values() & entries() 遍历键名、遍历键值、遍历键名+键值
- 三个方法都返回一个新的 Array Iterator 对象,对象根据方法不同包含不同的值。
// 语法 array.keys() array.values() array.entries() for (let index of ['a', 'b'].keys()) console.log(index); // 0 // 1 for (let elem of ['a', 'b'].values()) console.log(elem); // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) console.log(index, elem); // 0 "a" // 1 "b"
for in 和 for of 区别
Object.prototype.objCustom = function() ;
Array.prototype.arrCustom = function() ;
let iterable = [3, 5, 7];
iterable.foo = 'hello';
for (let i in iterable) <-- 循环的是索引
console.log(i); // 打印 0, 1, 2, "foo", "arrCustom", "objCustom"
for (let i in iterable)
if (iterable.hasOwnProperty(i))
console.log(i); // 打印 0, 1, 2, "foo"
for (let i of iterable) <-- 迭代的是值
console.log(i); // 打印 3, 5, 7
JS数组有哪些方法
改变原数组的方法(9个)
splice() 添加 / 删除数组元素
splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目
array.splice(index,howmany,item1,…,itemX)
- index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
- howmany:可选。要删除的项目数量。如果设置为 0,则不会删除项目。
- item1, …, itemX: 可选。向数组添加的新项目。
返回值: 如果有元素被删除,返回包含被删除项目的新数组。
删除元素
// 从数组下标0开始,删除3个元素
let a = [1, 2, 3, 4, 5, 6, 7]
let item = a.splice(0, 3) // [1,2,3]
console.log(a) // [4,5,6,7]
// 从最后一个元素开始删除3个元素,因为最后一个元素,所以只删除了7
let item = a.splice(-1, 3) // [7]
删除并添加
// 从数组下标0开始,删除3个元素,并添加元素'添加'
let a = [1, 2, 3, 4, 5, 6, 7]
let item = a.splice(0,3,'添加') // [1,2,3]
console.log(a) // ['添加',4,5,6,7]
// 从数组最后第二个元素开始,删除3个元素,并添加两个元素'添加1'、'添加2'
let b = [1, 2, 3, 4, 5, 6, 7]
let item = b.splice(-2,3,'添加1','添加2') // [6,7]
console.log(b) // [1,2,3,4,5,'添加1','添加2']
不删除只添加
let a = [1, 2, 3, 4, 5, 6, 7]
let item = a.splice(0,0,'添加1','添加2') // [] 没有删除元素,返回空数组
console.log(a) // ['添加1','添加2',1,2,3,4,5,6,7]
let b = [1, 2, 2023前端面试题及答案整理(JS面试题)