JavaScript核心深入与基础拓展
本章将深入探讨JavaScript的核心机制,包括作用域、闭包、this指向、原型链、执行上下文、事件循环等关键概念,以及DOM操作和BOM相关的实用知识。
一、核心概念深入
1.1 作用域与闭包
词法作用域
核心概念:作用域
什么是作用域?
作用域是变量和函数的可访问范围。它决定了代码中变量在哪里可以被访问,在哪里不可见。JavaScript采用词法作用域(也叫静态作用域),意味着函数的作用域在函数定义时就确定了,而不是在调用时确定。
作用域的类型:
| 类型 |
说明 |
示例 |
| 全局作用域 |
在任何地方都能访问的变量 |
在顶层代码中声明的变量 |
| 函数作用域 |
只在函数内部可访问 |
函数内用var声明的变量 |
| 块级作用域 |
只在代码块内可访问(ES6) |
let、const在{}内声明的变量 |
词法作用域示例:
let globalVar = "全局变量";
function outer() {
let outerVar = "外部变量";
function inner() {
let innerVar = "内部变量";
console.log(innerVar);
console.log(outerVar);
console.log(globalVar);
}
inner();
}
outer();
作用域链:
当访问一个变量时,JavaScript引擎会沿着作用域链向外查找,直到找到该变量或到达全局作用域。
let name = "全局";
function fn1() {
let name = "fn1";
function fn2() {
let name = "fn2";
console.log(name);
}
fn2();
}
fn1();
闭包的定义与应用场景
核心概念:闭包
什么是闭包?
闭包是指有权访问另一个函数作用域中变量的函数。简单来说,当一个函数"记住"并可以访问它被创建时所在的词法作用域时,就产生了闭包。即使该函数在其词法作用域之外执行,它仍然可以访问那个作用域中的变量。
闭包的形成条件:
存在函数嵌套
内部函数引用了外部函数的变量
内部函数被传递到外部调用
闭包的基本示例:
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment());
console.log(counter.increment());
console.log(counter.getCount());
闭包的应用场景:
1. 数据私有化(封装)
function createPerson(name) {
let _age = 0;
return {
getName: () => name,
getAge: () => _age,
setAge: (age) => {
if (age > 0 && age < 150) {
_age = age;
}
}
};
}
const person = createPerson("张三");
person.setAge(25);
console.log(person.getAge());
2. 函数工厂
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5));
console.log(triple(5));
3. 回调函数与事件处理
function setupButtons() {
for (let i = 1; i <= 3; i++) {
setTimeout(() => {
console.log("按钮" + i);
}, i * 1000);
}
}
闭包的注意事项:
闭包会持有对外部变量的引用,可能导致内存泄漏
在循环中使用闭包时,注意var和let的区别
及时释放不再需要的闭包引用
1.2 this 指向
核心概念:this
什么是this?
this是函数执行时的上下文对象,它的值取决于函数是如何被调用的,而不是在哪里定义的。理解this的绑定规则是掌握JavaScript的关键之一。
默认绑定、隐式绑定、显式绑定
this绑定的四种规则:
| 绑定类型 |
触发场景 |
this指向 |
| 默认绑定 |
独立调用函数 |
全局对象(严格模式下是undefined) |
| 隐式绑定 |
作为对象的方法调用 |
调用该方法的对象 |
| 显式绑定 |
使用call/apply/bind |
指定的对象 |
| new绑定 |
使用new调用构造函数 |
新创建的对象 |
默认绑定:
function showThis() {
console.log(this);
}
showThis();
"use strict";
function showThisStrict() {
console.log(this);
}
showThisStrict();
隐式绑定:
const person = {
name: "张三",
greet: function() {
console.log("你好,我是" + this.name);
}
};
person.greet();
const greet = person.greet;
greet();
显式绑定(call、apply、bind):
const person1 = { name: "张三" };
const person2 = { name: "李四" };
function greet(greeting, punctuation) {
console.log(greeting + ",我是" + this.name + punctuation);
}
greet.call(person1, "你好", "!");
greet.apply(person2, ["欢迎", "~"]);
const greetZhang = greet.bind(person1, "早安");
greetZhang("!");
| 方法 |
执行时机 |
参数形式 |
返回值 |
| call() |
立即执行 |
逐个传递 |
函数返回值 |
| apply() |
立即执行 |
数组形式 |
函数返回值 |
| bind() |
返回新函数 |
逐个传递 |
新函数 |
箭头函数的 this
箭头函数没有自己的this,它会捕获定义时外层作用域的this值(词法this)。
const person = {
name: "张三",
greetRegular: function() {
console.log("普通函数:" + this.name);
},
greetArrow: () => {
console.log("箭头函数:" + this.name);
},
greetCorrect: function() {
const inner = () => {
console.log("正确用法:" + this.name);
};
inner();
}
};
person.greetRegular();
person.greetArrow();
person.greetCorrect();
箭头函数this的最佳实践:
不要用箭头函数定义对象的方法
在回调函数中使用箭头函数可以保持外层this
箭头函数不能用作构造函数(不能用new调用)
1.3 原型与原型链
核心概念:原型
什么是原型?
原型是JavaScript实现继承的机制。每个JavaScript对象在创建时会关联另一个对象,这个对象就是它的原型。当我们访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript引擎会沿着原型链向上查找。
__proto__ 与 prototype
关键概念:
| 属性 |
所属 |
作用 |
| prototype |
构造函数 |
定义由该构造函数创建的实例的共享属性和方法 |
| __proto__ |
对象实例 |
指向创建该对象的构造函数的prototype |
| constructor |
prototype对象 |
指向关联的构造函数 |
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`你好,我是${this.name}`);
};
const person1 = new Person("张三", 25);
const person2 = new Person("李四", 30);
person1.sayHello();
person2.sayHello();
console.log(person1.__proto__ === Person.prototype);
console.log(Person.prototype.constructor === Person);
console.log(person1 instanceof Person);
原型链示意图:
person1实例
→
Person.prototype
→
Object.prototype
→
null
console.log(person1.name);
console.log(person1.sayHello);
console.log(person1.toString);
console.log(person1.xxx);
继承机制
ES5原型链继承:
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + "正在吃东西");
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(this.name + "正在汪汪叫");
};
const dog = new Dog("旺财", "金毛");
dog.eat();
dog.bark();
ES6 class继承(语法糖):
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + "正在吃东西");
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(this.name + "正在汪汪叫");
}
}
const dog = new Dog("旺财", "金毛");
dog.eat();
dog.bark();
1.4 执行上下文与调用栈
核心概念:执行上下文
什么是执行上下文?
执行上下文是JavaScript代码执行时的环境抽象概念。每当JavaScript引擎执行代码时,都会创建一个执行上下文。执行上下文决定了代码可以访问哪些变量、函数和对象。
变量提升
在代码执行前,JavaScript引擎会先进行变量提升:将变量声明和函数声明提升到作用域顶部。
console.log(a);
var a = 10;
foo();
function foo() {
console.log("hello");
}
var bar = function() {
console.log("world");
};
let和const不会提升(暂时性死区):
let b = 20;
console.log(b);
执行上下文创建阶段
执行上下文的生命周期:
创建阶段完成的工作:
1. 创建变量对象(Variable Object)
- 收集var声明的变量(初始化为undefined)
- 收集函数声明
- 收集let/const声明(uninitialized)
2. 建立作用域链
3. 确定this的值
function test(a, b) {
var c = 10;
function d() {}
var e = function() {};
}
调用栈:
调用栈是JavaScript引擎追踪函数执行状态的栈结构。每当函数被调用,就会创建一个新的执行上下文并压入栈;函数执行完毕后,执行上下文从栈中弹出。
function first() {
console.log("first开始");
second();
console.log("first结束");
}
function second() {
console.log("second开始");
third();
console.log("second结束");
}
function third() {
console.log("third执行");
}
first();
1.5 事件循环(初步)
核心概念:事件循环
什么是事件循环?
JavaScript是单线程语言,意味着同一时间只能执行一个任务。事件循环是JavaScript实现异步编程的核心机制,它允许JavaScript在等待某些操作完成时(如网络请求、定时器),继续执行其他代码。
同步与异步
同步代码:按顺序执行,前一个任务完成后才执行下一个。
异步代码:不会阻塞后续代码执行,在将来某个时间点执行。
console.log("1. 开始");
setTimeout(() => {
console.log("2. 定时器回调");
}, 0);
console.log("3. 结束");
宏任务与微任务概念
任务队列分类:
| 类型 |
包含 |
执行时机 |
| 宏任务(Macro Task) |
setTimeout、setInterval、setImmediate、I/O、UI渲染 |
每次事件循环执行一个 |
| 微任务(Micro Task) |
Promise.then/catch/finally、process.nextTick、MutationObserver |
在当前宏任务结束后立即执行所有微任务 |
console.log("1. 脚本开始");
setTimeout(() => {
console.log("2. setTimeout");
}, 0);
Promise.resolve()
.then(() => {
console.log("3. Promise 1");
})
.then(() => {
console.log("4. Promise 2");
});
console.log("5. 脚本结束");
事件循环流程图:
执行同步代码
→
检查微任务队列
→
执行所有微任务
→
UI渲染
→
执行一个宏任务
→
循环...
1.6 严格模式
"use strict" 的作用与限制:
严格模式通过在脚本或函数开头添加 "use strict" 来启用,它可以帮助你写出更安全、更规范的代码。
| 限制项 |
说明 |
示例 |
| 禁止意外创建全局变量 |
未声明的变量不能赋值 |
a = 10; // 报错 |
| 禁止this指向全局对象 |
独立调用的函数中this为undefined |
function f() { console.log(this); } |
| 禁止重复参数名 |
函数参数不能重名 |
function f(a, a) {} // 报错 |
| 禁止删除不可删除的属性 |
delete不能删除变量、函数等 |
delete Object.prototype; // 报错 |
| 禁止八进制字面量 |
不能使用0开头的八进制数 |
var n = 010; // 报错 |
| 禁止with语句 |
with语句会使代码难以优化 |
with(obj) {} // 报错 |
| 保留字限制 |
不能使用未来可能的关键字 |
var let = 10; // 报错 |
"use strict";
let x = 10;
function showThis() {
console.log(this);
}
showThis();
const obj = {};
Object.defineProperty(obj, "name", { value: "张三", writable: false });
严格模式的优点:
消除JavaScript中一些不安全、不合理的语法
提高编译器效率,增加运行速度
为未来新版本的JavaScript做铺垫
帮助发现潜在的错误
二、DOM 操作与 BOM
2.1 DOM 基础
核心概念:DOM
什么是DOM?
DOM(Document Object Model,文档对象模型)是HTML文档的编程接口。它将HTML文档表示为一个节点树,JavaScript可以通过DOM API来访问和操作文档的内容、结构和样式。
节点树与节点类型
DOM节点类型:
| 节点类型 |
nodeType值 |
说明 |
| 元素节点 |
1 |
HTML元素,如 <div>、<p> |
| 属性节点 |
2 |
元素的属性,如 class、id |
| 文本节点 |
3 |
元素中的文本内容 |
| 注释节点 |
8 |
HTML注释 |
| 文档节点 |
9 |
整个文档(document) |
const div = document.querySelector("div");
console.log(div.nodeType);
console.log(div.nodeName);
获取元素
常用获取元素的方法:
const elemById = document.getElementById("myId");
const elemsByClass = document.getElementsByClassName("myClass");
const elemsByTag = document.getElementsByTagName("div");
const elem = document.querySelector(".myClass");
const elem2 = document.querySelector("#myId");
const elem3 = document.querySelector("div.container > p");
const elems = document.querySelectorAll(".myClass");
| 方法 |
返回类型 |
特点 |
| getElementById |
Element |
最快,只能用ID |
| getElementsByClassName |
HTMLCollection(动态) |
实时更新 |
| getElementsByTagName |
HTMLCollection(动态) |
实时更新 |
| querySelector |
Element |
CSS选择器,返回第一个 |
| querySelectorAll |
NodeList(静态) |
CSS选择器,返回所有 |
遍历节点
const elem = document.querySelector(".container");
elem.parentNode;
elem.parentElement;
elem.childNodes;
elem.children;
elem.firstChild;
elem.firstElementChild;
elem.lastChild;
elem.lastElementChild;
elem.previousSibling;
elem.previousElementSibling;
elem.nextSibling;
elem.nextElementSibling;
2.2 操作 DOM
修改内容
const elem = document.getElementById("content");
console.log(elem.innerHTML);
elem.innerHTML = "<strong>新内容</strong>";
console.log(elem.textContent);
elem.textContent = "纯文本内容";
innerHTML vs textContent:
innerHTML:解析HTML标签,有XSS风险,性能较低
textContent:纯文本,更安全,性能更好
修改属性
const img = document.querySelector("img");
img.getAttribute("src");
img.src;
img.setAttribute("src", "new.jpg");
img.src = "new.jpg";
img.hasAttribute("alt");
img.removeAttribute("title");
img.dataset.id;
img.dataset.userName;
img.dataset.id = "123";
修改样式
const box = document.getElementById("box");
box.style.width = "200px";
box.style.backgroundColor = "red";
box.style.cssText = "width: 200px; height: 100px;";
box.classList.add("active");
box.classList.remove("active");
box.classList.toggle("active");
box.classList.contains("active");
box.classList.replace("old", "new");
box.className = "box active highlight";
| 方法 |
适用场景 |
| style属性 |
动态计算样式、少量样式修改 |
| classList |
切换状态、添加/移除类(推荐) |
| className |
完全替换所有类名 |
创建、插入、删除元素
const div = document.createElement("div");
div.id = "newDiv";
div.textContent = "新创建的div";
const text = document.createTextNode("文本内容");
const container = document.getElementById("container");
container.appendChild(div);
container.insertBefore(div, container.firstChild);
container.append(div);
container.prepend(div);
container.after(div);
container.before(div);
container.removeChild(div);
div.remove();
const clone = div.cloneNode();
const cloneDeep = div.cloneNode(true);
2.3 事件处理
核心概念:事件
什么是事件?
事件是用户或浏览器执行的某种动作,如点击、鼠标移动、键盘按键等。JavaScript可以通过事件监听器来响应这些动作,实现交互功能。
事件监听
const btn = document.getElementById("myBtn");
btn.addEventListener("click", function(event) {
console.log("按钮被点击了");
console.log(event);
});
btn.onclick = function() {
console.log("点击");
};
function handleClick() {
console.log("点击");
}
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);
| 方式 |
优点 |
缺点 |
| addEventListener |
可添加多个监听器、可移除、支持事件捕获 |
语法稍复杂 |
| onclick |
语法简单 |
只能绑定一个、无法移除、不支持捕获 |
事件对象
btn.addEventListener("click", function(e) {
e.target;
e.currentTarget;
e.type;
e.timeStamp;
e.clientX;
e.clientY;
e.pageX;
e.pageY;
e.key;
e.keyCode;
});
事件流(捕获、冒泡)
事件传播的三个阶段
1. 捕获阶段:事件从window向下传播到目标元素
2. 目标阶段:事件到达目标元素
3. 冒泡阶段:事件从目标元素向上传播到window
elem.addEventListener("click", handler, false);
elem.addEventListener("click", handler, true);
事件委托
什么是事件委托?
事件委托是利用事件冒泡机制,将事件监听器绑定在父元素上,通过判断event.target来处理子元素的事件。这样可以减少事件监听器的数量,提高性能。
document.querySelectorAll("li").forEach(li => {
li.addEventListener("click", handler);
});
document.querySelector("ul").addEventListener("click", function(e) {
if (e.target.tagName === "LI") {
console.log("点击了:" + e.target.textContent);
}
});
事件委托的优点:
减少内存消耗(只需一个监听器)
动态添加的元素也能响应事件
代码更简洁
阻止默认行为与传播
elem.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
});
document.querySelector("a").addEventListener("click", function(e) {
e.preventDefault();
console.log("链接被点击,但不会跳转");
});
2.4 BOM(浏览器对象模型)
核心概念:BOM
什么是BOM?
BOM(Browser Object Model,浏览器对象模型)提供了与浏览器窗口交互的对象和方法。BOM的核心是window对象,它既是全局对象,也是浏览器窗口的接口。
window 对象
var globalVar = "全局变量";
console.log(window.globalVar);
const timerId = setTimeout(() => {
console.log("延迟执行");
}, 1000);
clearTimeout(timerId);
const intervalId = setInterval(() => {
console.log("重复执行");
}, 1000);
clearInterval(intervalId);
alert("提示信息");
const result = confirm("确定吗?");
const name = prompt("请输入姓名:");
window.open("https://example.com");
window.close();
window.scrollTo(0, 0);
window.print();
navigator 对象
navigator.userAgent;
navigator.language;
navigator.platform;
navigator.onLine;
navigator.cookieEnabled;
navigator.geolocation.getCurrentPosition(function(position) {
console.log("纬度:" + position.coords.latitude);
console.log("经度:" + position.coords.longitude);
});
location 对象
location.href;
location.protocol;
location.host;
location.hostname;
location.port;
location.pathname;
location.search;
location.hash;
location.href = "https://example.com";
location.assign("https://example.com");
location.replace("https://example.com");
location.reload();
location.reload(true);
history 对象
history.length;
history.back();
history.forward();
history.go(-1);
history.go(1);
history.pushState({page: 1}, "title", "/page1");
history.replaceState({page: 2}, "title", "/page2");
window.addEventListener("popstate", function(e) {
console.log(e.state);
});
screen 对象
screen.width;
screen.height;
screen.availWidth;
screen.availHeight;
screen.colorDepth;
screen.pixelDepth;
本地存储
本地存储对比
| 特性 |
localStorage |
sessionStorage |
cookie |
| 存储大小 |
约5MB |
约5MB |
约4KB |
| 生命周期 |
永久存储 |
会话期间(关闭标签页清除) |
可设置过期时间 |
| 发送到服务器 |
否 |
否 |
是(每次请求都发送) |
| 作用域 |
同源窗口共享 |
仅当前标签页 |
同源窗口共享 |
localStorage.setItem("name", "张三");
localStorage.getItem("name");
localStorage.removeItem("name");
localStorage.clear();
const user = { name: "张三", age: 25 };
localStorage.setItem("user", JSON.stringify(user));
const savedUser = JSON.parse(localStorage.getItem("user"));
document.cookie = "name=张三; expires=Fri, 31 Dec 2025 23:59:59 GMT; path=/";
document.cookie;
三、本章小结
核心知识点回顾:
核心概念深入:
作用域与闭包:词法作用域决定了变量的可访问范围,闭包是函数"记住"其词法作用域的能力
this指向:默认绑定、隐式绑定、显式绑定、new绑定四种规则,箭头函数没有自己的this
原型与原型链:__proto__指向原型对象,prototype是构造函数的属性,原型链实现继承
执行上下文:变量提升、执行上下文创建阶段、调用栈管理函数调用
事件循环:同步任务在主线程执行,异步任务通过事件循环处理,宏任务和微任务有不同优先级
严格模式:消除不合理语法、提高性能、为未来版本铺路
DOM操作与BOM:
DOM基础:节点树结构、获取元素方法、遍历节点
操作DOM:修改内容、属性、样式,创建/插入/删除元素
事件处理:事件监听、事件对象、事件流、事件委托
BOM:window、navigator、location、history、screen对象,本地存储
学习建议:
1. 深入理解JavaScript的核心机制,这些是框架和高级特性的基础
2. 多动手实践DOM操作,这是前端开发的基本功
3. 理解事件机制对于构建交互式应用至关重要
4. 闭包和原型链是面试高频考点,务必掌握