如果你最近想要换工作或者巩固一下自己的前端知识基础,不妨和我一起参与到每日刷题的过程中来,如何?
第四天要刷的面试题如下:
-
叙述+操作符的计算规则
-
bigint类型的作用
-
对比扩展运算符和Object.assign
-
说说const的原理及其可修改性
-
如果尝试new一个箭头函数会怎么样
下面是我的一些理解:
1.1 与数字做加法隐式转换规则
测试程序如下:
const a = [undefined, null, 0, '3', 123n, Symbol(0), true, {}];
function testPrimitiveValue(_x){
_x.forEach(i=>{
try {console.log(2+i)} catch(e) {console.log(e)}
})
}
function testValueOf(_x){
_x.forEach(i=>{
try {console.log(2+{valueOf(){return i}})} catch(e) {console.log(e)}
})
}
function testToString(_x){
_x.forEach(i=>{
try {console.log(2+{toString(){return i}})} catch(e) {console.log(e)}
})
}
function testPropToString(_x){
_x.forEach(i=>{
try {console.log(2+{})} catch(e) {console.log(e)}
})
}
NaN
2
2
23
Cannot mix BigInt and other types, use explicit conversions
Cannot convert a Symbol value to a number
3
2[object Object]
*/
NaN
2
2
23
TypeError: Cannot mix BigInt and other types, use explicit conversions
TypeError: Cannot convert a Symbol value to a number
3
2[object Object]
*/
NaN
2
2
23
Cannot mix BigInt and other types, use explicit conversions
Cannot convert a Symbol value to a number
3
Cannot convert object to primitive value
*/
2[object Object]
2[object Object]
2[object Object]
2[object Object]
2[object Object]
2[object Object]
2[object Object]
2[object Object]
*/
结论
与数字做加法运算,会尝试将另外一个操作数转成number类型:
-
如果另外一个操作数是primitive value则:
-
undefined -> NaN
-
null -> 0
-
number -> 不用转换
-
string -> 优先级高于number不会转换(反而是另一个number操作数会被转成string类型的)
-
bigint -> 仍为bigint(而不是number),证明:
console.log(2n+{valueOf(){return 1n}}); // 3n
-
symbol -> 报错:Cannot convert a Symbol value to a number
-
boolean -> 0或者1
-
object -> ↓↓↓↓↓↓
-
如果另外一个操作数是object则检查valueOf方法的返回值:
-
undefined -> NaN
-
null -> 0
-
number -> 不用转换
-
string -> 优先级高于number不会转换(反而是另一个number操作数会被转成string类型的)
-
bigint -> 仍为bigint(而不是number),证明:
console.log(2n+{valueOf(){return 1n}}); // 3n
-
symbol -> 报错:Cannot convert a Symbol value to a number
-
boolean -> 0或者1
-
object -> ↓↓↓↓↓↓
-
如果另外一个操作数是object并且valueOf方法不存在则检查toString方法的返回值:
-
undefined -> NaN
-
null -> 0
-
number -> 不用转换
-
string -> 优先级高于number不会转换(反而是另一个number操作数会被转成string类型的)
-
bigint -> 仍为bigint(而不是number),证明:
console.log(2n+{valueOf(){return 1n}}); // 3n
-
symbol -> 报错:Cannot convert a Symbol value to a number
-
boolean -> 0或者1
-
object -> Cannot convert object to primitive value 可以看出来,toString方法的返回指检查和valueOf返回值的检查方法基本上一致,但是如果toString方法还是返回引用类型的话,那就是给机会不中用了!
-
如果另外一个操作数是object并且valueOf、toString方法都不存在 这种情况下返回值出奇的统一就是将2和Object.prototype.toString.call(x)的拼接值
总结一下: 如果另外一个操作数x是primitive value则x一定会向number类型尝试转换,记住每一种转换的结果即可; 如果x是引用类型的,则先查valueOf的存在性和其返回值,再查toString的存在性和返回值,最后由Object.prototype.toString.call(x)结束。
1.2 与字符串做加法隐式转换规则
测试程序如下:
const a = [undefined, null, 0, '3', 123n, Symbol(0), true, {}];
function testPrimitiveValue(_x){
_x.forEach(i=>{
try {console.log(""+{valueOf(){return i}})} catch(e) {console.log(e)}
})
}
function testValueOf(_x){
_x.forEach(i=>{
try {console.log(""+{valueOf(){return i}})} catch(e) {console.log(e)}
})
}
function testToString(_x){
_x.forEach(i=>{
try {console.log(""+{toString(){return i}})} catch(e) {console.log(e)}
})
}
function testPropToString(_x){
_x.forEach(i=>{
try {console.log(""+{})} catch(e) {console.log(e)}
})
}
undefined
null
0
3
123
Cannot convert a Symbol value to a string
true
[object Object]
*/
undefined
null
0
3
123
Cannot convert a Symbol value to a string
true
[object Object]
*/
undefined
null
0
3
123
Cannot convert a Symbol value to a string
true
Cannot convert object to primitive value
*/
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
*/
结论
与字符串做加法运算,会尝试将另外一个操作数转成string类型:
-
如果另外一个操作数是primitive value则:
-
undefined -> 'undefined'
-
null -> 'null'
-
number -> '0'
-
string -> 不用转变
-
bigint -> '123' n不见了
-
symbol -> Cannot convert a Symbol value to a string
-
boolean -> 'true'
-
object -> ↓↓↓↓↓↓
-
如果另外一个操作数是object则检查valueOf方法的返回值:
-
undefined -> 'undefined'
-
null -> 'null'
-
number -> '0'
-
string -> 不用转变
-
bigint -> '123' n不见了
-
symbol -> Cannot convert a Symbol value to a string
-
boolean -> 'true'
-
object -> ↓↓↓↓↓↓
-
如果另外一个操作数是object并且valueOf方法不存在则检查toString方法的返回值:
-
undefined -> 'undefined'
-
null -> 'null'
-
number -> '0'
-
string -> 不用转变
-
bigint -> '123' n不见了
-
symbol -> Cannot convert a Symbol value to a string
-
boolean -> 'true'
-
object -> Cannot convert object to primitive value 可以看出来,toString方法的返回值检查和valueOf返回值的检查方法基本上一致,但是如果toString方法还是返回引用类型的话,那就是给机会不中用了!
-
如果另外一个操作数是object并且valueOf、toString方法都不存在 这种情况下返回值出奇的统一就是将2和Object.prototype.toString.call(x)的拼接值
总结一下: 如果另外一个操作数x是primitive value则x一定会向string类型尝试转换,记住每一种转换的结果即可; 如果x是引用类型的,则先查valueOf的存在性和其返回值,再查toString的存在性和返回值,最后由Object.prototype.toString.call(x)结束。
1.3 非数字,字符串做加法隐式转换规则
测试程序如下:
console.log(true + true);
console.log(true + undefined);
console.log(true + null);
console.log(true + 123n);
console.log(true + Symbol(0));
console.log(true + {});
console.log(true + {valueOf(){return 1}});
console.log(true + {toString(){return 1}});
console.log(undefined + true);
console.log(undefined + undefined);
console.log(undefined + null);
console.log(undefined + 123n);
console.log(undefined + Symbol(0));
console.log(undefined + {});
console.log(undefined + {valueOf(){return 1}});
console.log(undefined + {toString(){return 1}});
console.log(null + true);
console.log(null + undefined);
console.log(null + null);
console.log(null + 123n);
console.log(null + Symbol(0));
console.log(null + {});
console.log(null + {valueOf(){return 1}});
console.log(null + {toString(){return 1}});
console.log({} + true);
console.log({} + undefined);
console.log({} + null);
console.log({} + 123n);
console.log({} + Symbol(0));
console.log({} + {});
console.log({} + {valueOf(){return 1}});
console.log({} + {toString(){return 1}});
完整的结论
-
bigint不能出现在一般的+运算中!
-
所有值作为+的操作数的时候都会先转成number或者string类型之后再使用转换值计算
-
symbol不能向number或者string隐式转换,所以symbol也不能参与+运算
-
如果操作数之一为string类型的,或者转化类型之后为string的,则应该按照【与字符串做加法隐式转换规则 】计算
-
如果操作数之一为number类型的,并且另一个操作数不为string或者转化类型之后的string,则应该按照【与数字做加法隐式转换规则】计算
-
如果两个操作数均不为number或者string,或者转换之后的类型军部为number或者string,则只有可能是null undefined boolean,它们都是先转成number类型的再计算
-
引用类型转成number或者string的方式为,依次检查: valueOf -> toString -> Object.prototype.toString.call(x)的返回值。
看到这,不给作者点个赞?不点赞可能记不住哟~
由于js遵循的是IEEE双精度浮点数标准,也就是使用64bit的二进制数去表示一个浮点数,其中1bit为符号位,11bit为指数位,剩下的52位为尾数。
这就造成了52bit最大只能表示绝对值为2^53 - 1
的数字,如果将符号位算进来,就是-(2^53 - 1) -> +(2^53 - 1)
,也就是-2^53+1 -> 2^53-1
。
这恰好对应了Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
的值。
也就是说超过这个范围,js就只能用浮点数代替了,就会产生舍入误差了。所以ECMAScript 2020才引入bigint
这个新的数据类型来表示这个范围之外的整数。
bigint的原理就是使用非固定长度的二进制数表示整数,因为其长度是不固定的,所以bigint的数据不能参与到和number类型的数据的运算过程中去。
-
相同点:
-
两者都是浅拷贝
-
在实现浅拷贝功能的时候,两者都是后面的值覆盖前面的值
-
都只会复制ownProperty,包括ownPropertyName和ownPropertySymbol,不涉及原型链上的属性
-
-
不同点:
-
浅拷贝只是扩展运算符的众多用途的一种
-
扩展运算符产生一个新的对象,而Object.assign是往目标对象上合并属性(不会产生新的对象)
-
-
此外:{...obj1, ...obj2}相当于Object.assign({}, obj1, obj2)
-
const原理:在变量名与内存地址之间建立不可变的绑定,当尝试改变变量名的内存地址的时候,由于不可变的绑定的存在会报错
-
可修改性:对于primitive value修改值会报错,对于引用值来说依然不可以改变这种绑定关系,但是可以对其属性尝试性的进行修改:但需要通过
Object.isFrozen
来判断这个值是否被冻结了,如果被冻结了,其属性依然是不可以修改的。
补充:Object三傻:freeze seal preventExtensions
先手写一个函数实现new函数的功能:
function myNew (constructor, ...rest) {
if(typeof constructor !== "function") throw new Error('constructor must be a function');
const obj = Object.create(constructor.prototype);
const rst = constructor.apply(obj, rest);
return (rst && (Object(rst) === rst)) ? rst : obj;
}
-
第二步中,箭头函数没有原型对象,所以
constructor.prototype
不存在,为undefined,Object.create只接受null或者object类型的,所以这里会报错,这是第一个问题; -
第三步中,apply内部会用到this,此时的this应该指向constructor,但是constructor是箭头函数没有this,所以this指向错误;