JavaScript中10个让人困惑的特性
JavaScript是一门有趣的语言,但有些特性确实让人摸不着头脑。即使是有经验的开发者,也可能会被这些特性"坑"到。今天我们来聊聊JavaScript中10个比较特别的现象。
1. NaN:自己不等于自己
NaN表示"不是一个数字",但它有个很奇怪的特点:
console.log(Nan === NaN); // false
console.log(Nan == NaN); // false为什么会出现这种情况?因为NaN代表一个无法表示的数值结果,比如0除以0。既然两个"未定义的结果"不能保证相同,它们就不应该相等。
检测NaN的正确方法:
console.log(Number.isNaN(NaN)); // true
// 不要用全局的isNaN
console.log(isNaN("hello")); // true,因为它会先把"hello"转成数字
console.log(Number.isNaN("hello")); // false2. 数组长度可以手动修改
数组的length属性不只是用来查看的,还可以修改:
let fruits = ['苹果', '香蕉', '橙子'];
// 增加长度会创建空位
fruits.length = 5;
console.log(fruits); // ['苹果', '香蕉', '橙子', empty × 2]
// 减少长度会删除元素
fruits.length = 1;
console.log(fruits); // ['苹果'] - 其他水果被删掉了空位和undefined不一样:
let sparse = new Array(3); // [empty, empty, empty]
let explicit = [undefined, undefined, undefined];
console.log(sparse.map(() => '填充')); // 还是 [empty, empty, empty]
console.log(explicit.map(() => '填充')); // ['填充', '填充', '填充']3. 看起来像数组但不是数组
有些对象看起来像数组,但不能用数组的方法:
function test() {
console.log(Array.isArray(arguments)); // false
// arguments.map(...) // 会报错
}转换方法:
function betterTest() {
const args = [...arguments]; // 方法一
// const args = Array.from(arguments); // 方法二
return args.map(x => x * 2);
}4. 小数计算不精确
这是计算机浮点数的通病,不是JavaScript独有的:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false解决方法:
// 对于大整数,用BigInt
const bigNum = BigInt(Number.MAX_SAFE_INTEGER);
// 对于小数,可以转成整数计算
function addDecimals(a, b) {
const multiplier = 100; // 根据精度需要调整
return (a * multiplier + b * multiplier) / multiplier;
}
// 或者用toFixed显示
console.log((0.1 + 0.2).toFixed(2)); // "0.30"5. 对象的冻结是浅层的
Object.freeze()只能冻结第一层属性:
const frozenObj = Object.freeze({
name: '不可变',
details: { version: 1 }
});
// 不能修改第一层
// frozenObj.name = '新名字' // 报错
// 但可以修改内层对象
frozenObj.details.version = 2; // 这样可以如果需要深度冻结,需要自己实现:
function deepFreeze(obj) {
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach(prop => {
if (obj[prop] !== null &&
typeof obj[prop] === 'object' &&
!Object.isFrozen(obj[prop])) {
deepFreeze(obj[prop]);
}
});
return obj;
}6. 原型污染风险
如果不小心修改了Object.prototype,会影响所有对象:
// 危险操作示例
const badData = JSON.parse('{"__proto__": {"isHacked": true}}');
let target = {};
// 某些库可能会这样合并对象
if (badData.__proto__) {
Object.assign(Object.prototype, badData.__proto__);
}
const cleanObj = {};
console.log(cleanObj.isHacked); // true - 所有对象都被影响了防护措施:
// 创建没有原型的对象
const safeObj = Object.create(null);
// 对输入数据做检查
function sanitizeInput(data) {
if (data && data.__proto__) {
throw new Error('危险数据');
}
return data;
}7. 类型转换的陷阱
+运算符遇到对象时会进行类型转换:
console.log([] + {}); // "[object Object]"
// 相当于 "" + "[object Object]"
console.log({} + []); // 0 或 "[object Object]",取决于环境建议做法:
// 总是用===
console.log(0 == '0'); // true
console.log(0 === '0'); // false
// 显式转换
const num = Number('123');
const str = String(123);8. 变量提升和暂时性死区
不同类型的变量提升方式不同:
// 函数声明 - 完全提升
sayHi(); // "你好!"
function sayHi() { console.log("你好!"); }
// var - 声明提升,值是undefined
console.log(myVar); // undefined
var myVar = "值";
// let/const - 有暂时性死区
console.log(myLet); // 报错
let myLet = "值";9. this的指向问题
this的值取决于调用方式:
const obj = {
name: "测试对象",
oldMethod: function() {
setTimeout(function() {
console.log(this.name); // undefined
}, 100);
},
newMethod: function() {
setTimeout(() => {
console.log(this.name); // "测试对象"
}, 100);
}
};在类中使用箭头函数:
class Button {
constructor(text) {
this.text = text;
}
// 自动绑定this
handleClick = () => {
console.log(this.text);
};
}
const btn = new Button('点击我');
document.getElementById('btn').addEventListener('click', btn.handleClick);10. Symbol的用途
Symbol创建唯一的值,适合做"私有"属性:
const secretKey = Symbol('secret');
class SafeBox {
constructor() {
this[secretKey] = '机密数据';
}
getSecret() {
return this[secretKey];
}
}
const box = new SafeBox();
console.log(box.getSecret()); // "机密数据"
// 这些方式访问不到secretKey
for (let key in box) console.log(key); // 没有输出
console.log(JSON.stringify(box)); // "{}"还可以自定义对象行为:
const counter = {
value: 0,
[Symbol.iterator]() {
return {
next: () => {
return {
value: this.value++,
done: this.value > 5
};
}
};
}
};
for (let num of counter) {
console.log(num); // 0, 1, 2, 3, 4
}实用建议
// 好的实践
const calculatePrice = (price, quantity) => {
// 转成整数避免小数问题
const total = Math.round(price * 100) * quantity;
return total / 100;
};
// 明确的类型检查
if (typeof value === 'number' && !Number.isNaN(value)) {
// 安全操作
}JavaScript的这些特性虽然有时候让人困惑,但理解之后就能更好地避免问题。希望这些例子能帮助你在开发中少踩一些坑!
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!