跳转到内容

JS相关知识

JavaScript阻止事件冒泡的方法

在 JavaScript 中,事件冒泡是指事件从触发元素向上传播到其祖先元素的过程。阻止事件冒泡可以防止事件传播到父元素或其他祖先元素。以下是阻止事件冒泡的几种方法:

1. 使用 event.stopPropagation()

  • 作用

    • 阻止事件继续向上冒泡。
document.getElementById('child').addEventListener('click', function(event) {
  console.log('Child clicked');
  event.stopPropagation(); // 阻止事件冒泡
});
document.getElementById('parent').addEventListener('click', function() {
  console.log('Parent clicked'); // 不会执行
});

2. 使用 event.stopImmediatePropagation()

  • 作用

    • 阻止事件冒泡,并阻止同一元素上的其他事件监听器执行。
document.getElementById('child').addEventListener('click', function(event) {
  console.log('First listener');
  event.stopImmediatePropagation(); // 阻止冒泡和其他监听器
});
document.getElementById('child').addEventListener('click', function() {
  console.log('Second listener'); // 不会执行
});
document.getElementById('parent').addEventListener('click', function() {
  console.log('Parent clicked'); // 不会执行
});

3. 使用 return false(仅限于 jQuery)

  • 作用

    • 在 jQuery 中,return false 会同时阻止事件冒泡和默认行为。
$('#child').on('click', function() {
  console.log('Child clicked');
  return false; // 阻止冒泡和默认行为
});
$('#parent').on('click', function() {
  console.log('Parent clicked'); // 不会执行
});

4. 使用事件委托和条件判断

  • 作用

    • 通过事件委托和条件判断,间接阻止事件冒泡。
document.getElementById('parent').addEventListener('click', function(event) {
  if (event.target.id === 'child') {
    console.log('Child clicked');
    return; // 阻止父元素处理事件
  }
  console.log('Parent clicked');
});

5. 阻止默认行为和冒泡

  • 作用

    • 同时阻止事件冒泡和默认行为。
document.getElementById('child').addEventListener('click', function(event) {
  console.log('Child clicked');
  event.preventDefault(); // 阻止默认行为
  event.stopPropagation(); // 阻止事件冒泡
});
document.getElementById('parent').addEventListener('click', function() {
  console.log('Parent clicked'); // 不会执行
});

总结

方法作用适用场景
event.stopPropagation()阻止事件冒泡通用
event.stopImmediatePropagation()阻止事件冒泡和其他监听器需要阻止同一元素上的其他监听器时
return false阻止事件冒泡和默认行为(仅限 jQuery)使用 jQuery 时
事件委托和条件判断间接阻止事件冒泡需要灵活控制事件处理时
event.preventDefault() + event.stopPropagation()阻止默认行为和事件冒泡需要同时阻止默认行为和冒泡时

根据具体需求选择合适的方法,可以有效控制事件的传播和行为。

JavaScript阻止默认事件

在 JavaScript 中,阻止默认事件是指阻止浏览器对某些事件的默认行为。例如,阻止表单提交、阻止链接跳转、阻止右键菜单等。以下是阻止默认事件的几种方法:

1. 使用 event.preventDefault()

  • 作用

    • 阻止事件的默认行为。
document.getElementById('myLink').addEventListener('click', function(event) {
  event.preventDefault(); // 阻止链接跳转
  console.log('Link clicked, but no navigation');
});

2. 使用 return false(仅限于 jQuery)

  • 作用

    • 在 jQuery 中,return false 会同时阻止事件冒泡和默认行为。
$('#myLink').on('click', function() {
  console.log('Link clicked, but no navigation');
  return false; // 阻止默认行为和事件冒泡
});

3. 在 HTML 中直接使用 onEvent 属性

  • 作用

    • 在 HTML 元素上直接使用 onEvent 属性,并通过 return false 阻止默认行为。
<a href="https://example.com" onclick="return false;">Click me</a>

4. 阻止表单提交

  • 作用

    • 阻止表单的默认提交行为。
document.getElementById('myForm').addEventListener('submit', function(event) {
  event.preventDefault(); // 阻止表单提交
  console.log('Form submitted, but no page reload');
});

5. 阻止右键菜单

  • 作用

    • 阻止右键点击时显示默认的上下文菜单。
document.addEventListener('contextmenu', function(event) {
  event.preventDefault(); // 阻止右键菜单
  console.log('Right-click disabled');
});

6. 阻止文本选择

  • 作用

    • 阻止用户选择文本。
document.addEventListener('selectstart', function(event) {
  event.preventDefault(); // 阻止文本选择
  console.log('Text selection disabled');
});

7. 阻止拖拽

  • 作用

    • 阻止元素的默认拖拽行为。
document.getElementById('myImage').addEventListener('dragstart', function(event) {
  event.preventDefault(); // 阻止图片拖拽
  console.log('Image drag disabled');
});

总结

方法作用适用场景
event.preventDefault()阻止事件的默认行为通用
return false阻止默认行为和事件冒泡(仅限 jQuery)使用 jQuery 时
HTML onEvent 属性直接在 HTML 中阻止默认行为简单场景
阻止表单提交阻止表单的默认提交行为表单处理
阻止右键菜单阻止右键点击时的上下文菜单自定义右键菜单
阻止文本选择阻止用户选择文本保护内容
阻止拖拽阻止元素的默认拖拽行为防止资源被拖拽

根据具体需求选择合适的方法,可以有效控制事件的默认行为。

JavaScript事件委托

事件委托(Event Delegation) 是一种利用事件冒泡机制,将事件处理程序绑定到父元素而非子元素的技术。它可以显著提高性能,并简化动态内容的事件管理。

1. 事件委托的原理

  • 事件冒泡:当子元素触发事件时,事件会从子元素向上冒泡到父元素。
  • 事件捕获:事件从父元素向下捕获到子元素(较少使用)。
  • 事件委托:通过在父元素上监听事件,利用事件冒泡机制处理子元素的事件。

2. 事件委托的优势

  1. 减少事件处理程序:只需在父元素上绑定一个事件处理程序,而不是为每个子元素绑定。
  2. 动态内容支持:新增的子元素无需重新绑定事件。
  3. 性能优化:减少内存占用,提升页面性能。

3. 事件委托的实现

(1) 基本实现

html
<ul id="parent">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
javascript
document.getElementById('parent').addEventListener('click', function(event) {
  if (event.target.tagName === 'LI') {
    console.log('点击了:', event.target.textContent);
  }
});

(2) 动态添加子元素

javascript
const parent = document.getElementById('parent');
const newItem = document.createElement('li');
newItem.textContent = 'Item 4';
parent.appendChild(newItem);

// 无需重新绑定事件,点击新元素仍会触发

4. 事件委托的注意事项

  1. 事件目标:使用 event.target 获取实际触发事件的元素。
  2. 事件冒泡:确保事件能够冒泡到父元素(某些事件如 focusblur 不会冒泡)。
  3. 性能优化:避免在父元素上绑定过多事件处理程序。

5. 事件委托的适用场景

  1. 列表或表格:如点击列表项或表格行。
  2. 动态内容:如动态添加的按钮或链接。
  3. 性能敏感场景:如大型表格或列表。

总结

事件委托是一种高效的事件处理技术,通过利用事件冒泡机制,将事件处理程序绑定到父元素,从而减少事件处理程序数量,支持动态内容,并提升性能。

JavaScript中void(0)作用

在 JavaScript 中,void(0) 是一个特殊操作符,其核心作用是 执行表达式并始终返回 undefined。以下是它的常见用途及详细解释:

1. 阻止默认行为

在 HTML 的 <a> 标签中,href="javascript:void(0)" 用于阻止点击链接时的默认跳转行为。

示例:

html
<a href="javascript:void(0)" onclick="alert('Clicked!')">点击不跳转</a>
  • 效果:点击链接时会触发 onclick 事件,但不会跳转页面。

2. 返回 undefined

void 操作符会执行其后的表达式,但最终返回 undefined。这在需要明确返回 undefined 的场景中很有用。

示例:

javascript
// 箭头函数中返回 undefined
const fn = () => void console.log("执行后返回 undefined");

// 等同于
const fn = () => {
    console.log("执行后返回 undefined");
    return undefined;
};

3. 避免表达式返回值的影响

当需要执行代码但不想让返回值影响上下文时,可以使用 void

示例:

javascript
// 在 IIFE 中忽略返回值
void function() {
    console.log("立即执行函数");
}();

// 避免模块加载返回值
void import("./module.js");

4. 防止 undefined 被篡改

在旧版 JavaScript(ES5 之前)中,全局 undefined 可以被重新赋值,而 void 0 始终返回真正的 undefined

示例:

javascript
// 旧版代码中的安全做法
if (value === void 0) {
    console.log("value 是 undefined");
}

5. 书签工具(Bookmarklets)

在浏览器书签工具中,javascript:void(...) 用于执行代码而不改变当前页面内容。

示例:

javascript
javascript:void(document.body.style.backgroundColor='red');

void 的语法解析

  • 语法void expression
  • 返回值:无论 expression 是什么,void 始终返回 undefined
  • 优先级void 的优先级较高,通常需要括号包裹表达式(如 void(0))。

示例:

javascript
console.log(void 0);          // 输出: undefined
console.log(void (1 + 1));    // 输出: undefined
console.log(void "Hello");    // 输出: undefined

与替代方案的对比

场景void(0)替代方案(如event.preventDefault()
阻止链接跳转简单直接更符合语义,需绑定事件监听
返回 undefined显式返回undefined直接使用return undefined
书签工具避免页面被替换无替代方案

注意事项

  • 现代替代方案:在事件处理中,优先使用 event.preventDefault()return false
  • 代码可读性void 可能让代码意图不清晰,需谨慎使用。
  • 严格模式:ES5 后 undefined 不可写,void 0 的必要性降低。

总结

  • 核心作用void(0) 执行表达式并返回 undefined
  • 常见用途:阻止默认跳转、确保返回 undefined、书签工具。
  • 适用场景:简单拦截行为、兼容旧代码、无返回值操作。

JavaScript中preventDefault方法

在 JavaScript 中,preventDefault() 是事件对象(Event)的一个方法,用于阻止事件的默认行为。许多 HTML 元素有默认的行为,例如点击链接会跳转页面、提交表单会刷新页面等。通过调用 preventDefault(),可以阻止这些默认行为的发生。

使用场景

  1. 阻止链接跳转

    html
    <a href="https://example.com" id="myLink">Click me</a>
    <script>
      document.getElementById('myLink').addEventListener('click', function(event) {
        event.preventDefault(); // 阻止链接跳转
        console.log('Link click prevented');
      });
    </script>
  2. 阻止表单提交

    html
    <form id="myForm">
      <input type="text" name="username" />
      <button type="submit">Submit</button>
    </form>
    <script>
      document.getElementById('myForm').addEventListener('submit', function(event) {
        event.preventDefault(); // 阻止表单提交
        console.log('Form submission prevented');
      });
    </script>
  3. 阻止右键菜单

    javascript
    document.addEventListener('contextmenu', function(event) {
      event.preventDefault(); // 阻止右键菜单
      console.log('Right-click menu prevented');
    });
  4. 阻止文本选中

    javascript
    document.addEventListener('selectstart', function(event) {
      event.preventDefault(); // 阻止文本选中
      console.log('Text selection prevented');
    });

注意事项

  1. 事件传播

    • preventDefault() 只阻止事件的默认行为,不会阻止事件冒泡或捕获。如果需要阻止事件传播,可以使用 stopPropagation()stopImmediatePropagation()
  2. 兼容性

    • preventDefault() 在现代浏览器中广泛支持。如果需要兼容旧版浏览器(如 IE8 及以下),可以使用以下代码:
      javascript
      if (event.preventDefault) {
        event.preventDefault();
      } else {
        event.returnValue = false; // 兼容 IE8 及以下
      }
  3. 不可取消的事件

    • 某些事件的默认行为无法取消(例如 scroll 事件)。对于这些事件,调用 preventDefault() 不会有任何效果。

return false 的区别

  • 在 jQuery 中,return false 会同时调用 preventDefault()stopPropagation()
  • 在原生 JavaScript 中,return false 不会阻止默认行为,除非在事件处理函数中显式调用 preventDefault()

示例:阻止表单提交并验证输入

html
<form id="myForm">
  <input type="text" name="username" required />
  <button type="submit">Submit</button>
</form>
<script>
  document.getElementById('myForm').addEventListener('submit', function(event) {
    let input = event.target.querySelector('input[name="username"]');
    if (input.value.trim() === '') {
      event.preventDefault(); // 阻止表单提交
      alert('Username cannot be empty!');
    }
  });
</script>

总结

  • preventDefault() 用于阻止事件的默认行为。
  • 适用于链接跳转、表单提交、右键菜单等场景。
  • 不会阻止事件传播,如果需要阻止传播,需额外调用 stopPropagation()
  • 是现代 Web 开发中处理事件的重要工具之一。

JavaScript事件流

在 JavaScript 中,事件流(Event Flow)描述了事件在 DOM 树中传播的过程。事件流分为三个阶段:捕获阶段(Capture Phase)目标阶段(Target Phase)冒泡阶段(Bubble Phase)。理解事件流对于处理事件委托、优化事件监听器至关重要。

1. 事件流的三个阶段

(1)捕获阶段(Capture Phase)

  • 方向:从 window 对象向下传播到目标元素。
  • 特点:事件从最外层元素向内层元素传播。
  • 触发条件:事件监听器设置为捕获模式(useCapture: true)。

(2)目标阶段(Target Phase)

  • 方向:事件到达目标元素。
  • 特点:事件在目标元素上触发。

(3)冒泡阶段(Bubble Phase)

  • 方向:从目标元素向上传播到 window 对象。
  • 特点:事件从内层元素向外层元素传播。
  • 触发条件:默认情况下,事件监听器处于冒泡模式(useCapture: false)。

2. 事件流的传播过程

以下是一个典型的事件流传播过程:

  1. 捕获阶段:事件从 window 开始,依次经过 document<html><body>,直到目标元素的父元素。
  2. 目标阶段:事件到达目标元素。
  3. 冒泡阶段:事件从目标元素开始,依次经过父元素、<body><html>document,直到 window

3. 事件监听器的注册

通过 addEventListener 方法注册事件监听器,可以指定是否在捕获阶段触发。

语法:

javascript
element.addEventListener(eventType, handler, useCapture);
  • eventType:事件类型(如 "click")。
  • handler:事件处理函数。
  • useCapture:是否在捕获阶段触发(默认 false,即冒泡阶段触发)。

示例:

javascript
const parent = document.getElementById("parent");
const child = document.getElementById("child");

parent.addEventListener("click", () => console.log("Parent Captured"), true);
child.addEventListener("click", () => console.log("Child Clicked"));
parent.addEventListener("click", () => console.log("Parent Bubbled"));

// 点击 child 元素时输出:
// Parent Captured
// Child Clicked
// Parent Bubbled

4. 阻止事件传播

  • event.stopPropagation():阻止事件继续传播(捕获或冒泡)。
  • event.stopImmediatePropagation():阻止事件传播,并阻止同一元素上的其他事件监听器执行。

示例:

javascript
child.addEventListener("click", (event) => {
    console.log("Child Clicked");
    event.stopPropagation(); // 阻止冒泡
});

5. 事件委托

事件委托是一种利用事件冒泡机制的技术,将事件监听器绑定到父元素,通过事件目标(event.target)处理子元素的事件。

示例:

html
<ul id="list">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>

<script>
document.getElementById("list").addEventListener("click", (event) => {
    if (event.target.tagName === "LI") {
        console.log("Clicked:", event.target.textContent);
    }
});
</script>

6. 事件流的兼容性

  • 所有现代浏览器都支持事件流的三个阶段。
  • 旧版 IE(IE8 及以下)仅支持冒泡阶段。

7. 事件流的应用场景

场景描述示例
事件委托减少事件监听器数量,优化性能父元素监听子元素点击事件
阻止默认行为阻止表单提交、链接跳转等event.preventDefault()
阻止事件传播防止事件冒泡或捕获event.stopPropagation()
动态元素事件为动态添加的元素绑定事件事件委托

总结

  • 事件流:捕获 → 目标 → 冒泡。
  • 捕获阶段:从外向内传播,需显式设置 useCapture: true
  • 冒泡阶段:从内向外传播,默认行为。
  • 事件委托:利用冒泡机制,减少事件监听器数量。
  • 阻止传播event.stopPropagation()event.stopImmediatePropagation()

理解事件流有助于更好地管理事件监听器,优化性能并实现复杂交互。

JavaScript中null和undefined的区别

在 JavaScript 中,nullundefined 都表示“无”的概念,但它们的含义、用法和场景有显著区别。以下是两者的详细对比:

一、核心区别

特性undefinednull
含义表示“未定义”表示“空对象引用”(人为定义的“空值”)
类型typeof undefined 返回 "undefined"typeof null 返回 "object"(历史遗留问题)
默认赋值变量未初始化时的默认值需显式赋值(开发者主动设置)
使用场景系统级“空值”应用级“空值”

二、具体表现

  1. 默认值差异
  • undefined 是变量声明但未赋值时的默认值:

    javascript
    let a;
    console.log(a); // undefined
  • null 需显式赋值,表示“空值”:

    javascript
    let b = null; // 明确表示 b 为空
  1. 函数返回值
  • 函数无返回值时,默认返回 undefined

    javascript
    function foo() {}
    console.log(foo()); // undefined
  • 若需返回“空”,可显式返回 null

    javascript
    function bar() { return null; }
  1. 参数缺失
  • 函数参数未传递时,值为 undefined
    javascript
    function test(arg) { console.log(arg); }
    test(); // undefined
  1. 对象属性
  • 对象不存在的属性值为 undefined

    javascript
    const obj = {};
    console.log(obj.name); // undefined
  • 若需显式清空属性,可赋值为 null

    javascript
    obj.name = null; // 表示 name 属性为空

三、相等性比较

  • 宽松相等(==

    javascript
    console.log(null == undefined); // true(都表示“无”)
  • 严格相等(===

    javascript
    console.log(null === undefined); // false(类型不同)

四、实际应用场景

  1. 使用 undefined 的场景
  • 检测变量是否声明:

    javascript
    if (typeof variable === 'undefined') {
      // 变量未声明或未赋值
    }
  • 函数参数默认值:

    javascript
    function greet(name = 'Anonymous') {
      console.log(`Hello, ${name}!`);
    }
    greet(); // Hello, Anonymous!
  1. 使用 null 的场景
  • 主动释放对象引用(优化内存):

    javascript
    let data = { /* 大数据对象 */ };
    data = null; // 解除引用,帮助垃圾回收
  • 表示明确的“空值”(如 API 响应):

    javascript
    // 接口返回:用户未设置头像时返回 null
    const avatar = fetchUserAvatar(); // 可能返回 null

五、注意事项

  1. 避免混淆赋值

    • 不要显式将变量赋值为 undefined,保留其默认语义。
    • null 表示人为设置的“空值”。
  2. JSON 序列化

    javascript
    JSON.stringify({ a: undefined, b: null }); 
    // 输出 "{"b":null}"(undefined 被忽略)
  3. 数值转换

    javascript
    Number(undefined); // NaN
    Number(null);      // 0

总结

场景undefinednull
默认空值系统自动分配开发者主动设置
语义“未定义”“空对象”或“空值”
使用建议保留其默认行为用于明确的空值占位

理解二者的区别,可避免代码中的歧义,提升代码可读性和健壮性。

JavaScript变量声明提升

JavaScript 中的 变量声明提升(Hoisting) 是 JavaScript 引擎在代码执行前将变量和函数声明提升到作用域顶部的行为。理解变量声明提升有助于避免代码中的潜在错误。

1. 变量声明提升的原理

  • 提升(Hoisting):JavaScript 引擎在代码执行前会扫描整个作用域,将变量和函数声明提升到作用域的顶部。
  • 仅提升声明:变量的赋值操作不会被提升,只有声明部分会被提升。

2. 变量声明提升的行为

(1) var 声明的变量

  • 提升:声明会被提升到作用域顶部,但赋值不会。
  • 初始值:提升后的变量值为 undefined

示例

javascript
console.log(a); // 输出:undefined(声明提升,但未赋值)
var a = 10;
console.log(a); // 输出:10

实际执行顺序

javascript
var a; // 声明提升
console.log(a); // 输出:undefined
a = 10; // 赋值
console.log(a); // 输出:10

(2) letconst 声明的变量

  • 提升:声明会被提升,但不会初始化(进入“暂时性死区”)。
  • 初始值:在声明前访问会抛出 ReferenceError

示例

javascript
console.log(b); // 报错:ReferenceError: Cannot access 'b' before initialization
let b = 20;
console.log(b); // 输出:20

实际执行顺序

javascript
let b; // 声明提升,但未初始化
console.log(b); // 报错:暂时性死区
b = 20; // 赋值
console.log(b); // 输出:20

3. 函数声明提升

(1) 函数声明

  • 提升:整个函数声明(包括函数体)会被提升到作用域顶部。
  • 行为:可以在声明前调用。

示例

javascript
foo(); // 输出:Hello
function foo() {
  console.log('Hello');
}

实际执行顺序

javascript
function foo() {
  console.log('Hello');
}
foo(); // 输出:Hello

(2) 函数表达式

  • 提升:只有变量声明会被提升,函数赋值不会被提升。
  • 行为:在赋值前调用会报错。

示例

javascript
bar(); // 报错:TypeError: bar is not a function
var bar = function() {
  console.log('World');
};

实际执行顺序

javascript
var bar; // 声明提升
bar(); // 报错:bar 是 undefined
bar = function() {
  console.log('World');
};

4. 变量声明提升的作用域

  • 函数作用域var 声明的变量会提升到函数作用域顶部。
  • 块级作用域letconst 声明的变量会提升到块级作用域顶部,但在声明前不可访问。

示例

javascript
function test() {
  console.log(x); // 输出:undefined
  if (true) {
    var x = 10;
    let y = 20;
  }
  console.log(x); // 输出:10
  console.log(y); // 报错:ReferenceError: y is not defined
}
test();

5. 变量声明提升的注意事项

  1. 避免使用未声明的变量:未声明的变量不会提升,直接使用会报错。

    javascript
    console.log(z); // 报错:ReferenceError: z is not defined
  2. 避免重复声明

    • var 允许重复声明,后声明的变量会覆盖前者。
    • letconst 不允许重复声明,会报错。

    示例

    javascript
    var a = 1;
    var a = 2; // 允许
    console.log(a); // 输出:2
    
    let b = 1;
    let b = 2; // 报错:SyntaxError: Identifier 'b' has already been declared
  3. 优先使用 letconst

    • letconst 提供了块级作用域,避免了 var 的变量提升问题。

总结

声明方式提升行为初始值作用域
var声明提升,赋值不提升undefined函数作用域
let声明提升,但进入暂时性死区不可访问块级作用域
const声明提升,但进入暂时性死区不可访问块级作用域
函数声明整个函数(包括函数体)提升函数体函数作用域
函数表达式仅变量声明提升,函数体不提升undefined函数作用域

理解变量声明提升有助于编写更健壮的 JavaScript 代码,避免因变量作用域和声明顺序导致的错误。

JavaScript中hoisting是什么

Hoisting(变量提升) 是 JavaScript 中的一种行为,指的是变量和函数的声明在代码执行前被提升到其作用域的顶部。这意味着可以在声明之前使用变量或调用函数,但实际行为因变量类型(varletconst)和函数类型(函数声明、函数表达式)而异。

1. 变量提升

(1) var 声明的变量

  • 提升行为:声明会被提升到作用域顶部,但赋值不会。
  • 初始值:提升后的变量值为 undefined

示例

javascript
console.log(x); // 输出:undefined
var x = 10;
console.log(x); // 输出:10

实际执行顺序

javascript
var x; // 声明提升
console.log(x); // 输出:undefined
x = 10; // 赋值
console.log(x); // 输出:10

(2) letconst 声明的变量

  • 提升行为:声明会被提升,但不会初始化(进入“暂时性死区”)。
  • 初始值:在声明前访问会抛出 ReferenceError

示例

javascript
console.log(y); // 报错:ReferenceError: Cannot access 'y' before initialization
let y = 20;
console.log(y); // 输出:20

实际执行顺序

javascript
let y; // 声明提升,但未初始化
console.log(y); // 报错:暂时性死区
y = 20; // 赋值
console.log(y); // 输出:20

2. 函数提升

(1) 函数声明

  • 提升行为:整个函数声明(包括函数体)会被提升到作用域顶部。
  • 行为:可以在声明前调用。

示例

javascript
foo(); // 输出:Hello
function foo() {
  console.log('Hello');
}

实际执行顺序

javascript
function foo() {
  console.log('Hello');
}
foo(); // 输出:Hello

(2) 函数表达式

  • 提升行为:只有变量声明会被提升,函数赋值不会被提升。
  • 行为:在赋值前调用会报错。

示例

javascript
bar(); // 报错:TypeError: bar is not a function
var bar = function() {
  console.log('World');
};

实际执行顺序

javascript
var bar; // 声明提升
bar(); // 报错:bar 是 undefined
bar = function() {
  console.log('World');
};

3. 变量提升的作用域

  • var:提升到函数作用域顶部。
  • letconst:提升到块级作用域顶部。

示例

javascript
function test() {
  console.log(a); // 输出:undefined
  if (true) {
    var a = 10;
    let b = 20;
  }
  console.log(a); // 输出:10
  console.log(b); // 报错:ReferenceError: b is not defined
}
test();

4. 注意事项

(1) 避免使用未声明的变量:未声明的变量不会提升,直接使用会报错。

javascript
console.log(z); // 报错:ReferenceError: z is not defined

(2) 避免重复声明

  • var 允许重复声明,后声明的变量会覆盖前者。
  • letconst 不允许重复声明,会报错。

示例

javascript
var x = 1;
var x = 2; // 允许
console.log(x); // 输出:2

let y = 1;
let y = 2; // 报错:SyntaxError: Identifier 'y' has already been declared

(3) 优先使用 letconst

  • letconst 提供了块级作用域,避免了 var 的变量提升问题。

总结

声明方式提升行为初始值作用域
var声明提升,赋值不提升undefined函数作用域
let声明提升,但进入暂时性死区不可访问块级作用域
const声明提升,但进入暂时性死区不可访问块级作用域
函数声明整个函数(包括函数体)提升函数体函数作用域
函数表达式仅变量声明提升,函数体不提升undefined函数作用域

理解变量提升有助于编写更健壮、可维护的 JavaScript 代码。

JavaScript时间死区

在 JavaScript 中,时间死区(Temporal Dead Zone,简称 TDZ) 是指从进入作用域到变量声明之间的区域,在这段时间内访问变量会抛出 ReferenceError。时间死区是 letconst 声明的特性,而 var 不存在时间死区。

1. 时间死区的定义

  • 作用域letconst 声明的变量具有块级作用域。
  • 时间死区:从进入作用域到变量声明之间的区域,访问变量会报错。

2. 时间死区的表现

示例 1:let 的时间死区

javascript
console.log(x); // 报错: ReferenceError: Cannot access 'x' before initialization
let x = 10;

示例 2:const 的时间死区

javascript
console.log(y); // 报错: ReferenceError: Cannot access 'y' before initialization
const y = 20;

对比 var

var 声明的变量会被提升(Hoisting),不会产生时间死区。

javascript
console.log(z); // 输出: undefined
var z = 30;

3. 时间死区的原因

  • 提升(Hoisting)letconst 也会被提升,但不会初始化(与 var 不同)。
  • 初始化前访问:在变量声明之前访问会导致 ReferenceError

4. 时间死区的实际影响

示例 1:函数作用域

javascript
function example() {
    console.log(a); // 报错: ReferenceError
    let a = 10;
}
example();

示例 2:块级作用域

javascript
if (true) {
    console.log(b); // 报错: ReferenceError
    let b = 20;
}

5. 避免时间死区

  • 声明前置:将 letconst 声明放在作用域顶部。
  • 避免提前访问:确保在变量声明后再访问。

正确示例

javascript
let x = 10;
console.log(x); // 输出: 10

6. 时间死区与 typeof

在时间死区内使用 typeof 也会报错。

示例

javascript
console.log(typeof x); // 报错: ReferenceError
let x = 10;

7. 时间死区的好处

  • 更严格的变量管理:避免在声明前意外使用变量。
  • 减少错误:强制开发者遵循良好的编码习惯。

总结

特性varlet/const
作用域函数作用域块级作用域
提升声明和初始化都提升仅声明提升,不初始化
时间死区
初始化前访问返回undefined抛出ReferenceError

时间死区letconst 的重要特性,通过强制变量在声明后才能访问,避免了潜在的错误和不一致性。

JavaScript作用域

在 JavaScript 中,作用域(Scope) 决定了变量、函数和对象的可访问性。理解作用域是掌握 JavaScript 编程的基础。以下是 JavaScript 作用域的详细说明:

1. 作用域的类型

(1) 全局作用域(Global Scope)

  • 定义:在函数和代码块之外声明的变量或函数。
  • 特点
    • 在整个程序中都可以访问。
    • 容易造成命名冲突和变量污染。

示例

javascript
const globalVar = '全局变量';

function test() {
  console.log(globalVar); // 输出:全局变量
}
test();

(2) 函数作用域(Function Scope)

  • 定义:在函数内部声明的变量或函数。
  • 特点
    • 只能在函数内部访问。
    • 使用 var 声明的变量具有函数作用域。

示例

javascript
function test() {
  var localVar = '局部变量';
  console.log(localVar); // 输出:局部变量
}
test();
console.log(localVar); // 报错:ReferenceError: localVar is not defined

(3) 块级作用域(Block Scope)

  • 定义:在代码块({})内部声明的变量。
  • 特点
    • 只能在代码块内部访问。
    • 使用 letconst 声明的变量具有块级作用域。

示例

javascript
if (true) {
  let blockVar = '块级变量';
  console.log(blockVar); // 输出:块级变量
}
console.log(blockVar); // 报错:ReferenceError: blockVar is not defined

2. 作用域链(Scope Chain)

  • 定义:JavaScript 引擎通过作用域链查找变量。
  • 规则
    • 从当前作用域开始查找变量。
    • 如果找不到,则向上一级作用域查找,直到全局作用域。
    • 如果全局作用域中也没有找到,则抛出 ReferenceError

示例

javascript
const globalVar = '全局变量';

function outer() {
  const outerVar = '外部变量';

  function inner() {
    const innerVar = '内部变量';
    console.log(innerVar); // 输出:内部变量
    console.log(outerVar); // 输出:外部变量
    console.log(globalVar); // 输出:全局变量
  }

  inner();
}

outer();

3. 作用域与变量声明

(1) var 的作用域

  • 函数作用域var 声明的变量在函数内部有效。
  • 变量提升var 声明的变量会提升到函数作用域的顶部。

示例

javascript
function test() {
  console.log(x); // 输出:undefined(变量提升)
  var x = 10;
  console.log(x); // 输出:10
}
test();

(2) letconst 的作用域

  • 块级作用域letconst 声明的变量在代码块内部有效。
  • 暂时性死区:在声明前访问会抛出 ReferenceError

示例

javascript
if (true) {
  console.log(y); // 报错:ReferenceError: Cannot access 'y' before initialization
  let y = 20;
  console.log(y); // 输出:20
}

4. 作用域的应用场景

(1) 避免全局变量污染

  • 使用函数作用域或块级作用域限制变量的可见性。

示例

javascript
(function() {
  const localVar = '局部变量';
  console.log(localVar); // 输出:局部变量
})();
console.log(localVar); // 报错:ReferenceError: localVar is not defined

(2) 闭包(Closure)

  • 函数可以访问其外部作用域的变量,即使外部函数已经执行完毕。

示例

javascript
function outer() {
  const outerVar = '外部变量';
  return function inner() {
    console.log(outerVar); // 输出:外部变量
  };
}

const innerFunc = outer();
innerFunc();

总结

作用域类型定义特点变量声明
全局作用域在函数和代码块之外声明的变量整个程序可访问,易造成变量污染varletconst
函数作用域在函数内部声明的变量仅在函数内部可访问var
块级作用域在代码块内部声明的变量仅在代码块内部可访问letconst

理解 JavaScript 的作用域有助于编写更清晰、更健壮的代码,避免变量冲突和意外行为。

JavaScript未声明变量

在 JavaScript 中,未声明变量 是指未使用 varletconst 关键字声明的变量。直接使用未声明变量会导致错误或意外行为。以下是关于未声明变量的详细说明:

1. 未声明变量的行为

(1) 非严格模式

  • 如果直接赋值给未声明变量,JavaScript 会隐式创建一个全局变量。
  • 这种行为可能导致意外的全局变量污染。

示例

javascript
function test() {
  x = 10; // 未声明变量,隐式创建全局变量
}
test();
console.log(x); // 输出:10(全局变量)

(2) 严格模式

  • 在严格模式下,直接使用未声明变量会抛出 ReferenceError

示例

javascript
'use strict';
function test() {
  x = 10; // 报错:ReferenceError: x is not defined
}
test();

2. 未声明变量与未定义变量的区别

特性未声明变量未定义变量
定义未使用varletconst 声明已声明但未赋值
严格模式抛出ReferenceError值为undefined
非严格模式隐式创建全局变量值为undefined

示例

javascript
// 未声明变量
console.log(x); // 报错:ReferenceError: x is not defined

// 未定义变量
let y;
console.log(y); // 输出:undefined

3. 检测未声明变量

  • 使用 typeof 检测未声明变量不会抛出错误,而是返回 'undefined'

示例

javascript
if (typeof x === 'undefined') {
  console.log('x 未声明或未定义');
}

4. 避免未声明变量

(1) 使用严格模式

  • 在脚本或函数顶部添加 'use strict';,避免隐式创建全局变量。

示例

javascript
'use strict';
x = 10; // 报错:ReferenceError: x is not defined

(2) 显式声明变量

  • 始终使用 varletconst 声明变量。

示例

javascript
let x = 10; // 显式声明
console.log(x); // 输出:10

(3) 使用 lint 工具

  • 使用 ESLint 等工具检测未声明变量。

示例

javascript
// ESLint 规则
{
  "rules": {
    "no-undef": "error"
  }
}

总结

行为非严格模式严格模式
未声明变量隐式创建全局变量抛出ReferenceError
未定义变量值为undefined值为undefined

未声明变量可能导致意外的全局变量污染或运行时错误,因此应始终显式声明变量,并启用严格模式以避免潜在问题。

JavaScript为变量分配默认值

在 JavaScript 中,为变量分配默认值是一种常见的编程模式,尤其是在处理函数参数或对象解构时。以下是几种常用的方法:

  1. 使用逻辑或操作符 (||)

逻辑或操作符 (||) 可以用来为变量分配默认值。如果左侧的值为假值(如 nullundefined0""false),则返回右侧的默认值。

示例

javascript
function greet(name) {
    name = name || "Guest";
    console.log(`Hello, ${name}!`);
}

greet(); // 输出: Hello, Guest!
greet("Alice"); // 输出: Hello, Alice!

注意:这种方法会忽略所有假值(如 0""),可能不适用于所有场景。

  1. 使用空值合并操作符 (??)

空值合并操作符 (??) 是 ES2020 引入的特性,仅在左侧的值为 nullundefined 时返回右侧的默认值。

示例

javascript
function greet(name) {
    name = name ?? "Guest";
    console.log(`Hello, ${name}!`);
}

greet(); // 输出: Hello, Guest!
greet(null); // 输出: Hello, Guest!
greet(""); // 输出: Hello, !
greet(0); // 输出: Hello, 0!
greet("Alice"); // 输出: Hello, Alice!

注意?? 只对 nullundefined 生效,不会忽略其他假值。

  1. 函数参数默认值

ES6 允许在函数定义时为参数直接指定默认值。

示例

javascript
function greet(name = "Guest") {
    console.log(`Hello, ${name}!`);
}

greet(); // 输出: Hello, Guest!
greet("Alice"); // 输出: Hello, Alice!

注意:默认值仅在参数为 undefined 时生效。

  1. 对象解构默认值

在解构对象时,可以为属性分配默认值。

示例

javascript
const user = { name: "Alice" };

const { name, age = 25 } = user;
console.log(name); // 输出: Alice
console.log(age); // 输出: 25
  1. 数组解构默认值

在解构数组时,也可以为元素分配默认值。

示例

javascript
const colors = ["red", "green"];

const [firstColor, secondColor = "blue"] = colors;
console.log(firstColor); // 输出: red
console.log(secondColor); // 输出: green
  1. 结合逻辑或和三元运算符

在某些复杂场景中,可以结合逻辑或 (||) 和三元运算符 (? :) 来分配默认值。

示例

javascript
function greet(name) {
    name = name ? name : "Guest";
    console.log(`Hello, ${name}!`);
}

greet(); // 输出: Hello, Guest!
greet("Alice"); // 输出: Hello, Alice!
  1. 使用 typeof 检查

在需要更精确控制默认值的场景中,可以使用 typeof 检查变量是否为 undefined

示例

javascript
function greet(name) {
    name = typeof name !== "undefined" ? name : "Guest";
    console.log(`Hello, ${name}!`);
}

greet(); // 输出: Hello, Guest!
greet("Alice"); // 输出: Hello, Alice!

总结

  • ||:简单但会忽略所有假值。
  • ??:仅对 nullundefined 生效。
  • 函数参数默认值:ES6 特性,推荐使用。
  • 解构默认值:适用于对象和数组解构。
  • 三元运算符和 typeof:适用于需要更精确控制的场景。

根据具体需求选择合适的方法,函数参数默认值和解构默认值是现代 JavaScript 中最常用的方式。

如何检查JavaScript中的变量类型

在 JavaScript 中,检查变量类型的常用方法有以下几种,各有不同的适用场景和局限性:

1. typeof 操作符

作用:返回变量的基本类型(原始类型)。适用场景:检查原始类型(number, string, boolean, undefined, symbol, function)。局限性

  • null 返回 "object"(历史遗留问题)。
  • 对数组、对象、日期等引用类型都返回 "object"
javascript
console.log(typeof 42);          // "number"
console.log(typeof "hello");     // "string"
console.log(typeof true);        // "boolean"
console.log(typeof undefined);   // "undefined"
console.log(typeof Symbol());    // "symbol"
console.log(typeof function(){});// "function"
console.log(typeof null);        // "object"(⚠️ 陷阱)
console.log(typeof []);          // "object"
console.log(typeof {});          // "object"

2. instanceof 操作符

作用:检测对象是否是某个构造函数的实例。适用场景:检查引用类型(如 Array, Date, RegExp 等)。局限性

  • 无法检测原始类型(如 number, string)。
  • 跨窗口或 iframe 时可能失效(因为不同全局环境的构造函数不同)。
javascript
console.log([] instanceof Array);         // true
console.log({} instanceof Object);        // true
console.log(new Date() instanceof Date);  // true

// 原始类型无法检测
console.log("hello" instanceof String);   // false(需要 new String("hello"))

3. Object.prototype.toString.call()

作用:返回 [object Type] 格式的字符串,最通用的类型检测方法。 适用场景:精确检测所有类型(包括原始类型和引用类型)。

javascript
function getType(value) {
  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

console.log(getType(42));           // "number"
console.log(getType("hello"));      // "string"
console.log(getType(null));         // "null"(✅ 可区分 null)
console.log(getType([]));           // "array"
console.log(getType({}));           // "object"
console.log(getType(new Date()));   // "date"
console.log(getType(/regex/));      // "regexp"
console.log(getType(Symbol()));     // "symbol"

4. 特定类型的检查方法

针对某些特殊类型,可以使用专用函数:

  • 数组Array.isArray(value)
  • NaNNumber.isNaN(value)
  • 整数Number.isInteger(value)
  • undefined:直接比较 value === undefined
  • null:直接比较 value === null
javascript
// 检查数组
console.log(Array.isArray([]));      // true

// 检查 NaN
console.log(Number.isNaN(NaN));      // true

// 检查 undefined
let x;
console.log(x === undefined);        // true

// 检查 null
let y = null;
console.log(y === null);             // true

总结

方法适用场景局限性
typeof原始类型(除null)和函数无法区分null 和对象
instanceof引用类型(如Array, Date无法检测原始类型
Object.prototype.toString所有类型(最通用)需要封装工具函数
专用方法(如Array.isArray特定类型(如数组、NaN)仅针对特定类型

推荐做法

  • 使用 Object.prototype.toString.call() 作为通用类型检查方法。
  • 对特定类型(如数组、nullundefined)使用专用方法。

JavaScript如何判断一个对象是否属于某个类

在 JavaScript 中,判断一个对象是否属于某个类(或构造函数)有多种方法,具体取决于你的需求和场景。以下是常用的几种方法:

  1. 使用 instanceof 运算符

instanceof 运算符用于检查一个对象是否是某个类的实例(包括继承链上的类)。

示例

javascript
class Animal {}
class Dog extends Animal {}

const dog = new Dog();

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true (因为 Dog 继承自 Animal)
console.log(dog instanceof Object); // true (所有对象都是 Object 的实例)

注意

  • instanceof 会检查原型链,因此如果对象是子类的实例,也会返回 true
  • 如果对象的原型被修改,instanceof 的结果可能会受到影响。
  1. 使用 constructor 属性

每个对象都有一个 constructor 属性,指向创建该对象的构造函数。

示例

javascript
class Animal {}
class Dog extends Animal {}

const dog = new Dog();

console.log(dog.constructor === Dog); // true
console.log(dog.constructor === Animal); // false

注意

  • constructor 属性可以被修改,因此不一定可靠。
  • 不会检查原型链。
  1. 使用 Object.prototype.toString()

Object.prototype.toString() 方法可以返回对象的类型字符串,格式为 [object 类型]

示例

javascript
class Animal {}
class Dog extends Animal {}

const dog = new Dog();

console.log(Object.prototype.toString.call(dog)); // [object Object]
console.log(Object.prototype.toString.call(dog) === "[object Dog]"); // false

注意

  • 默认情况下,自定义类的实例会返回 [object Object]
  • 可以通过重写 Symbol.toStringTag 属性来定制类型字符串。

定制 toString 行为

javascript
class Dog {
    get [Symbol.toStringTag]() {
        return "Dog";
    }
}

const dog = new Dog();
console.log(Object.prototype.toString.call(dog)); // [object Dog]
  1. 使用 Object.getPrototypeOf()

Object.getPrototypeOf() 方法可以获取对象的原型,然后与类的原型进行比较。

示例

javascript
class Animal {}
class Dog extends Animal {}

const dog = new Dog();

console.log(Object.getPrototypeOf(dog) === Dog.prototype); // true
console.log(Object.getPrototypeOf(dog) === Animal.prototype); // false

注意

  • 只会检查直接原型,不会检查原型链。
  1. 使用 Symbol.hasInstance

Symbol.hasInstance 是一个内置符号,可以自定义 instanceof 的行为。

示例

javascript
class Animal {
    static [Symbol.hasInstance](instance) {
        return instance.hasTail; // 自定义判断逻辑
    }
}

const dog = { hasTail: true };
console.log(dog instanceof Animal); // true

注意

  • 这种方法可以灵活地自定义 instanceof 的逻辑。
  1. 使用 isPrototypeOf()

isPrototypeOf() 方法用于检查某个对象是否在另一个对象的原型链上。

示例

javascript
class Animal {}
class Dog extends Animal {}

const dog = new Dog();

console.log(Dog.prototype.isPrototypeOf(dog)); // true
console.log(Animal.prototype.isPrototypeOf(dog)); // true

注意

  • 会检查整个原型链。

总结

方法检查原型链可自定义适用场景
instanceof检查对象是否是类的实例
constructor检查对象的构造函数
Object.prototype.toString获取对象的类型字符串
Object.getPrototypeOf检查对象的直接原型
Symbol.hasInstance自定义instanceof 行为
isPrototypeOf检查对象是否在原型链上

根据具体需求选择合适的方法:

  • 如果需要检查原型链,使用 instanceofisPrototypeOf
  • 如果需要检查构造函数,使用 constructor
  • 如果需要自定义类型判断逻辑,使用 Symbol.hasInstanceObject.prototype.toString

JavaScript如何判断array和object

在 JavaScript 中,数组(Array)和对象(Object)都是复杂数据类型,但它们的使用场景和特性不同。以下是判断一个变量是数组还是对象的几种方法:

1. 使用 Array.isArray()

  • 作用

    • 判断一个变量是否为数组。
const arr = [1, 2, 3];
const obj = { a: 1, b: 2 };

console.log(Array.isArray(arr)); // 输出: true
console.log(Array.isArray(obj)); // 输出: false

2. 使用 instanceof

  • 作用

    • 判断一个变量是否为某个构造函数的实例。
const arr = [1, 2, 3];
const obj = { a: 1, b: 2 };

console.log(arr instanceof Array); // 输出: true
console.log(obj instanceof Array); // 输出: false
console.log(obj instanceof Object); // 输出: true

3. 使用 Object.prototype.toString.call()

  • 作用

    • 返回变量的类型字符串,可以准确判断数组和对象。
const arr = [1, 2, 3];
const obj = { a: 1, b: 2 };

console.log(Object.prototype.toString.call(arr)); // 输出: [object Array]
console.log(Object.prototype.toString.call(obj)); // 输出: [object Object]

4. 使用 typeof(不推荐)

  • 作用

    • 返回变量的数据类型,但无法区分数组和对象。
const arr = [1, 2, 3];
const obj = { a: 1, b: 2 };

console.log(typeof arr); // 输出: object
console.log(typeof obj); // 输出: object

5. 使用 constructor

  • 作用

    • 判断变量的构造函数。
const arr = [1, 2, 3];
const obj = { a: 1, b: 2 };

console.log(arr.constructor === Array); // 输出: true
console.log(obj.constructor === Object); // 输出: true

6. 使用 Object.prototype.isPrototypeOf()

  • 作用

    • 判断一个对象是否在另一个对象的原型链上。
const arr = [1, 2, 3];
const obj = { a: 1, b: 2 };

console.log(Array.prototype.isPrototypeOf(arr)); // 输出: true
console.log(Object.prototype.isPrototypeOf(obj)); // 输出: true

总结

方法优点缺点
Array.isArray()简单直接,推荐使用仅适用于判断数组
instanceof可以判断多种类型跨框架时可能不准确
Object.prototype.toString.call()准确判断类型语法稍复杂
typeof简单无法区分数组和对象
constructor可以判断构造函数可能被修改,不可靠
Object.prototype.isPrototypeOf()可以判断原型链关系语法稍复杂

根据具体需求选择合适的方法,可以准确判断变量是数组还是对象。推荐使用 Array.isArray() 判断数组,使用 Object.prototype.toString.call() 判断对象。

typeof bar=="object"判断类型为对象的潜在陷阱及如何避免

在 JavaScript 中,使用 typeof bar == "object" 判断一个变量是否为对象时,存在一些潜在陷阱。以下是常见问题及解决方法:

1. null 也被判断为对象

typeof null 返回 "object",这是 JavaScript 的历史遗留问题。

示例:

javascript
const bar = null;
console.log(typeof bar === "object"); // true

解决方法:

额外检查 bar !== null

javascript
if (bar !== null && typeof bar === "object") {
    console.log("bar 是对象");
}

2. 数组也被判断为对象

typeof [] 返回 "object",因为数组是特殊的对象。

示例:

javascript
const bar = [1, 2, 3];
console.log(typeof bar === "object"); // true

解决方法:

使用 Array.isArray() 排除数组。

javascript
if (typeof bar === "object" && bar !== null && !Array.isArray(bar)) {
    console.log("bar 是对象");
}

3. 函数也被判断为对象

typeof function() {} 返回 "function",但函数也是对象。

示例:

javascript
const bar = function() {};
console.log(typeof bar === "object"); // false

解决方法:

通常不需要特别处理,因为 typeof 已经将函数单独分类。

4. 包装对象(如 new String()

通过构造函数创建的包装对象(如 new String("hello"))也是对象。

示例:

javascript
const bar = new String("hello");
console.log(typeof bar === "object"); // true

解决方法:

使用 Object.prototype.toString 判断具体类型。

javascript
if (Object.prototype.toString.call(bar) === "[object Object]") {
    console.log("bar 是普通对象");
}

5. 使用 Object.prototype.toString 判断类型

Object.prototype.toString 可以更精确地判断对象类型。

示例:

javascript
const bar = {};
console.log(Object.prototype.toString.call(bar)); // [object Object]

完整判断:

javascript
function isPlainObject(obj) {
    return Object.prototype.toString.call(obj) === "[object Object]";
}

console.log(isPlainObject({})); // true
console.log(isPlainObject([])); // false
console.log(isPlainObject(null)); // false
console.log(isPlainObject(new String("hello"))); // false

6. 使用 instanceof 判断对象

instanceof 可以判断对象是否属于某个构造函数。

示例:

javascript
const bar = {};
console.log(bar instanceof Object); // true

注意:

  • instanceof 会检查原型链,可能不适用于跨框架对象。
  • null 和原始类型(如 "hello")会返回 false

7. 使用 constructor 判断对象

通过 constructor 属性可以判断对象的构造函数。

示例:

javascript
const bar = {};
console.log(bar.constructor === Object); // true

注意:

  • constructor 属性可能被修改。
  • nullundefined 没有 constructor 属性。

总结

方法优点缺点
typeof bar === "object"简单直接无法区分null、数组等
Object.prototype.toString精确判断对象类型代码较长
instanceof检查原型链不适用于跨框架对象
constructor直接判断构造函数可能被修改

推荐方法:

javascript
function isPlainObject(obj) {
    return obj !== null && typeof obj === "object" && !Array.isArray(obj);
}

// 或
function isPlainObject(obj) {
    return Object.prototype.toString.call(obj) === "[object Object]";
}

通过合理选择判断方法,可以避免 typeof bar == "object" 的潜在陷阱,准确判断对象类型。

JavaScript的typeof返回哪些数据类型

在 JavaScript 中,typeof 是一个用于检测变量或表达式类型的操作符。它返回一个表示数据类型的字符串。以下是 typeof 可能返回的结果:

  1. "undefined"
  • 表示变量未定义或未赋值。
let a;
console.log(typeof a); // "undefined"
console.log(typeof b); // "undefined"(未声明的变量)
  1. "boolean"
  • 表示布尔值(truefalse)。
let isTrue = true;
console.log(typeof isTrue); // "boolean"
  1. "number"
  • 表示数字类型(包括整数、浮点数、NaNInfinity)。
let num = 42;
let float = 3.14;
let nan = NaN;
let inf = Infinity;
console.log(typeof num);  // "number"
console.log(typeof float); // "number"
console.log(typeof nan);  // "number"(NaN 是数字类型)
console.log(typeof inf);  // "number"
  1. "string"
  • 表示字符串类型。
let str = "Hello, World!";
console.log(typeof str); // "string"
  1. "bigint"
  • 表示大整数类型(BigInt)。
let bigNum = 123n;
console.log(typeof bigNum); // "bigint"
  1. "symbol"
  • 表示符号类型(Symbol)。
let sym = Symbol("foo");
console.log(typeof sym); // "symbol"
  1. "object"
  • 表示对象类型(包括普通对象、数组、null 等)。
let obj = { name: "Alice" };
let arr = [1, 2, 3];
let nul = null;
console.log(typeof obj);  // "object"
console.log(typeof arr);  // "object"(数组也是对象)
console.log(typeof nul);  // "object"(null 被错误地识别为对象,这是历史遗留问题)
  1. "function"
  • 表示函数类型。
function foo() {}
console.log(typeof foo); // "function"
  1. "object"(特殊对象)
  • 对于内置对象(如 DateRegExpMapSet 等),typeof 也会返回 "object"
console.log(typeof new Date());      // "object"
console.log(typeof /regex/);        // "object"
console.log(typeof new Map());      // "object"
console.log(typeof new Set());      // "object"

总结

typeof 返回的可能值有:

  • "undefined"
  • "boolean"
  • "number"
  • "string"
  • "bigint"
  • "symbol"
  • "object"
  • "function"

需要注意的是,typeof null 返回 "object",这是 JavaScript 的一个历史遗留问题。如果需要更精确的类型判断,可以使用 Object.prototype.toString.call()instanceof

简述JSON stringify

JSON.stringify() 是 JavaScript 中的一个内置方法,用于将 JavaScript 对象或值转换为 JSON 格式的字符串。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。

语法

javascript
JSON.stringify(value, replacer, space);
  • value: 需要转换为 JSON 字符串的值(通常是对象或数组)。
  • replacer (可选):
    • 可以是一个函数,用于自定义序列化过程,选择哪些属性需要被包含在结果中。
    • 也可以是一个数组,指定需要包含的属性名。
  • space (可选):
    • 用于控制输出字符串的缩进和格式化。
    • 可以是数字(表示缩进的空格数)或字符串(用作缩进字符)。

返回值

返回一个表示给定值的 JSON 格式字符串。

基本用法

javascript
const obj = {
    name: "Alice",
    age: 25,
    isStudent: true,
    hobbies: ["reading", "coding"]
};

const jsonString = JSON.stringify(obj);
console.log(jsonString);
// 输出: {"name":"Alice","age":25,"isStudent":true,"hobbies":["reading","coding"]}

使用 replacer 函数

javascript
const obj = {
    name: "Alice",
    age: 25,
    isStudent: true
};

const jsonString = JSON.stringify(obj, (key, value) => {
    if (key === "age") return undefined; // 排除 age 属性
    return value;
});
console.log(jsonString);
// 输出: {"name":"Alice","isStudent":true}

使用 replacer 数组

javascript
const obj = {
    name: "Alice",
    age: 25,
    isStudent: true
};

const jsonString = JSON.stringify(obj, ["name", "isStudent"]);
console.log(jsonString);
// 输出: {"name":"Alice","isStudent":true}

使用 space 格式化输出

javascript
const obj = {
    name: "Alice",
    age: 25,
    isStudent: true
};

const jsonString = JSON.stringify(obj, null, 2);
console.log(jsonString);
// 输出:
// {
//   "name": "Alice",
//   "age": 25,
//   "isStudent": true
// }

注意事项

  1. 不支持的数据类型

    • undefined、函数、Symbol 值在序列化时会被忽略(如果在数组中则会被转换为 null)。
    • NaNInfinity 会被转换为 null
    • Date 对象会被转换为 ISO 格式的字符串。
  2. 循环引用

    • 如果对象中存在循环引用(即对象引用自身),JSON.stringify() 会抛出错误。
  3. 自定义序列化

    • 如果对象定义了 toJSON() 方法,JSON.stringify() 会调用该方法并使用其返回值。

示例:自定义 toJSON()

javascript
const obj = {
    name: "Alice",
    age: 25,
    toJSON() {
        return {
            name: this.name,
            isAdult: this.age >= 18
        };
    }
};

const jsonString = JSON.stringify(obj);
console.log(jsonString);
// 输出: {"name":"Alice","isAdult":true}

总结

JSON.stringify() 是将 JavaScript 对象转换为 JSON 字符串的标准方法,支持自定义序列化和格式化输出。它在数据存储、传输和 API 交互中非常常用。

JSON对象及JSON字符串之间的相互转换

在 JavaScript 中,JSON 对象和 JSON 字符串之间的相互转换是非常常见的操作。主要通过以下两个方法实现:

JSON.stringify(): 将 JavaScript 对象或值转换为 JSON 字符串。

JSON.parse(): 将 JSON 字符串解析为 JavaScript 对象或值。

  1. 将 JavaScript 对象转换为 JSON 字符串

使用 JSON.stringify() 方法可以将 JavaScript 对象或值转换为 JSON 格式的字符串。

语法

javascript
JSON.stringify(value, replacer, space);
  • value: 需要转换的 JavaScript 对象或值。
  • replacer (可选): 用于过滤或转换结果。
  • space (可选): 用于格式化输出的缩进。

示例

javascript
const obj = {
    name: "Alice",
    age: 25,
    isStudent: true,
    hobbies: ["reading", "coding"]
};

const jsonString = JSON.stringify(obj);
console.log(jsonString);
// 输出: {"name":"Alice","age":25,"isStudent":true,"hobbies":["reading","coding"]}
  1. 将 JSON 字符串转换为 JavaScript 对象

使用 JSON.parse() 方法可以将 JSON 字符串解析为 JavaScript 对象或值。

语法

javascript
JSON.parse(text, reviver);
  • text: 需要解析的 JSON 字符串。
  • reviver (可选): 一个函数,用于在返回之前转换解析结果。

示例

javascript
const jsonString = '{"name":"Alice","age":25,"isStudent":true,"hobbies":["reading","coding"]}';

const obj = JSON.parse(jsonString);
console.log(obj);
// 输出: 
// {
//   name: "Alice",
//   age: 25,
//   isStudent: true,
//   hobbies: ["reading", "coding"]
// }
  1. 使用 reviver 函数

JSON.parse()reviver 函数可以用于在解析过程中对值进行转换。

示例

javascript
const jsonString = '{"name":"Alice","age":25,"isStudent":true,"dob":"1998-05-14"}';

const obj = JSON.parse(jsonString, (key, value) => {
    if (key === "dob") {
        return new Date(value); // 将日期字符串转换为 Date 对象
    }
    return value;
});

console.log(obj.dob instanceof Date); // 输出: true
  1. 使用 replacer 函数

JSON.stringify()replacer 函数可以用于在序列化过程中过滤或转换值。

示例

javascript
const obj = {
    name: "Alice",
    age: 25,
    isStudent: true
};

const jsonString = JSON.stringify(obj, (key, value) => {
    if (key === "age") return undefined; // 排除 age 属性
    return value;
});

console.log(jsonString);
// 输出: {"name":"Alice","isStudent":true}
  1. 注意事项

JSON 字符串的格式

  • JSON 字符串必须使用双引号(")表示键和字符串值,单引号无效。

  • 例如:'{"name":"Alice"}' 是有效的,而 "{'name':'Alice'}" 是无效的。

不支持的数据类型

  • undefined、函数、Symbol 值在序列化时会被忽略。

  • NaNInfinity 会被转换为 null

循环引用

  • 如果对象中存在循环引用(即对象引用自身),JSON.stringify() 会抛出错误。

日期对象

  • 日期对象会被转换为 ISO 格式的字符串,解析时需要手动转换回日期对象。

示例:完整转换流程

javascript
// JavaScript 对象
const obj = {
    name: "Alice",
    age: 25,
    isStudent: true,
    dob: new Date()
};

// 转换为 JSON 字符串
const jsonString = JSON.stringify(obj);
console.log(jsonString);
// 输出: {"name":"Alice","age":25,"isStudent":true,"dob":"2023-10-01T12:00:00.000Z"}

// 转换回 JavaScript 对象
const parsedObj = JSON.parse(jsonString, (key, value) => {
    if (key === "dob") return new Date(value); // 将日期字符串转换为 Date 对象
    return value;
});

console.log(parsedObj.dob instanceof Date); // 输出: true

总结

  • JSON.stringify():将 JavaScript 对象转换为 JSON 字符串。
  • JSON.parse():将 JSON 字符串解析为 JavaScript 对象。
  • 这两个方法结合使用,可以实现 JavaScript 对象和 JSON 字符串之间的无缝转换,是数据存储、传输和 API 交互中的核心工具。

JavaScript中将base字符串转为integer

在 JavaScript 中,将特定进制的字符串(如二进制、八进制、十六进制等)转换为十进制整数,可以通过 parseInt(string, base) 实现。以下是具体方法及注意事项:

1. 使用 parseInt(string, base)

  • 功能:将字符串按指定进制解析为整数。
  • 参数
    • string:要解析的字符串。
    • base:进制基数(2 到 36 之间的整数)。
  • 返回值:十进制整数。若字符串不合法或进制无效,返回 NaN

示例

javascript
// 二进制 "1010" → 十进制 10
console.log(parseInt("1010", 2)); // 输出: 10

// 十六进制 "1A" → 十进制 26
console.log(parseInt("1A", 16)); // 输出: 26

// 八进制 "17" → 十进制 15
console.log(parseInt("17", 8)); // 输出: 15

2. 处理非法字符

如果字符串包含非法字符(如十六进制的 "G"),parseInt 会忽略非法字符后的内容:

javascript
console.log(parseInt("12G", 16)); // 解析到 "12" → 输出: 18(1*16 + 2)

3. 进制范围限制

base 参数必须是 2 到 36 之间的整数,否则返回 NaN

javascript
console.log(parseInt("10", 1));  // 输出: NaN(base < 2)
console.log(parseInt("10", 37)); // 输出: NaN(base > 36)

4. 自动进制推断

如果省略 base 参数,parseInt 会根据字符串前缀推断进制:

  • 0x 开头 → 按十六进制解析。
  • 0 开头 → 按八进制解析(ES5 之后严格模式下无效)。
  • 其他情况 → 按十进制解析。

示例

javascript
console.log(parseInt("0x1A")); // 输出: 26(十六进制)
console.log(parseInt("017"));  // 输出: 15(旧版八进制解析,ES6 后严格模式下报错)
console.log(parseInt("123"));  // 输出: 123(十进制)

5. 自定义进制转换(Base 2-36)

若需处理非标准进制(如 Base32、Base58),需自行实现或使用第三方库:

javascript
// 示例:Base36 字符串转十进制
console.log(parseInt("Z", 36)); // 输出: 35(0-9 + A-Z)

6. 错误处理

通过 isNaN 检查是否解析成功:

javascript
const value = parseInt("XYZ", 10);
if (isNaN(value)) {
    console.log("解析失败");
} else {
    console.log(value);
}

总结

场景方法示例
二进制 → 十进制parseInt(str, 2)parseInt("1010", 2) → 10
十六进制 → 十进制parseInt(str, 16)parseInt("1A", 16) → 26
八进制 → 十进制parseInt(str, 8)parseInt("17", 8) → 15
自动推断进制parseInt(str)parseInt("0x1A") → 26
错误处理isNaN()isNaN(parseInt("XYZ")) → true

注意事项

  • 始终明确指定 base 参数,避免自动推断导致的意外结果。
  • 处理非法字符时需额外验证输入合法性。
  • 超出 2-36 进制的需求需使用自定义逻辑或第三方库。

JavaScript中datatypes的两个基本组

在 JavaScript 中,数据类型(Data Types)可以分为两大类:原始类型(Primitive Types)引用类型(Reference Types)。以下是它们的详细说明及区别:

1. 原始类型(Primitive Types)

原始类型是 JavaScript 中最基本的数据类型,它们的值直接存储在变量中。原始类型是不可变的(Immutable),即它们的值不能被修改,只能被替换。

常见的原始类型

类型描述示例
string字符串"Hello", 'World'
number数字(整数或浮点数)42, 3.14
boolean布尔值(truefalsetrue, false
null空值null
undefined未定义的值undefined
symbol唯一标识符(ES6 新增)Symbol("id")
bigint大整数(ES2020 新增)123n

特点

  • 不可变性:原始类型的值不能被修改。
  • 按值传递:在赋值或传递时,复制的是值本身。
  • 存储位置:存储在栈内存中。

示例

javascript
let a = 10;
let b = a; // b 是 a 的副本
a = 20;
console.log(b); // 输出: 10(b 的值未受影响)

2. 引用类型(Reference Types)

引用类型是复杂的数据类型,它们的值存储在堆内存中,变量存储的是指向堆内存的引用(地址)。引用类型是可变的(Mutable),即它们的值可以被修改。

常见的引用类型

类型描述示例
object对象{ name: "Alice", age: 25 }
array数组[1, 2, 3]
function函数function() {}
date日期对象new Date()
regexp正则表达式/abc/

特点

  • 可变性:引用类型的值可以被修改。
  • 按引用传递:在赋值或传递时,复制的是引用(地址)。
  • 存储位置:存储在堆内存中。

示例

javascript
let obj1 = { name: "Alice" };
let obj2 = obj1; // obj2 和 obj1 指向同一个对象
obj1.name = "Bob";
console.log(obj2.name); // 输出: Bob(obj2 的值被修改)

3. 原始类型与引用类型的区别

特性原始类型引用类型
存储方式直接存储值存储引用(地址)
可变性不可变可变
赋值/传递按值传递(复制值)按引用传递(复制引用)
存储位置栈内存堆内存
比较方式比较值是否相等比较引用是否相等

示例

javascript
// 原始类型比较
let a = 10;
let b = 10;
console.log(a === b); // true(值相等)

// 引用类型比较
let obj1 = { name: "Alice" };
let obj2 = { name: "Alice" };
console.log(obj1 === obj2); // false(引用不同)

4. 类型检测

  • 原始类型:使用 typeof 检测。
    javascript
    console.log(typeof "Hello"); // "string"
    console.log(typeof 42);      // "number"
    console.log(typeof true);    // "boolean"
    console.log(typeof null);    // "object"(历史遗留问题)
    console.log(typeof undefined); // "undefined"
    console.log(typeof Symbol("id")); // "symbol"
    console.log(typeof 123n);    // "bigint"
  • 引用类型:使用 typeof 检测对象和函数,使用 instanceof 检测具体类型。
    javascript
    console.log(typeof {});      // "object"
    console.log(typeof []);      // "object"
    console.log(typeof function() {}); // "function"
    
    console.log([] instanceof Array); // true
    console.log({} instanceof Object); // true

总结

JavaScript 中的数据类型分为两大类:

  1. 原始类型stringnumberbooleannullundefinedsymbolbigint
  2. 引用类型objectarrayfunctiondateregexp

理解它们的区别对于掌握 JavaScript 的内存管理、赋值行为和类型检测至关重要。