JS面试真题 part4
JS面试真题 part4
- 16、谈谈JavaScript中的类型转换机制
- 标准回答:
- 17、深拷贝浅拷贝的区别?如何实现深拷贝
- 标准回答:
- 18、JavaScript中如何实现函数缓存?函数缓存有哪些应用场景?
- 标准回答:
- 19、JavaScript字符串的常用方法有哪些
- 标准回答:
- 20、数组的常用方法有哪些
- 标准回答:
16、谈谈JavaScript中的类型转换机制
自己回答:
基础类型里:
- 数字和字符串==比较,字符串转为数字比较。加号+运算,数字转字符串
- 布尔和数字比较,布尔转成数字
- 引用类型,转成valueof比较
标准回答:
常见的类型转换有:
- 强制转换(显示转换)
- 自动转换(隐式转换)
强制转换(显示转换):我们很清楚看见发生类型转换,常见的方法有:
- Number()
- parseInt()
- String()
- Boolean()
自动转换(隐式转换):
- 比较运算符(
==
、!=
、>
、<
)、if
、while
需要布尔值的地方 - 算木运算(
+
、-
、*
、/
、%
)
1、自动转换为布尔值
在需要布尔值的地方,将非布尔值转为布尔值,比如if,while
- undefined
- null
- false
- +0
- -0
- NaN
- “”
除了上面几种会被转成false
,其他转成true
2、自动转换为字符串
遇到预期为字符串的地方,就会将非字符串的值自动转为字符串
常发生在+运算中,一旦存在字符串,就会进行字符串拼接操作
'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"
3、自动转换成数值
除了+
有可能转为字符串,其他运算符转成数值
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN
null转为数值时,值为0,undefined转为数值时,值为NaN
17、深拷贝浅拷贝的区别?如何实现深拷贝
自己回答:
基础数据存在栈里,引用类型存在堆里,引用类型的地址存在栈里,深拷贝和浅拷贝是对于引用类型来说,深拷贝是将堆里的数据拷贝一份,浅拷贝是拷贝引用类型存在栈里的地址,新旧地址指向同一个堆数据
实现:
最常用 JSON.parse(JSON.stringify(obj))
或者遍历循环赋值
标准回答:
基本类型数据保存在栈内存中,引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中。
浅拷贝: 创建新数据,这个数据有原始数据属性值的一份精确拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址。即浅拷贝是拷贝一层
,深层次的引用类型则共享内存地址
实现浅拷贝:
function shallowClone(obj) {
const newObj = {};
for(let prop in obj) {
if(obj.hasOwnProperty(prop)){
newObj[prop] = obj[prop];
}
}
return newObj;
}
在javaScript中,存在浅拷贝的现象有:
- Object.assign
- Array.prototype.slice(),Array.prototype.concat()
- 使用拓展运算符实现的复制
深拷贝:
深拷贝开辟一个新的栈,两个对象属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝对象有:
- _.cloneDeep()
- jQuery.extend()
- JSON.stringify()
- 手写循环递归
区别:
浅拷贝和深拷贝都是创建一个新的对象,但是在复制对象属性的时候,行为就不一样,浅拷贝只复制属性指向某个对象的指针,而不是复制对象本身,新旧对象还是共享同一颗内存,修改对象属性会影响原对象
const obj1 = {
name : 'init',
arr : [1,[2,3],4],
arr2: [1,[2,3],4],
};
const obj3=shallowClone(obj1) //
obj3.name = "update";
obj3.arr[1] = [5,6,7] ; //
console.log('obj1',obj1) // obj1 { name: 'init', arr: [ 1, [ 5, 6, 7 ],4 ], arr2: [ 1, [3,4,2],4 ] }
console.log('obj3',obj3) // obj3 { name: 'update', arr: [ 1, [ 5, 6, 7 ],4 ], arr2: [ 1, [3,4,2],4 ] }
const obj2=obj1
obj2.name = "222";
obj2.arr2[1] = [3,4,2] ; // obj2 { name: '222',, arr: [ 1, [ 5, 6, 7 ],4 ], arr2: [ 1, [3,4,2],4 ] }
console.log('obj2',obj2)
赋值的话不管改变的是基础类型还是引用类型,新数据的改变都会影响原数据,浅拷贝拷贝了一层,基础数据不会变
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
// 深拷贝
const obj1 = {
name : 'init',
arr : [1,[2,3],4],
};
const obj4=deepClone(obj1) //
obj4.name = "update";
obj4.arr[1] = [5,6,7] ; //
console.log('obj1',obj1) // obj1 { name: 'init', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: 'update', arr: [ 1, [ 5, 6, 7 ],
4 ] }
小结:
拷贝类型是引用类型的情况下:
- 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
18、JavaScript中如何实现函数缓存?函数缓存有哪些应用场景?
自己回答:
函数缓存?
标准回答:
函数缓存,就是函数运算过的结果进行缓存
本质就是用空间(缓存存储)换时间(计算过程)
常用于缓存数据计算结果和缓存对象
const add = (a,b) => a+b;
const calc = memoize(add); // 函数缓存
calc(10,20);// 30
calc(10,20);// 30 缓存
缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理
如何实现
实现函数缓存主要依靠闭包、柯里化、高阶函数,这里再简单复习下:
闭包: 函数+函数体内可访问的变量总和
(function() {
var a = 1;
function add() {
const b = 2
let sum = b + a
console.log(sum); // 3
}
add()
})()
add
函数本身,以及其内部可访问的变量,即 a=1
,这两个组合在一起就形成了闭包
柯里化:
把接受多个参数的函数转换成接受一个单一参数的函数
// 非函数柯里化
var add = function (x,y) {
return x+y;
}
add(3,4) //7
// 函数柯里化
var add2 = function (x) {
//** **
return function (y) {
return x+y;
}
}
add2(3)(4) //7
将一个二元函数拆分成两个一元函数
高阶函数:
通过接收其他函数作为参数或返回其他函数的函数
function foo(){
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz();//2
函数foo
返回另一个函数bar,baz持有对foo中定义的bar函数的引用,由于闭包特性,a的值能够得到。
开始实现函数函数。原理:把参数和对应的结果数据存在一个对象中,调用时判断参数对应的数据是否存在,存在就返回对应结果,否则就返回计算结果
const memoize = function (func, content) {
let cache = Object.create(null)
content = content || this
return (...key) => {
if (!cache[key]) {
cache[key] = func.apply(content, key)
}
return cache[key]
}
}
const calc = memoize(add);
const num1 = calc(100,200)
const num2 = calc(100,200) //缓存得到的结果
过程分析:
- 在当前函数作用域定义了一个空对象,用于缓存运行结果
- 运用柯里化返回一个函数,返回的函数由于闭包特性,可以访问到
cache
- 然后判断输入参数是不是在
cache
中。如果已经存在,直接返回cache
的内容,如果没有存在,使用函数func
对输入参数求值,然后把结果存储在cache
中
应用场景:
虽然使用缓存效率非常高,但并不是所有场景都适用
以下几种情况,适合使用缓存:
- 对于昂贵的函数调用,执行复杂计算的函数
- 对于具有有限且高度重复输入范围的函数
- 对于具有重复输入值的递归函数
- 对于纯函数,既每次使用特定输入调用时返回相同的函数
19、JavaScript字符串的常用方法有哪些
自己回答:string:chart、splitte、slite、
标准回答:
1、增
- concat()
2、删
- slice()
- substr()
- substring()
这三个都返回一个子字符串,都接收一或两个参数
let stringValue = "hello world";
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.slice(3, 7)); // "lo w"
console.log(stringValue.substring(3,7)); // "lo w"
console.log(stringValue.substr(3, 7)); // "lo worl"
3、改
- trimLeft()、trimRight() 、trim() 删除前、后、前后空格符,返回新字符串
- repeat() 重复几次
- padStart()、padEnd() 指定字符串长度,如果小于指定长度,第二个参数可以指定填充的字符
- toLowerCase()、toUpperCase() 大小写转化
4、查
- chatAt() 返回给定索引位置的字符
- indexOf() 返回传入字符的位置,没找到返回-1
- startWith() 是否以传入字符串为开头,返回布尔值
- includes() 是否含有传入发字符串,返回布尔值
5、转化(字符转数组)
- split
let str = "12+23+34"
let arr = str.split("+") // [12,23,34]
6、模板匹配方法
- match() 匹配
- search() 查找匹配的索引
- replace()、replaceAll()、匹配替换
match:接收一个参数,可以是正则表达字符串,也可以是RegExp对象(提供正则表达式),返回匹配的数组
let text = "cat, bat, sat, fat";
let pattern = /.at/;
let matches = text.match(pattern);
console.log(matches[0]); // "cat"
search:接收一个参数,可以是正则表达字符串,也可以是RegExp对象(提供正则表达式),返回匹配索引
let text = "ca3t, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 7
replace、replaceAll:接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素
let text = "cat, bat, sat, fat";
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"
let resultall = text.replaceAll("at", "ond");
console.log(resultall); // "cond, bond, sond, fond"
20、数组的常用方法有哪些
自己回答:foreach、map、find、inclued、fitter、concat、push、pop、shift、unshift、join、flat、
标准回答:
分为
- 操作方法:
- 增:push、unshift、splice(要删除的位置设为0,为新增)、concat(不会改变原数组)
- 删:pop、shift、splice、slice(不会改变原数组)
- 改:splice
- 查:indexOf、includes、find
- 排序方法
- reverse() 反转数组
- sort() 排序
- 转换方法
- join() 数组转字符串
- 迭代方法
- some() 对数组每一项都运行传入测试函数,至少有个一个元素返回true,则方法返回true
- every() 对数组每一项都运行传入测试函数,如果所有元素都返回true,则方法返回true
- forEach() 对数组每一项都运行传入的函数,没有返回值
- filter() 对数组每一项都运行传入的函数,函数返回true的项组成数组之后返回
- map() 对数组每一项都运行传入的函数,返回每次函数调用的结果构成的数组