每日五道前端面试题--day4
第四天要刷的面试题如下:
- 叙述+操作符的计算规则
- bigint类型的作用
- 对比扩展运算符和Object.assign
- 说说const的原理及其可修改性
- 如果尝试new一个箭头函数会怎么样
下面是我的一些理解:
1. 叙述+操作符的计算规则
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)}
})
}
// testPrimitiveValue(a);
/**
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]
*/
// testValueOf(a);
/**
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]
*/
// testToString(a);
/**
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
*/
// testPropToString(a);
/**
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)}
})
}
// testPrimitiveValue(a);
/**
undefined
null
0
3
123
Cannot convert a Symbol value to a string
true
[object Object]
*/
// testValueOf(a);
/**
undefined
null
0
3
123
Cannot convert a Symbol value to a string
true
[object Object]
*/
// testToString(a);
/**
undefined
null
0
3
123
Cannot convert a Symbol value to a string
true
Cannot convert object to primitive value
*/
// testPropToString(a);
/**
[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); // 2
console.log(true + undefined); // NaN
console.log(true + null); // 1
console.log(true + 123n); // Cannot mix BigInt and other types, use explicit conversions
console.log(true + Symbol(0)); // Cannot convert a Symbol value to a number
console.log(true + {}); // 'true[object Object]'
console.log(true + {valueOf(){return 1}}); // 2
console.log(true + {toString(){return 1}}); // 2
console.log(undefined + true); // NaN
console.log(undefined + undefined); // NaN
console.log(undefined + null); // NaN
console.log(undefined + 123n); // Cannot mix BigInt and other types, use explicit conversions
console.log(undefined + Symbol(0)); // Cannot convert a Symbol value to a number
console.log(undefined + {}); // 'undefined[object Object]'
console.log(undefined + {valueOf(){return 1}}); // NaN
console.log(undefined + {toString(){return 1}}); // NaN
console.log(null + true); // 1
console.log(null + undefined); // NaN
console.log(null + null); // 0
console.log(null + 123n); // Cannot mix BigInt and other types, use explicit conversions
console.log(null + Symbol(0)); // Cannot convert a Symbol value to a number
console.log(null + {}); // 'null[object Object]'
console.log(null + {valueOf(){return 1}}); // 1
console.log(null + {toString(){return 1}}); // 1
console.log({} + true); // [object Object]true
console.log({} + undefined); // [object Object]undefined
console.log({} + null); // [object Object]null
console.log({} + 123n); // [object Object]123
console.log({} + Symbol(0)); // Cannot convert a Symbol value to a number
console.log({} + {}); // [object Object][object Object]
console.log({} + {valueOf(){return 1}}); // [object Object]1
console.log({} + {toString(){return 1}}); // [object Object]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)的返回值。
看到这,不给作者点个赞?不点赞可能记不住哟~
2. bigint类型的作用
由于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类型的数据的运算过程中去。
3. 对比扩展运算符和Object.assign
- 相同点:
- 两者都是浅拷贝
- 在实现浅拷贝功能的时候,两者都是后面的值覆盖前面的值
- 都只会复制ownProperty,包括ownPropertyName和ownPropertySymbol,不涉及原型链上的属性
- 不同点:
- 浅拷贝只是扩展运算符的众多用途的一种
- 扩展运算符产生一个新的对象,而Object.assign是往目标对象上合并属性(不会产生新的对象)
- 此外:{...obj1, ...obj2}相当于Object.assign({}, obj1, obj2)
4. 说说const的原理及其可修改性
- const原理:在变量名与内存地址之间建立不可变的绑定,当尝试改变变量名的内存地址的时候,由于不可变的绑定的存在会报错
- 可修改性:对于primitive value修改值会报错,对于引用值来说依然不可以改变这种绑定关系,但是可以对其属性尝试性的进行修改:但需要通过
Object.isFrozen
来判断这个值是否被冻结了,如果被冻结了,其属性依然是不可以修改的。
补充:Object三傻:freeze seal preventExtensions
5. 如果尝试new一个箭头函数会怎么样
先手写一个函数实现new函数的功能:
function myNew (constructor, ...rest) {
// 1. 判断类型
if(typeof constructor !== "function") throw new Error('constructor must be a function');
// 2. 创建一个新对象其原型为构造函数的原型
const obj = Object.create(constructor.prototype);
// 3. 执行构造函数
const rst = constructor.apply(obj, rest);
// 4. 判断并返回
return (rst && (Object(rst) === rst)) ? rst : obj;
}
- 第二步中,箭头函数没有原型对象,所以
constructor.prototype
不存在,为undefined,Object.create只接受null或者object类型的,所以这里会报错,这是第一个问题; - 第三步中,apply内部会用到this,此时的this应该指向constructor,但是constructor是箭头函数没有this,所以this指向错误;
转载自:https://juejin.cn/post/7283435542158508051