壮心未与年俱老,死去犹能作鬼雄。这篇文章主要讲述原生JavaScript灵魂拷问,你能全部答对吗?#yyds干货盘点#相关的知识,希望能为你提供帮助。
前言
javascript之运算符篇
16. 你知道 ||
与 &
&
的返回值规则吗?
- 短路效应:
& &
和||
都会发生短路
& &
只有在两个操作数都为true
时,条件判断的结果才为true
,如果操作数一为false
,不会判断操作数二。||
两个操作数只要有一个为true
,条件判断的结果就为true
,因此操作数一为true
时,不会判断操作数二。
- 返回值规则
||
和& &
首先会对操作数一执行条件判断,如果不是布尔值就先强制转换为布尔类型,然后再执行条件判断。- 对于
||
来说,如果条件判断结果为true
就返回第一个操作数的值,如果为false
就返回第二个操作数的值。 & &
则相反,如果条件判断结果为true
就返回第二个操作数的值,如果为false
就返回第一个操作数的值。||
和& &
返回它们其中一个操作数的值,而非条件判断的结果
(2& lt; 3)||(3& lt; 2)
返回值是多少?
1 + - + + + - + 1
结果是多少?+/-
号在 javaScript
通常有三种用途:- 普通加减法: 二元运算符
++/--
: 自增自减,一元运算符+/-
: 正负,一元运算符
1 + (- (+ (+ (+ (- (+ 1)))))) ----&
gt;
1 + 1 = 2
18. 你能准确的做出自增与自减的题目吗?
++/--
在前,先加/减后用++/--
在后,先用后加/减
y
的值为?var x = 1;
var y = x + ++x + 3 * (x = ++x + x + x++ + 1)
+ x++ + 3;
console.log(y);
咱们来解剖一下这个长的要死的表达式:
第一个x值为1
第二个自增先加后用,x = 2
将 ++x + x + x++ + 1 的运算结果赋值给x
++x 先加后用,x = 3
x = 3
x++ 先用后加,x = 3
++x + x + x++ + 1 = 3 + 3 + 3 + 1 = 10
x++的后加,x = 4
将表达式的值赋给x,x由4变为10x++ 先用后加,x = 10
此时所有的变量都已经求出
y = 1 + 2 + 3*10 + 10 + 3 = 46
x++,x最终值为11
19. == 与 === 的区别,Object.is() 与 === 区别
== 与 === 区别
===
是严格相等,要求数据类型和值都要相等;==
只需要值相等。==
会发生隐式类型转换,===
不会发生隐式类型转换。 Object.is() 与 === 区别:Object.is()
来进行相等判断时,一般情况下与===
相同,它处理了一些特殊的情况,比如+0
和-0
不再相等,两个NaN
是相等的。
new
运算符的有两种优先级吗?MDN
对 new
操作符的描述中,语法是:new constructor[([arguments])]
([arguments])
意味着可以缺省,会存在 new constructor(...args)
和 new constructor
两种模式,并且前者的优先级高于后者。更详细的优先级见下图:文章图片
这个知识点非常重要,只有区分开了
new
带参列表和不带参列表,才能准确并且透彻的理解下面这道经典面试题。function Foo()
getName = function() console.log(1);
;
return this;
Foo.getName = function() console.log(2);
;
Foo.prototype.getName = function() console.log(3);
;
var getName = function() console.log(4);
;
function getName() console.log(5) ;
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
21.(ES6+) 可选链接运算符(?.)和空值合并运算符(??)
说到运算符,提一下这两个最新的扩展符,开阔一下眼界。
可选链接运算符(?.)当使用
?.
时,若当前值为假值,会停止执行,返回 undefined
。在日常编程中,如果要读取对象内部的属性,往往需要判断该对象是否存在,例如读取
message.body.user.firstName
,安全的写法const firstName = (message
&
&
message.body
&
&
message.body.user
&
&
message.body.user.firstName)
但如果有了 (?.) ,上面代码就可以简化为
const firstName = message?.body?.user?.firstName
空值合并运算符(??)空值合并运算符
(??)
是一个逻辑操作符,当左侧的操作数为 null
或 undefined
时,才会返回右侧操作数结果,否则返回左侧结果?? 和 || 有点像,但是 || 判断的是假值,?? 是判断 null和 undefined
//真值
const a = 1 ?? 默认值
console.log(a)// 1//假值
const a =?? 默认值
console.log(a)// //null
const a = null ?? 默认值
console.log(a)// 默认值//undefined
const a = undefined ?? 默认值
console.log(a)// 默认值
JavaScript之字符串篇 22.String(1), new String(1),1 区别是什么?
在
JavaScript
中,我们有下面三种定义字符串的方式。var str = 1;
var str1 = String(1);
var str2 = new String(1);
上面三种方式定义出的
1
,是否存在区别?我们来通过 ===
运算符和 typeof
运算符测试一下。console.log(str === str1)// true
console.log(str1 === str2) // false
console.log(str === str2)// falseconsole.log(typeof(str))// string
console.log(typeof(str1)) // string
console.log(typeof(str2)) // object
打印一下,
new String(1)
的结构见下图:文章图片
可见
new String()
生成的字符串为对象类型, [[PrimitiveValue]]
存储字符串的原始值。23.字符串是基本类型,那为什么可以调用字符串方法那?
这是
JavaScript
中的设计。JavaScript
为了便于基本类型操作,提供了三个特殊的引用类型(包装类),即 Number,String,Boolean
,它们的 [[PrimitiveValue]]
属性存储它们的本身值。基本类型的方法与属性是"
借"
来的,去向对应包装类"
借"
来的。光说这些有可能有些难理解,咱们来举个例子:
var str = zcxiaobao
str2 = str.toUpperCase()
其实js引擎内部会这样处理:
var str = zcxiaobao
// 调用方法,创建String的一个实例
new String(str)
// 调用实例上的方法,并将值返回
str2 = new String(str).toUpperCase()
// 销毁实例
24.修改string.length大小能改变字符串长度吗?为什么?
先看一个示例:
var str = 123456;
str.length = 0;
console.log(str, str.length);
// 123456 6
很明显,修改
str.length
是无法做到修改字符串的长度的。str
为原始值,调用 length
相当借用 new String(str).length
,修改的是 new String(str)
的 length
,跟原始值 str
无关。扩展:修改new String()生成字符串的length会生效吗?为什么?如果将上面代码修改一下,
str
是由 new String
产生,修改 length
属性会生效吗?var str = new String(123456);
str.length = 0;
console.log(str, str.length);
// String "123456" 6
答案告诉我们,还是失败了。
new String()
生成的字符串是对象类型,为啥还不能使用 length
属性。那说明 length
属性,很有可能配置不可写,测试一下上述猜想:Object.getOwnPropertyDescriptor(str, length)
/*value: 6,
writable: false,
enumerable: false,
configurable: false*/
由控制台的打印可知:
new String()
生成的字符串的 length
属性不止是不可写,而且是不可枚举、不可配置的。25.字符串截取函数slice(),substring(),substr()的区别是什么?
基本使用
substring
: 方法返回一个字符串在开始索引到结束索引之间的一个子集,或从开始索引直到字符串的末尾的一个子集。str.substring(indexStart, [indexEnd])
注意事项:
substring
截取字符串中[indexStart, indexEnd)
处字符串- 如果任一参数大于
stringName.length
,则被当作stringName.length
- 如果 indexStart 大于 indexEnd,则 substring 的执行效果就像两个参数调换了一样。见下面的例子。
const str = "123456" console.log(str.substring(0,1) === str.substring(1,0)) // true
- substr: 方法返回一个字符串中从指定位置开始到指定字符数的字符。
str.substr(start, [length])
注意事项
- substr: 方法返回一个字符串中从指定位置开始到指定字符数的字符。
substr()
会从start
获取长度为length
字符(如果截取到字符串的末尾,则会停止截取)。- 如果
start
是正的并且大于或等于字符串的长度,则substr()
返回一个空字符串。 - 若
start
为负数,则将该值加上字符串长度后再进行计算(如果加上字符串的长度后还是负数,则从0
开始截取)。 - 如果
length
为0
或为负数,substr()
返回一个空字符串。如果length
省略,则将substr()
字符提取到字符串的末尾。
slice
: 方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串。str.slice(beginIndex[, endIndex])
注意事项
- 若
beginIndex
为负数,则将该值加上字符串长度后再进行计算(如果加上字符串的长度后还是负数,则从0
开始截取)。 - 如果
beginIndex
大于或等于字符串的长度,则slice
() 返回一个空字符串。 - 如果
endIndex
省略,则将slice()
字符提取到字符串的末尾。如果为负,它被视为strLength + endIndex
其中strLength
是字符串的长度。
方法 | 参数 | 描述 |
---|---|---|
slice | slice(begin, end) | 返回从begin位置开始到end位置的子串(不包含end) |
substring | substring(start, end) | 返回从start位置开始到end位置的子串(不包含end) |
substr | substr(start, length) | 返回从start位置开始length长度的子串 |
slice
和substring
参数都是起始索引和结束索引,但slice
参数可以是负数,substring
参数如果小于0
,则会被视为0
substr
与其他两个不同,两个参数为起始索引和要包含在生成的字符串中的字符的长度。 26.你真的理解透JSON.stringify
了吗?
基本使用
JSON.stringify
可以将数组或者对象转化成 JSON
字符串。JSON.parse
可以将 JSON
字符串转化为数组或对象。基于这两个方法,可以产生很多用处,例如:- 对象的深拷贝(存在缺点: 循环引用等)
localStorage
只能存取字符串格式的内容,因此存之前转换成JSON
字符串,取出来用时,在转化成数组或对象。 学透JSON.stringfy
JSON.stringify(value[, replacer [, space]])
参数:
value
: 将要序列化成 一个JSON
字符串的值。replacer
(可选):- 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
- 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的
JSON
字符串中; - 如果该参数为
null
或者未提供,则对象所有的属性都会被序列化。
space
:指定缩进用的空白字符串,用于美化输出
JavaScript之数组篇 27.那些数组方法会修改原数组吗?那些不会?
那么可以类比推理一下,push返回的是插入元素的新数组长度,pop返回删除的元素值。
29.
new Array()
与 Array.of()
的区别是什么?Array.of()
方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。这好像跟new Array
的功能极度接近,为什么ES6
添加了新方法那?
new Array(n)
,只有一个参数时,构造的并非只含 n
的数组,而是 1 * n
的数组,值全为 undefined
。具体看测试:Array.of(7);
// [7]
Array.of(1, 2, 3);
// [1, 2, 3]Array(7);
// [ , , , , , , ]
Array(1, 2, 3);
// [1, 2, 3]
30.includes 与 indexOf的区别是什么?
基本使用
indexOf
: 返回元素在array
中的位置,不存在就返回-1
includes
: 用来判断元素是否存在
includes
可以检测NaN
var arr = [NaN] console.log(arr.indexOf(NaN))// -1 console.log(arr.includes(NaN))// true
includes
返回值为true/false
,在判断数组中是否存在某元素时,includes
的可读性更好
splice()
删除元素的注意事项我来假设一个场景,大家就理解为什么会有此问题。
现在有这样一个数组
[1,-1,2,-1,-5]
,我想删除掉所有的负数,于是我就写出了下面的代码:const arr = [1,-1,-2,1,-5]
for(let i = 0;
i<
arr.length;
i++)
if (arr[i] <
0)
arr.splice(i, 1)console.log(arr) // [1, -2, 1]
上面的代码感觉没什么错误,为啥
-2
没被删除掉?我在
splice
执行后,打印一下当前 i
位置的元素值,大家应该就可以理解。for(let i = 0;
i<
arr.length;
i++)
if (arr[i] <
0)
arr.splice(i, 1)
console.log(a[i]) // -2 undefined
数组删除
-1
之后,当前的 i
值为 -2
,此次遍历结束,i++
,正好空过了 -2
元素。因此如果splice删除元素发生在数组中,一定要注意回调i的位置。32. 你会使用数组高阶函数吗
33. 你会手撕数组高阶函数吗?
34.你知道forEach如何跳出循环吗?
break return
break return
都无法中断 forEach
循环,我们来挨着验证一下:- break
var arr = [1,2,3,4] arr.forEach((val, key) => // Uncaught SyntaxError: Illegal break statement if (key > 1) breakconsole.log(val) )
上述代码直接发生报错,可见break
无法在forEach
中使用。
- return
var arr = [1,2,3,4] arr.forEach((val, key) => console.log(key = , key) if (key > 1) returnconsole.log(val) )
输出结果
key =0 1 key =1 2 key =2 key =3
虽然有两个val
未被打印,但循环还是进行了4
次,return
无法中断forEach
,只是类似于continue
的功能。
try catchtry catch
可以中断forEach
的,而且是唯一可以中断forEach
的方式。
try
监视代码块,在需要中断的地方抛出异常。具体见下面案例:var arr = [1,2,3,4]
try
arr.forEach(function (val, key)
console.log(key = , key)if (key >
1)
throw Error();
)
catch (e)
输出结果
key =0
key =1
key =2
官方推荐: every/some传送门: Array.prototype.forEach
除了抛出异常以外,没有办法中止或跳出
forEach()
循环。如果你需要中止或跳出循环,forEach()
方法不是应当使用的工具。可以使用
some
和 every
来替代 forEach
函数: every
在碰到 return false
的时候,中止循环。some
在碰到 return true
的时候,中止循环。具体使用见下面案例:var arr = [1,2,3,4]
arr.some((val, key) =>
console.log(key = , key);
if (key >
1)
return true)
35. 如何实现数组乱序
36. 如何实现数组去重
你知道多少种数组去重的方法吗?
使用双重
for
和 splice
function unique(arr)
for(var i=0;
i<
arr.length;
i++)
for(var j=i+1;
j<
arr.length;
j++)
if(arr[i]==arr[j])
//第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
// 删除后注意回调j
j--;
return arr;
使用
indexOf
或 includes
加新数组
//使用indexof
function unique(arr)
var uniqueArr = [];
// 新数组
for (let i = 0;
i <
arr.length;
i++)
if (uniqueArr.indexOf(arr[i]) === -1)
//indexof返回-1表示在新数组中不存在该元素
uniqueArr.push(arr[i])//是新数组里没有的元素就push入return uniqueArr;
// 使用includes
function unique(arr)
var uniqueArr = [];
for (let i = 0;
i <
arr.length;
i++)
//includes 检测数组是否有某个值
if (!uniqueArr.includes(arr[i]))
uniqueArr.push(arr[i])//return uniqueArr;
sort
排序后,使用快慢指针的思想
function unique(arr)
arr.sort((a, b) =>
a - b);
var slow = 1,
fast = 1;
while (fast <
arr.length)
if (arr[fast] != arr[fast - 1])
arr[slow ++] = arr[fast];
++ fast;
arr.length = slow;
return arr;
sort
方法用于从小到大排序(返回一个新数组),其参数中不带以上回调函数就会在两位数及以上时出现排序错误(如果省略,元素按照转换为的字符串的各个字符的 Unicode
位点进行排序。两位数会变为长度为二的字符串来计算)。ES6
提供的 Set
去重
function unique(arr)
const result = new Set(arr);
return [...result];
//使用扩展运算符将Set数据结构转为数组
Set
中的元素只会出现一次,即 Set
中的元素是唯一的。使用哈希表存储元素是否出现(
ES6
提供的 map
)
function unique(arr)
let map = new Map();
let uniqueArr = new Array();
// 数组用于返回结果
for (let i = 0;
i <
arr.length;
i++)
if(map.has(arr[i]))// 如果有该key值
map.set(arr[i], true);
else
map.set(arr[i], false);
// 如果没有该key值
uniqueArr.push(arr[i]);
return uniqueArr ;
map
对象保存键值对,与对象类似。但 map
的键可以是任意类型,对象的键只能是字符串类型。如果数组中只有数字也可以使用普通对象作为哈希表。
filter
配合 indexOf
function unique(arr)
return arr.filter(function (item, index, arr)
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
//不是那么就证明是重复项,就舍弃
return arr.indexOf(item) === index;
)
这里有可能存在疑问,我来举个例子:
const arr = [1,1,2,1,3]
arr.indexOf(arr[0]) === 0 // 1 的第一次出现
arr.indexOf(arr[1]) !== 1 // 说明前面曾经出现过1
reduce
配合 includes
function unique(arr)
let uniqueArr = arr.reduce((acc,cur)=>
if(!acc.includes(cur))
acc.push(cur);
return acc;
,[]) // []作为回调函数的第一个参数的初始值
return uniqueArr
37. 如何实现数组扁平化
38. 类数组如何转化为数组?
Array.prototype.slice.call()
const arrayLike =
0: 111,
1: 222,
length: 2console.log(Array.prototype.slice.call(arrayLike)) // ["1", "2"]
Array.from()
Array.from
是 ES6
新增的方法,它可以将类数组对象和可遍历(iterable)转变为真正的数组。const arrayLike =
0: 1,
1: 2,
length: 2console.log(Array.from(arrayLike)) // ["1", "2"]
(...)
展开运算符扩展运算符调用的是遍历器接口,如果一个对象没有部署此接口就无法完成转换。上面咱们自己写的普通类数组就无法使用...运算符。
const arrayLike =
0: 1,
1: 2,
length: 2// Uncaught TypeError: arrayLike is not iterable
console.log([...arrayLike]) // ["1", "2"]
如果部署了遍历器接口,例如
arguments
类数组,便可以使用扩展运算符。function fn()
console.log([...arguments])fn(1,2,3) // [1, 2, 3]
后语如果大家感觉此文对你有一些帮助,希望能点个赞,鼓励鼓励阿包,阿包会不断努力的。另外如果本文有问题,或者对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!
推荐阅读
- web安全day37(Linux脚本判断和循环,编写脚本实现内网主机存活批量检测)
- #yyds干货盘点#7个场景让你深入理解min/max - width/height
- 一文解析Apache Avro数据
- #星光计划2.0# openHarmony轻松连接华为云物联网平台
- 一文带你了解 TreeMap ,LinkedHashMap 的主要特点#yyds干货盘点#
- 操作系统实验报告一
- Java语言程序设计 上机实验6掌握Java的图形用户界面编程,掌握布局管理器和事件的响应方法
- 数据结构与算法第四次实验报告图
- #yyds干货盘点# 更高级别的抽象---函数式思想