JS面试真题 part4

16、谈谈JavaScript中的类型转换机制

自己回答:

基础类型里:

  • 数字和字符串==比较,字符串转为数字比较。加号+运算,数字转字符串
  • 布尔和数字比较,布尔转成数字
  • 引用类型,转成valueof比较

标准回答:

常见的类型转换有:

  • 强制转换(显示转换)
  • 自动转换(隐式转换)

强制转换(显示转换):我们很清楚看见发生类型转换,常见的方法有:

  • Number()
  • parseInt()
  • String()
  • Boolean()

自动转换(隐式转换):

  • 比较运算符(==!=><)、ifwhile需要布尔值的地方
  • 算木运算(+-*/%

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() 对数组每一项都运行传入的函数,返回每次函数调用的结果构成的数组
  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值