JavaScript中10个让人困惑的特性

更新日期: 2025-11-14 阅读: 35 标签: 特性

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")); // false

2. 数组长度可以手动修改

数组的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
}

实用建议

  1. 总是使用=== 避免类型转换的意外

  2. 理解变量提升 把声明放在使用之前

  3. 小心小数运算 必要时转成整数计算

  4. 使用现代语法 箭头函数、let/const等

  5. 代码检查 使用ESLint发现潜在问题

// 好的实践
const calculatePrice = (price, quantity) => {
  // 转成整数避免小数问题
  const total = Math.round(price * 100) * quantity;
  return total / 100;
};

// 明确的类型检查
if (typeof value === 'number' && !Number.isNaN(value)) {
  // 安全操作
}

JavaScript的这些特性虽然有时候让人困惑,但理解之后就能更好地避免问题。希望这些例子能帮助你在开发中少踩一些坑!

本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/13179

CSS新特性contain,控制页面的重绘与重排

contain 属性允许我们指定特定的 DOM 元素和它的子元素,让它们能够独立于整个 DOM 树结构之外。目的是能够让浏览器有能力只对部分元素进行重绘、重排,而不必每次都针对整个页面。

Html5、Css3、ES6的新特性

Html5的新特性语义化标签:有利于SEO,有助于爬虫抓取更多的有效信息,爬虫是依赖于标签来确定上下文和各个关键字的权重。表单新特性,多媒体视频(video)和音频(audio)

ES6新特性--var、let、const

var不存在块级作用域,具有变量提升机制。 let和const存在块级作用域,不存在变量提升。在同一作用域内只能声明一次。const在声明时需要赋值且无法修改,但如果常量是对象,则对象的属性可以修改。

Js即将到来的3个新特性

Optional Chaining(可选链式调用);Nullish coalescing(空值合并);Pipeline operator(管道运算符)通过三个函数对字符串进行处理;

Angular 8的新特性介绍

在今天早些时候Angular团队发布了8.0.0稳定版。其实早在NgConf 2019大会上,演讲者就已经提及了从工具到差分加载的许多内容以及更多令人敬畏的功能。下面是我对8.0.0一些新功能的简单介绍,希望可以帮助大家快速了解新版本

使用 React 要懂的 Js特性

与我使用的其他框架相比,我最喜欢 React 的原因之一就是它对 JavaScript 的暴露程度。没有模板DSL( JSX 编译为合理的 JavaScript),组件 API 只是通过添加 React Hooks 变得更简单,并且该框架为解决的核心 UI 问题提供非常少的抽象概念

ES2019 新特性汇总

最近 ECMAScript2019,最新提案完成:tc39 Finished Proposals,我这里也是按照官方介绍的顺序进行整理,如有疑问,可以查看官方介绍啦~另外之前也整理了 《ES6/ES7/ES8/ES9系列》,可以一起看哈。

Js的用途和特性

JavaScript 最初的目的是为了“赋予网页生命”。这种编程语言我们称之为脚本。它们可以写在 HTML 中,在页面加载的时候会自动执行。脚本作为纯文本存在和执行。它们不需要特殊的准备或编译即可运行。

十个超级实用的 JS 特性

你可能刚上手 JavaScript,或者只是曾经偶尔用过。不管怎样,JavaScript 改变了很多,有些特性非常值得一用。 这篇文章介绍了一些特性,在我看来,一个严肃的 JavaScript 开发者每天都多多少少会用到这些特性

解密HTTP/2与HTTP/3 的新特性

HTTP/2 相比于 HTTP/1.1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何优雅降级应该是国内还不普遍使用的原因之一。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!