目录
一、JavaScript中的变量提升
1. 机制
2. 示例
3. 注意事项
4. 总结
二、var、let和const的区别。
1. 作用域(Scope)
2. 变量提升(Hoisting)
3. 重新赋值和重新声明
4. 示例
示例1:作用域和块级行为
示例2:变量提升
示例3:重新赋值和声明
5. 区别
三、什么是严格模式?如何启用?
1.严格模式概念
2.如何启用严格模式?
1. 启用整个脚本
2. 启用单个函数
注意事项
四、javaScript的数据类型有哪些
1. 原始数据类型
2. 对象数据类型
3.typeof 检测变量的类
总结:
五、解释==和===运算符的区别
1. == 运算符(值相等)
2. === 运算符(值和类型相等)
使用建议
六、JavaScript中的事件冒泡和事件捕获
1. 事件冒泡
2. 事件捕获
3. 完整事件流
4. 注意事项
七、深拷贝/浅拷贝
1. 概念
2.浅拷贝
3.深拷贝
4.第三方库实现深拷贝
5.关键区别总结
八、解释this关键字的指向规则
1.默认绑定(独立函数调用)
2.隐式绑定(方法调用)
3.显式绑定(call/apply/bind)
4.new绑定(构造函数调用)
5.箭头函数
6.事件处理函数
7.总结
九、什么是IIFE
1. IIFE的基本语法
2. IIFE的特点
3. IIFE的常见用途
4. IIFE的变体
5. 注意事项
十、描述null和undefined的区别
1.定义
2.类型差异
3.使用场景
4.相等性比较
5.默认行为
6.示例
一、JavaScript中的变量提升
在JavaScript中,变量提升(Hoisting)是一个核心概念,它描述了变量和函数声明在代码执行前被“提升”到其作用域顶部的行为。
1. 机制
- 声明提升:在JavaScript中,使用
var
关键字声明的变量会被提升到当前作用域(如全局或函数作用域)的顶部。但请注意,初始化(赋值)部分不会被提升,它保持在原位置执行。 - 函数提升:函数声明(如
function myFunc() {}
)也会被提升,且整个函数体都会被提升,允许你在声明前调用它。 - 作用域规则:提升发生在作用域内。例如,在函数内部声明的变量只提升到函数顶部,而不是全局作用域。
2. 示例
-
变量声明提升示例: 在以下代码中,变量
a
的声明被提升,但赋值a = 10
没有被提升。因此,在声明前访问a
会得到undefined
,而不是错误。console.log(a); // 输出: undefined var a = 10; console.log(a); // 输出: 10
实际执行顺序相当于:
var a; // 声明提升到顶部 console.log(a); // a 未初始化,输出 undefined a = 10; // 初始化保持原位 console.log(a); // 输出 10
-
函数声明提升示例: 函数声明整体被提升,因此可以在声明前调用它。
myFunc(); // 输出: "Hello" function myFunc() {console.log("Hello"); }
实际执行顺序相当于:
function myFunc() { // 整个函数被提升console.log("Hello"); } myFunc(); // 输出: "Hello"
3. 注意事项
-
var
vslet
/const
:ES6引入的let
和const
也有提升,但行为不同。它们被提升到块级作用域顶部,但初始化前访问会抛出错误(称为“暂时性死区”)console.log(b); // 报错: ReferenceError let b = 20;
这里,
b
的声明被提升,但访问时未初始化,导致错误。这避免了var
的undefined
问题。 -
函数表达式 vs 函数声明:函数表达式(如
var func = function() {}
)不会被提升,只有声明部分被提升func(); // 报错: TypeError var func = function() {console.log("Hi"); };
实际执行:
var func; // 声明提升 func(); // func 是 undefined,调用出错 func = function() { // 赋值保持原位console.log("Hi"); };
-
最佳实践:
- 总是声明变量在作用域顶部,以避免混淆。
- 优先使用
let
和const
代替var
,以减少提升带来的风险。 - 在复杂代码中,使用严格模式(
"use strict"
)帮助捕获错误。
4. 总结
变量提升是JavaScript的独特特性,源于其编译和执行机制。理解它有助于编写更可靠的代码。记住:声明被提升,初始化不被提升;函数声明整体提升,函数表达式则不然。通过合理使用现代语法和工具,可以规避大部分问题。
二、var
、let
和const
的区别。
1. 作用域(Scope)
var
:具有函数作用域(function-scoped)或全局作用域(global-scoped)。如果在函数内部声明,它只在函数内有效;如果在函数外部声明,则成为全局变量。let
:具有块级作用域(block-scoped)。它只在声明的代码块(如if
语句、for
循环或{}
块)内有效。const
:同样具有块级作用域。与let
类似,但声明后必须在声明时初始化,且不能重新赋值(但对象或数组的属性可以修改)。
2. 变量提升(Hoisting)
var
:支持变量提升。变量在声明前可以被访问,但值为undefined
。let
:不支持变量提升。在声明前访问会抛出ReferenceError
(暂时性死区)。const
:不支持变量提升,行为类似let
,在声明前访问会报错。
3. 重新赋值和重新声明
var
:可以重新赋值(修改值),也可以在同一作用域内重新声明(重新声明同一个变量名)。let
:可以重新赋值,但不能在同一作用域内重新声明。const
:不能重新赋值(值不可变),也不能重新声明。但如果是对象或数组,其属性或元素可以被修改。
4. 示例
示例1:作用域和块级行为
// 使用 var(函数作用域)
function exampleVar() {if (true) {var x = 10; // 在if块内声明}console.log(x); // 输出: 10,因为var是函数作用域
}
exampleVar();// 使用 let(块级作用域)
function exampleLet() {if (true) {let y = 20; // 在if块内声明}console.log(y); // 报错: ReferenceError: y is not defined,因为let是块级作用域
}
exampleLet();// 使用 const(块级作用域)
function exampleConst() {if (true) {const z = 30; // 在if块内声明}console.log(z); // 报错: ReferenceError: z is not defined,与let相同
}
exampleConst();
示例2:变量提升
// var 的变量提升
console.log(a); // 输出: undefined(提升但未初始化)
var a = 5;// let 的变量提升(报错)
console.log(b); // 报错: ReferenceError: Cannot access 'b' before initialization
let b = 10;// const 的变量提升(报错)
console.log(c); // 报错: ReferenceError: Cannot access 'c' before initialization
const c = 15;
示例3:重新赋值和声明
// var:可以重新赋值和重新声明
var num = 1;
num = 2; // 重新赋值成功
var num = 3; // 重新声明成功
console.log(num); // 输出: 3// let:可以重新赋值,但不能重新声明
let count = 1;
count = 2; // 重新赋值成功
// let count = 3; // 报错: SyntaxError: Identifier 'count' has already been declared// const:不能重新赋值,也不能重新声明
const PI = 3.14;
// PI = 3.14159; // 报错: TypeError: Assignment to constant variable
// const PI = 3.1416; // 报错: SyntaxError: Identifier 'PI' has already been declared// const 用于对象:属性可以修改
const person = { name: "Alice" };
person.name = "Bob"; // 允许修改属性
console.log(person.name); // 输出: Bob
5. 区别
特性 | var | let | const |
---|---|---|---|
作用域 | 函数作用域或全局作用域 | 块级作用域 | 块级作用域 |
变量提升 | 支持(值为undefined ) | 不支持(报错) | 不支持(报错) |
重新赋值 | 允许 | 允许 | 不允许(值不可变) |
重新声明 | 允许 | 不允许 | 不允许 |
初始化要求 | 可选 | 可选 | 必须初始化 |
三、什么是严格模式?如何启用?
1.严格模式概念
严格模式(Strict Mode)是 JavaScript 中的一种特殊运行模式,由 ECMAScript 5(ES5)引入。它通过更严格的语法和错误检查机制,帮助开发者避免常见的编码错误,提高代码的安全性和性能。在严格模式下,JavaScript 引擎会禁用一些不安全的特性(如隐式全局变量声明),并抛出更多错误从而让代码更易维护和调试。
严格模式的主要优点包括:
- 错误预防:捕获潜在错误(如变量未声明就使用),减少运行时问题。
- 性能优化:简化变量解析,提升执行效率。
- 未来兼容:为未来 JavaScript 版本的新特性做准备,避免遗留语法冲突。
2.如何启用严格模式?
启用严格模式非常简单:只需在 JavaScript 代码的顶部添加字符串 "use strict";
。这可以应用于整个脚本文件或单个函数作用域。以下是具体步骤和代码示例:
1. 启用整个脚本
在 JavaScript 文件的起始位置添加 "use strict";
,这样整个文件都运行在严格模式下。
"use strict"; // 启用全局严格模式// 示例代码
function test() {x = 10; // 在严格模式下,这会抛出 ReferenceError(x 未声明)console.log(x);
}
test();
2. 启用单个函数
如果只想在特定函数中使用严格模式,将 "use strict";
放在函数体的开头。
function strictFunction() {"use strict"; // 仅在该函数内启用严格模式let y = 20;// y = 30; // 正常赋值// z = 40; // 抛出 ReferenceError(z 未声明)
}function nonStrictFunction() {z = 50; // 在非严格模式下,z 会被隐式声明为全局变量(不推荐)console.log(z);
}
strictFunction();
nonStrictFunction();
注意事项
- 兼容性:严格模式在现代浏览器和 Node.js 中广泛支持(ES5+ 环境),但在旧版浏览器(如 IE9 以下)可能无效。测试时确保环境兼容。
- 作用域规则:
"use strict";
必须放在代码顶部(前无其他语句),否则无效。它只影响所在作用域及其子作用域。 - 常见影响:在严格模式下,以下操作会报错:
- 未声明变量(如
x = 10;
)。 - 删除不可删除的属性(如
delete Object.prototype;
)。 - 重复的函数参数(如
function(a, a) {}
)。 - 使用
eval
创建变量污染外部作用域。
- 未声明变量(如
四、javaScript的数据类型有哪些
分为两类:原始数据类型(Primitive Types)和对象数据类型(Object Type)
1. 原始数据类型
原始数据类型是不可变的(值本身不能被修改),存储在栈内存中。JavaScript 包括以下原始类型:
undefined
:表示变量未定义或未赋值。例如:let a; // a 的类型是 undefined
null
:表示空值或无值。常用于显式清空变量。let b = null; // b 的类型是 null
boolean
:布尔值,只有true
或false
。用于逻辑判断。let c = true; // c 的类型是 boolean
number
:数字类型,包括整数、浮点数、Infinity
、-Infinity
和NaN
(非数字)。例如:let d = 42; // 整数 let e = 3.14; // 浮点数
string
:字符串类型,表示文本数据。字符串是不可变的。let f = "hello"; // f 的类型是 string
symbol
(ES6 引入):唯一且不可变的值,常用作对象属性的键。例如:let g = Symbol("id"); // g 的类型是 symbol
bigint
(ES2020 引入):用于表示大整数(超过Number.MAX_SAFE_INTEGER
的整数)。在数字后加n
表示。let h = 123456789012345678901234567890n; // h 的类型是 bigint
2. 对象数据类型
对象数据类型是可变的(值可以被修改),存储在堆内存中,变量存储的是引用地址。所有非原始类型的值都属于对象类型,包括:
- 普通对象:键值对的集合。
let obj = { name: "Alice", age: 30 }; // obj 的类型是 object
- 数组:有序的元素集合。
let arr = [1, 2, 3]; // arr 的类型是 object(typeof 返回 "object")
- 函数:可执行的对象,
typeof
运算符返回"function"
,但本质上它是对象的一种。function greet() { console.log("Hi!"); } // typeof greet 返回 "function"
- 其他内置对象:如
Date
、RegExp
、Map
、Set
等。let date = new Date(); // date 的类型是 object let regex = /abc/; // regex 的类型是 object
3.typeof
检测变量的类
typeof
对原始类型返回对应名称(如"string"
)。- 示例:
console.log(typeof "hello"); // 输出 "string"
console.log(typeof null); // 输出 "object"(注意这个陷阱)
总结:
- 原始类型有 7 种:
undefined
、null
、boolean
、number
、string
、symbol
、bigint
。 - 对象类型只有 1 种:
object
,但包括多种子类型(如数组、函数等)。
五、解释==
和===
运算符的区别
1. == 运算符(值相等)
==
运算符用于比较两个值是否相等,但在比较前会进行类型转换。如果两个值的类型不同,JavaScript 会尝试将它们转换为相同类型后再比较。
- 数字和字符串比较时,字符串会尝试转换为数字。
- 布尔值和其他类型比较时,布尔值会转换为数字(
true
为 1,false
为 0)。 - 对象与非对象比较时,对象会尝试调用
valueOf()
或toString()
方法转换为原始值。
示例:
5 == '5' // true,字符串 '5' 转换为数字 5
true == 1 // true,布尔值 true 转换为数字 1
null == undefined // true,特殊规则
2. === 运算符(值和类型相等)
===
运算符不仅比较值是否相等,还比较类型是否相同。如果类型不同,直接返回 false
,不会进行类型转换。
- 类型和值都必须完全相同才会返回
true
。 NaN
与任何值(包括自身)比较都返回false
。null
和undefined
与自身比较返回true
,但互相比较返回false
。
示例:
5 === '5' // false,类型不同
true === 1 // false,类型不同
null === undefined // false,类型不同
使用建议
- 优先使用
===
,避免隐式类型转换带来的意外行为。 ==
仅在明确需要类型转换时使用,但需谨慎。
六、JavaScript中的事件冒泡和事件捕获
1. 事件冒泡
事件冒泡是DOM事件传播的默认机制。当一个事件发生在某个元素上时,它会从目标元素开始,逐级向上传播到DOM树的根节点(通常是document
对象)。这种传播方式类似于气泡从水底升到水面,因此称为“冒泡”。
- 触发顺序:从目标元素开始,依次触发父元素、祖父元素,直到根节点。
- 实际应用:常用于事件委托(Event Delegation),通过将事件监听器绑定到父元素来管理子元素的事件。
- 阻止冒泡:调用事件对象的
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. 事件捕获
事件捕获是另一种事件传播机制,与冒泡方向相反。事件从根节点开始,逐级向下传播到目标元素。捕获阶段在冒泡阶段之前发生。
- 触发顺序:从根节点开始,依次触发父元素、子元素,直到目标元素。
- 启用捕获:在
addEventListener
中设置第三个参数为true
。 - 实际应用:较少直接使用,但在需要提前拦截事件的场景(如性能优化)中可能有用。
document.getElementById('parent').addEventListener('click', function() {console.log('Parent captured'); // 先触发
}, true);document.getElementById('child').addEventListener('click', function() {console.log('Child clicked'); // 后触发
});
3. 完整事件流
DOM事件流的完整顺序分为三个阶段:
- 捕获阶段:从根节点到目标元素。
- 目标阶段:事件到达目标元素。
- 冒泡阶段:从目标元素回到根节点。
// 示例:同时监听捕获和冒泡阶段
document.getElementById('parent').addEventListener('click', function() {console.log('Bubbling phase');
});document.getElementById('parent').addEventListener('click', function() {console.log('Capturing phase');
}, true);
4. 注意事项
- 并非所有事件都支持冒泡,例如
focus
和blur
事件。 - 现代浏览器默认使用冒泡机制,但可以通过
addEventListener
的第三个参数灵活选择。 - 事件委托利用冒泡机制减少事件监听器数量,提升性能。
七、深拷贝/浅拷贝
1. 概念
在JavaScript中,拷贝对象分为深拷贝和浅拷贝。浅拷贝只复制对象的引用,而深拷贝会递归复制对象的所有属性,生成一个全新的对象。
2.浅拷贝
使用Object.assign()
方法可以实现浅拷贝。该方法将所有可枚举的自有属性从一个或多个源对象复制到目标对象。
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
使用展开运算符(...
)也可以实现浅拷贝。
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
3.深拷贝
使用JSON.parse(JSON.stringify())
可以实现简单的深拷贝。但这种方法无法处理函数、Symbol
、undefined
等特殊类型。
const original = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(original));
使用递归实现一个通用的深拷贝函数。
function deepClone(obj) {if (obj === null || typeof obj !== 'object') {return obj;}const clone = Array.isArray(obj) ? [] : {};for (let key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key]);}}return clone;
}
4.第三方库实现深拷贝
Lodash库提供了_.cloneDeep
方法,可以方便地实现深拷贝。
const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
const copy = _.cloneDeep(original);
5.关键区别总结
- 内存占用:浅拷贝共享引用类型的内存地址,深拷贝创建完全独立的内存空间
- 性能:浅拷贝速度更快,深拷贝因递归遍历消耗更多资源
- 修改影响:浅拷贝的嵌套对象修改会互相影响,深拷贝完全隔离
- 适用场景:浅拷贝适合简单数据,深拷贝适合需要完全隔离的复杂对象
八、解释this
关键字的指向规则
在JavaScript中,this
关键字的指向取决于函数的调用方式,而非声明时的环境
1.默认绑定(独立函数调用)
当函数作为独立函数调用时,this
默认指向全局对象(浏览器中为window
,Node.js中为global
)。严格模式下('use strict'
),this
为undefined
。
function showThis() {console.log(this);
}
showThis(); // 非严格模式:window;严格模式:undefined
2.隐式绑定(方法调用)
当函数作为对象的方法调用时,this
指向调用该方法的对象。
const obj = {name: 'Alice',greet: function() {console.log(this.name);}
};
obj.greet(); // 输出 'Alice'(this指向obj)
3.显式绑定(call/apply/bind)
通过call
、apply
或bind
方法显式指定this
的指向。
function sayHello() {console.log(this.name);
}
const person = { name: 'Bob' };
sayHello.call(person); // 输出 'Bob'(this指向person)
4.new绑定(构造函数调用)
使用new
调用构造函数时,this
指向新创建的实例对象。
function Person(name) {this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // 输出 'Alice'(this指向新实例)
5.箭头函数
箭头函数没有自己的this
,其this
继承自外层作用域。
const obj = {name: 'Charlie',greet: () => {console.log(this.name); // this继承自外层(可能是window)}
};
obj.greet(); // 输出 undefined(箭头函数不绑定obj)
6.事件处理函数
在DOM事件处理函数中,this
通常指向触发事件的元素。
button.addEventListener('click', function() {console.log(this); // 指向button元素
});
7.总结
- 默认绑定:独立调用时指向全局对象(严格模式为
undefined
)。 - 隐式绑定:方法调用时指向调用对象。
- 显式绑定:通过
call
/apply
/bind
指定this
。 - new绑定:构造函数中指向新实例。
- 箭头函数:继承外层
this
。 - 事件处理:指向触发事件的DOM元素。
九、什么是IIFE
IIFE(Immediately Invoked Function Expression)即立即调用的函数表达式,是一种在定义后立即执行的JavaScript函数。这种模式常用于创建独立的作用域,避免变量污染全局命名空间。
1. IIFE的基本语法
典型的IIFE语法如下:
(function() {// 函数体
})();
或者使用箭头函数:
(() => {// 函数体
})();
2. IIFE的特点
- 立即执行:函数定义后会被立即调用,无需手动调用。
- 作用域隔离:函数内部声明的变量不会泄露到全局作用域。
- 匿名函数:通常使用匿名函数,但也可以命名(较少见)。
3. IIFE的常见用途
-
避免全局污染:将代码封装在局部作用域中,防止变量冲突。
(function() {var localVar = '局部变量';console.log(localVar); // 仅在IIFE内有效 })();
-
模块化开发:在早期JavaScript中用于模拟模块化(ES6之前)。
var module = (function() {var privateVar = '私有变量';return {publicMethod: function() {console.log(privateVar);}}; })(); module.publicMethod(); // 访问公开方法
-
闭包应用:结合闭包保存状态。
var counter = (function() {var count = 0;return function() {return ++count;}; })(); console.log(counter()); // 1 console.log(counter()); // 2
4. IIFE的变体
-
带参数的IIFE:可以传递外部变量。
(function(window) {window.myApp = {}; })(window);
-
返回值:将结果赋值给变量。
var result = (function(a, b) {return a + b; })(1, 2); console.log(result); // 3
5. 注意事项
-
分号前置:若IIFE出现在脚本中未以分号结尾的行后,可能导致错误。建议在IIFE前加分号。
;(function() {// 安全执行 })();
-
箭头函数IIFE:ES6后可用箭头函数简化,但需注意其词法作用域特性。
(() => {console.log('箭头函数IIFE'); })();
十、描述null
和undefined
的区别
1.定义
null
表示一个空值或无对象引用,通常由开发者显式赋值。
undefined
表示变量已声明但未赋值,或访问对象不存在的属性,由 JavaScript 引擎自动分配。
2.类型差异
typeof null
返回 "object"
,这是 JavaScript 的历史遗留问题。
typeof undefined
返回 "undefined"
,明确表示未定义的状态。
3.使用场景
null
常用于主动释放对象引用或表示空值。
undefined
常见于未初始化的变量、函数无返回值或参数未传递时。
4.相等性比较
null == undefined
返回 true
(宽松相等)。
null === undefined
返回 false
(严格相等,类型不同)。
5.默认行为
未赋值的变量默认为 undefined
。
null
必须显式赋值,例如 let value = null;
。
6.示例
let a; // a 为 undefined
let b = null; // b 为 nullconsole.log(a === undefined); // true
console.log(b === null); // true
console.log(null == undefined); // true
console.log(null === undefined);// false