字符串提取方法
slice(start, end)
提取从start到end-1的字符,支持负数索引(从末尾计算)。
代码示例:
let str = "Hello World";
console.log(str.slice(0, 5)); // "Hello"
console.log(str.slice(-5)); // "World"
substring(start, end)
类似slice但不支持负数索引,自动交换start和end若start > end。
代码示例:
let str = "Hello World";
console.log(str.substring(6, 11)); // "World"
console.log(str.substring(11, 6)); // "World"(自动交换参数)
其他字符串操作方法
replace(searchValue, replaceValue)
替换匹配的字符串或正则表达式。
代码示例:
let str = "Hello World";
console.log(str.replace("World", "JavaScript")); // "Hello JavaScript"
toUpperCase() / toLowerCase()
转换大小写
代码示例:
let str = "Hello";
console.log(str.toUpperCase()); // "HELLO"
console.log(str.toLowerCase()); // "hello"
concat(str2, …, strN)
拼接字符串(通常直接用+更简洁)。
代码示例:
let str1 = "Hello";
let str2 = "World";
console.log(str1.concat(" ", str2)); // "Hello World"
String.trim()
去除首尾空格。
代码示例:
let str = " Hello ";
console.log(str.trim()); // "Hello"
字符访问方法
charAt(position)
返回指定位置的字符。
代码示例:
let str = "Hello";
console.log(str.charAt(1)); // "e"
charCodeAt(position)
返回字符的Unicode编码。
代码示例:
let str = "Hello";
console.log(str.charCodeAt(1)); // 101('e'的编码)
字符串分割
split(separator)
按分隔符拆分为数组。
代码示例:
let str = "apple,orange,banana";
console.log(str.split(",")); // ["apple", "orange", "banana"]
搜索字符串js方法
String.match()
match() 方法用于在字符串中搜索与正则表达式匹配的内容,返回一个数组或 null。
const str = "Hello, world!";
const regex = /world/;
const result = str.match(regex);
console.log(result); // ["world", index: 7, input: "Hello, world!", groups: undefined]
String.includes()
includes() 方法检查字符串是否包含指定的子字符串,返回 true 或 false。
const str = "Hello, world!";
const substring = "world";
const result = str.includes(substring);
console.log(result); // true
String.startsWith()
startsWith() 方法检查字符串是否以指定的子字符串开头,返回 true 或 false。
const str = "Hello, world!";
const prefix = "Hello";
const result = str.startsWith(prefix);
console.log(result); // true
String.endsWith()
endsWith() 方法检查字符串是否以指定的子字符串结尾,返回 true 或 false。
const str = "Hello, world!";
const suffix = "world!";
const result = str.endsWith(suffix);
console.log(result); // true
字符串模板
let firstName = "Bill";
let lastName = "Gates";
let text = `Welcome ${firstName}, ${lastName}!`;
JavaScript 数字运算
JavaScript 中浮点运算存在精度问题,这是由于 IEEE 754 标准的二进制浮点数表示方式导致的。例如:
var x = 0.2 + 0.1; // 结果为 0.30000000000000004
使用乘除法可以规避精度问题:
var x = (0.2 * 10 + 0.1 * 10) / 10; // 结果为 0.3
数据类型与运算规则
数字相加结果为算术和:
var x = 10 + 5; // 结果为 15
字符串相加结果为级联:
var x = "10" + "5"; // 结果为 "105"
数字与字符串相加会转换为字符串级联:
var x = 10 + "5"; // 结果为 "105"
var y = "5" + 10; // 结果为 "510"
减乘除运算规则
减、乘、除运算会强制将字符串转换为数字:
var x = "100" – "10"; // 结果为 90
var y = "100" * "10"; // 结果为 1000
var z = "100" / "10"; // 结果为 10
特殊数值处理
NaN(非数值)
非数字字符串运算会返回 NaN:
var x = "Hello" / 10; // 结果为 NaN
Infinity
超出数值范围或除以零会返回 Infinity:
var x = 2 / 0; // 结果为 Infinity
var y = -2 / 0; // 结果为 -Infinity
Infinity 的类型是数字:
typeof Infinity; // 返回 "number"
进阶01
函数作用域
函数内部声明的变量(包括参数)仅能在函数内部访问,外部无法直接调用。函数执行结束后,其内部变量会被销毁。不同函数之间的变量相互隔离,无法跨函数访问。
块作用域
通过 {} 包裹的代码块会形成块级作用域,其内部变量的可访问性取决于声明方式:
- let 和 const:声明的变量或常量具有块作用域,仅在当前代码块内有效,外部无法访问。
- var:声明的变量无视块作用域,仍可在代码块外部访问。
不同代码块之间的变量(使用 let 或 const 声明)相互隔离,无法跨块访问。
关键区别
- 作用域范围:函数作用域针对整个函数体,块作用域仅限 {} 内的代码。
- 变量声明影响:var 声明的变量受函数作用域约束,但不受块作用域限制;let/const 受块作用域约束。、
全局作用域的定义
全局作用域指在 script 标签或 .js 文件的最外层声明的变量或函数,它们可以在代码的任何位置被访问,包括函数内部或其他作用域。
全局变量的特点
- 全局变量会挂载到 window 对象上(浏览器环境)。
- 未使用任何关键字(如 var、let、const)直接赋值的变量会成为全局变量。
- 动态为 window 对象添加的属性也会成为全局变量。
不推荐的全局变量声明方式
// 1. 动态为 window 对象添加属性(不推荐)
window.globalVar1 = "动态添加的全局变量";
// 2. 函数内部未使用关键字声明的变量(不推荐)
function setGlobalVar() {
globalVar2 = "未声明直接赋值的全局变量";
}
setGlobalVar();
console.log(globalVar1); // "动态添加的全局变量"
console.log(globalVar2); // "未声明直接赋值的全局变量"
推荐的变量声明方式
// 使用 const 或 let 声明全局变量(推荐)
const GLOBAL_CONST = "常量全局变量";
let globalLet = "可变的全局变量";
// 函数内部访问全局变量
function accessGlobal() {
console.log(GLOBAL_CONST); // "常量全局变量"
console.log(globalLet); // "可变的全局变量"
}
accessGlobal();
避免全局变量污染的方法
- 使用模块化(如 ES6 Modules)减少全局变量。
- 使用立即执行函数(IIFE)封装作用域。
- 优先使用 const 和 let 代替 var。
// 使用 IIFE 限制作用域
(function() {
const localVar = "局部变量,不影响全局";
console.log(localVar);
})();
作用域链的核心机制
作用域链是JavaScript中变量查找的底层规则,其运行机制遵循以下原则:
当前函数作用域优先查找
函数执行时,会先在自身作用域内查找变量。若存在则直接使用,停止查找。
逐级向上查找父级作用域
若当前作用域未找到变量,会依次向上一层作用域(父级)查找,直至全局作用域。若全局作用域仍未找到,则报错。
作用域链的关键特性
嵌套串联形成链式结构
函数或块级作用域的嵌套关系会串联成链式结构,形成作用域链。例如内层函数可访问外层函数的变量。
查找规则从小到大的层级顺序
变量查找严格按照从当前作用域向父级作用域的层级顺序,不可逆向或跨级。
单向访问权限
子作用域可访问父作用域的变量,但父作用域无法访问子作用域的变量。这种单向性保证了作用域的隔离性。
内存的生命周期
JavaScript中内存的生命周期分为三个阶段:
内存分配
当变量、对象或函数被创建时,系统会自动分配内存。例如:
let obj = { name: "example" }; // 对象占用堆内存
let arr = [1, 2, 3]; // 数组占用堆内存
内存使用
分配的内存被读取或修改。例如:
obj.name = "updated"; // 修改对象属性
arr.push(4); // 修改数组
内存回收
不再使用的内存会被垃圾回收机制(Garbage Collection, GC)自动回收。
回收策略包括:
- 标记清除(Mark-and-Sweep):从根对象(如全局变量)出发,标记所有可达对象,未被标记的内存会被回收。
- 引用计数(Reference Counting):记录对象的引用次数,当计数归零时回收。此方式易导致循环引用问题,现代浏览器已弃用。
变量回收规则
全局变量
除非关闭页面或刷新,否则不会被回收。例如:
window.globalVar = "persistent"; // 需手动解除引用(globalVar = null)才能回收
局部变量
函数执行完毕后,若变量未被闭包或外部引用,则会被回收。例如:
function temporary() {
let localVar = "temp"; // 函数结束后可回收
}
内存泄漏的常见场景
未清理的引用
DOM 移除后仍保留引用导致内存泄漏:
let button = document.getElementById("myButton");
document.body.removeChild(button);
// 若未执行 button = null,内存可能未被释放
闭包误用
意外保留外部变量引用:
function leak() {
let largeData = new Array(1000000).fill("data");
return function() { console.log("leak"); };
// largeData 被闭包间接引用,无法回收
}
定时器或事件监听未清除
let interval = setInterval(() => {}, 1000);
clearInterval(interval); // 未清除会导致回调函数持续占用内存
闭包的概念理解
闭包(Closure)是 JavaScript 中函数的一种特性,指内层函数引用外层函数变量的组合。闭包的形成需要满足以下条件:
- 存在函数嵌套(内层函数定义在外层函数内部)。
- 内层函数引用了外层函数的变量或参数。
- 外层函数返回内层函数(或通过其他方式使内层函数被外部调用)。
闭包的核心是内层函数可以访问外层函数的作用域,即使外层函数已经执行完毕。
闭包的作用
闭包的主要作用包括:
- 数据私有化:通过闭包可以创建私有变量,外部无法直接访问,只能通过闭包暴露的函数间接操作。
- 持久化局部变量:外层函数的变量在调用结束后通常会被销毁,但闭包可以使其保留。
- 模块化开发:闭包是实现模块模式的基础,可以隐藏实现细节,仅暴露必要接口。
闭包可能引发的问题
闭包可能导致内存泄漏,因为被引用的外层函数变量不会被垃圾回收机制释放。以下是一个内存泄漏的示例代码:
function outer() {
const bigData = new Array(1000000).fill('data'); // 占用大量内存的变量
return function inner() {
console.log(bigData.length); // 内层函数引用外层变量
};
}
const leak = outer(); // 执行outer后,bigData本应被销毁,但因闭包保留
// 即使不再需要leak,bigData仍占用内存
leak(); // 调用inner时仍能访问bigData
解决方法
为避免内存泄漏,可以在不需要闭包时手动解除引用:
leak = null; // 解除对inner函数的引用,bigData会被垃圾回收
变量提升(Hoisting)
在 JavaScript 中,变量提升是指变量声明在代码执行前被提升到当前作用域的顶部。需要注意的是,只有声明会被提升,而赋值操作不会。
-
var 声明的变量:变量声明会被提升,初始值为 undefined。
console.log(a); // 输出 undefined
var a = 10; -
let 和 const 声明的变量:虽然也存在提升,但由于暂时性死区(Temporal Dead Zone, TDZ)的限制,在声明前访问会报错。
console.log(b); // 报错:Cannot access 'b' before initialization
let b = 20;
函数提升
函数提升是指函数声明在代码执行前被提升到当前作用域的顶部,包括函数体。函数表达式不会被提升。
-
函数声明:整个函数(包括函数体)会被提升。
foo(); // 输出 "Hello"
function foo() {
console.log("Hello");
} -
函数表达式:只有变量声明会被提升,函数体不会。
bar(); // 报错:bar is not a function
var bar = function() {
console.log("World");
};
提升的优先级
当变量和函数同名时,函数提升的优先级高于变量提升。
console.log(c); // 输出函数 c 的定义
function c() {}
var c = 10;
动态参数(arguments)
arguments是函数内部的一个内置对象,表现为伪数组(类数组对象),包含调用时传入的所有实参。
伪数组具有length属性和索引访问特性,但无法直接使用数组方法(如push、forEach)。
arguments的核心用途是动态获取函数调用时的实参,无需显式定义形参名称。
通过循环遍历arguments对象可以依次处理所有传入的实参,例如:
function showArgs() {
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
showArgs(1, 'a', true); // 输出 1, 'a', true
剩余参数(…rest)
剩余参数语法使用…作为前缀,将多余的实参收集到一个真数组中。
剩余参数必须作为最后一个形参,且一个函数只能有一个剩余参数。
与arguments不同,剩余参数是真正的数组,可直接使用数组方法:
function sum(…numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
sum(1, 2, 3); // 返回 6
展开运算符(…)
与剩余参数语法相同,但用途相反:将数组或可迭代对象展开为独立元素。
常用于函数调用、数组拼接或对象属性拷贝:
const arr1 = [1, 2];
const arr2 = […arr1, 3]; // [1, 2, 3]
Math.max(…[10, 20, 5]); // 展开为 Math.max(10, 20, 5)
箭头函数的基本语法
箭头函数(Arrow Function)是ES6引入的一种简化函数定义的语法形式。其基本语法如下:
const func = (param1, param2) => {
// 函数体
return result;
};
如果函数体只有一行且需要返回结果,可以省略大括号和return:
const sum = (a, b) => a + b;
如果只有一个参数,可以省略参数的小括号:
const square = x => x * x;
箭头函数的特点
1.没有自己的this
箭头函数不会创建自己的this,而是继承外层作用域的this。这使得它在回调函数中特别有用,避免了传统函数中this指向混乱的问题。
const obj = {
name: "Alice",
greet: function() {
setTimeout(() => {
console.log(`Hello, ${this.name}!`);
}, 1000);
}
};
obj.greet(); // 输出 "Hello, Alice!"
2.没有arguments对象
箭头函数没有自己的arguments对象,但可以通过剩余参数(rest parameters)实现类似功能:
const showArgs = (…args) => console.log(args);
showArgs(1, 2, 3); // 输出 [1, 2, 3]
3.不能作为构造函数
箭头函数不能使用new调用,因为它没有prototype属性:
const Foo = () => {};
new Foo(); // 报错:Foo is not a constructor
注意事项
不适合用于对象方法
如果对象方法需要访问自身的this,使用传统函数更合适:
const person = {
name: "Bob",
sayName: function() {
console.log(this.name);
}
};
不能用于原型方法
箭头函数无法通过prototype扩展,因此不适合作为原型方法:
function Person(name) {
this.name = name;
}
Person.prototype.sayName = () => {
console.log(this.name); // this指向外层,而非实例
};
解构赋值
解构赋值允许从数组或对象中提取值并赋值给变量。
需要加分号的两种情况
;[x, y] = [1, 2] // 避免与上一行代码合并解析
数组解构
通过模式匹配从数组中提取值:
const [a, b] = [1, 2]; // a=1, b=2
let a = 1
;[a, b] = [2, 3] // 避免语法歧义
多维数组解构
嵌套解构可用于多维数组:
const [a, [b, c]] = [1, [2, 3]]; // a=1, b=2, c=3
对象解构
通过属性名提取对象的值:
const { name, age } = { name: 'Alice', age: 25 }; // name='Alice', age=25
多级对象解构
嵌套解构可用于多级对象:
const { user: { id, info: { email } } } = { user: { id: 1, info: { email: 'a@b.com' } } };
// id=1, email='a@b.com'
遍历数组 forEach
对数组每个元素执行函数,无返回值:
arr.forEach((item, index, array) => { /* 操作 */ });
筛选数组 filter
返回满足条件的新数组:
const newArr = arr.filter((item, index, array) => item > 2);
数组方法语法格式
进阶02
创建对象的三种不同方法
- map:返回处理后的新数组
arr.map((item, index, array) => { return 新值 });
- reduce:累加和
arr.reduce((acc, item, index, array) => { return 累计值 }, 初始值);
- find:返回第一个匹配项
arr.find((item, index, array) => item === 目标值);
- some/every:检测是否至少一项/全部项 满足条件
arr.some((item) => item > 0);
arr.every((item) => item > 0);e.target 属性解析
在 JavaScript 事件处理中,e.target 是事件对象的一个关键属性,表示触发事件的 DOM 元素。以下是相关属性的详细说明和使用方法:
获取触发事件的元素
e.target 返回触发事件的原始元素。例如,点击按钮时,e.target 指向该按钮的 DOM 节点:
document.addEventListener('click', function(e) {
console.log(e.target); // 输出被点击的元素
});
获取元素的标签名
e.target.tagName 返回元素的标签名(大写形式)。例如,点击 <button> 会返回 "BUTTON":
document.addEventListener('click', function(e) {
console.log(e.target.tagName); // 如 "DIV"、"BUTTON"
});
访问自定义数据属性
e.target.dataset 提供对元素上 data-* 属性的访问,返回一个对象。例如:
<div id="item" data-id="123" data-category="example"></div>
document.getElementById('item').addEventListener('click', function(e) {
console.log(e.target.dataset.id); // "123"
console.log(e.target.dataset.category); // "example"
});事件委托
-
事件委托:通过 e.target 识别子元素触发的事件。
document.querySelector('ul').addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
console.log('列表项被点击');
}
}); -
动态数据绑定:利用 dataset 传递数据。
<button data-action="delete" data-item-id="42">删除</button>
document.querySelector('button').addEventListener('click', function(e) {
const action = e.target.dataset.action; // "delete"
const id = e.target.dataset.itemId; // "42"
});
1.利用对象字面量创建对象
对象字面量是最简单直观的创建对象的方式,直接通过键值对定义属性和方法。
const person = {
name: 'Alice',
age: 25,
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // 输出: Hello, my name is Alice
2.利用 new Object 创建对象
通过 new Object() 构造函数创建空对象,再动态添加属性和方法。
const person = new Object();
person.name = 'Bob';
person.age = 30;
person.greet = function() {
console.log(`Hi, I'm ${this.name}`);
};
person.greet(); // 输出: Hi, I'm Bob
3.利用构造函数创建对象
通过自定义构造函数批量创建具有相同结构的对象,通常构造函数名首字母大写。
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log(`My name is ${this.name}, age ${this.age}`);
};
}
const alice = new Person('Alice', 25);
const bob = new Person('Bob', 30);
alice.greet(); // 输出: My name is Alice, age 25
构造函数的特点
构造函数在JavaScript中用于创建对象实例。通常以大写字母开头,通过new关键字调用。构造函数内部通过this绑定属性和方法,自动返回新创建的对象实例。
创建构造函数
以下是一个简单的构造函数示例:
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
}
const person1 = new Person('Alice', 25);
实例成员
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员(实例属性和实例方法)
- 构造函数通过new调用,默认返回新对象实例(this)。
function Person(name) {
this.name = name; // 实例属性
this.sayHello = function() { // 实例方法
console.log(`Hello, ${this.name}`);
};
}
const person1 = new Person('Alice');
person1.sayHello(); // 输出:Hello, Alice
静态成员
构造函数的属性和方法被称为静态成员(静态属性和静态方法)
静态成员只能构造函数来访问,静态方法中的this指向构造函数,比如Date.now() Math.PI()
function Person() {}
Person.species = 'Human'; // 静态属性
Person.greet = function() { // 静态方法
console.log(`Species: ${this.species}`);
};
console.log(Person.species); // 输出:Human
Person.greet(); // 输出:Species: Human
内置构造函数
1.Object
Object.keys()
用于获取对象自身可枚举属性的键名数组,返回一个由字符串组成的数组。
语法
Object.keys(obj)
示例
const obj = { a: 1, b: 2 };
console.log(Object.keys(obj)); // ["a", "b"]
Object.values()
用于获取对象自身可枚举属性的值数组,返回一个由属性值组成的数组。
语法
Object.values(obj)
示例
const obj = { a: 1, b: 2 };
console.log(Object.values(obj)); // [1, 2]
Object.assign()
用于合并多个对象的属性到目标对象,返回修改后的目标对象。
语法
Object.assign(target, …sources)
示例
const target = { a: 1 };
const source = { b: 2 };
const result = Object.assign(target, source);
result === target //true
console.log(result); // { a: 1, b: 2 }
2.Array
forEach 方法
数组的 forEach 方法用于遍历数组的每一项,并对每一项执行回调函数。语法如下:
数组.forEach(function(item, index, arr) {
// 操作逻辑
}, thisArg);
- item 是当前遍历的数组元素。
- index 是当前元素的索引。
- arr 是原数组(可选)。
- thisArg 用于设置回调函数中的 this 指向(可选)。
- 在严格模式(use strict)下,若不指定 thisArg,回调函数中的 this 为 undefined。
filter 方法
filter 方法用于过滤数组,返回满足条件的新数组。语法如下:
const 新数组 = 数组.filter(function(item, index) {
return 条件; // 必须返回布尔值
});
- item 是当前遍历的数组元素。
- index 是当前元素的索引(可选)。
- 回调函数必须返回布尔值,true 表示保留该元素,false 表示过滤掉该元素。
map 方法
map 方法用于对数组的每一项进行转换,并返回转换后的新数组。语法如下:
const 新数组 = 数组.map(function(当前元素, 索引, 原数组) {
return 转换逻辑; // 返回转换后的元素
});
- 当前元素 是当前遍历的数组元素。
- 索引 是当前元素的索引(可选)。
- 原数组 是调用 map 的数组(可选)。
- 回调函数必须返回转换后的值。
reduce 方法
reduce 方法用于将数组中的元素聚合为单个值。语法如下:
const 最终结果 = 数组.reduce(function(累加器, 当前元素, 索引, 原数组) {
return 累加器和当前元素的运算逻辑; // 返回新的累加器值
}, 初始值);
- 累加器 是上一次回调的返回值或初始值。
- 当前元素 是当前遍历的数组元素。
- 索引 是当前元素的索引(可选)。
- 原数组 是调用 reduce 的数组(可选)。
- 初始值 是可选的,若未提供,则默认使用数组的第一个元素作为初始值。
Array.join()
将数组中的所有元素连接成一个字符串。可以指定分隔符,默认为逗号。
const arr = ['a', 'b', 'c'];
console.log(arr.join('-')); // 输出 "a-b-c"
Array.find()
返回数组中满足提供的测试函数的第一个元素的值。如果没有找到则返回 undefined。
const arr = [5, 12, 8, 130, 44];
const found = arr.find(element => element > 10);
console.log(found); // 输出 12
Array.every()
测试数组中的所有元素是否都通过了提供的函数的测试。返回布尔值。
const arr = [1, 30, 39, 29, 10, 13];
const isBelowThreshold = arr.every(element => element < 40);
console.log(isBelowThreshold); // 输出 true
Array.some()
测试数组中是否至少有一个元素通过了提供的函数的测试。返回布尔值。
const arr = [1, 2, 3, 4, 5];
const hasEven = arr.some(element => element % 2 === 0);
console.log(hasEven); // 输出 true
Array.concat()
合并两个或多个数组,返回一个新数组,不会改变原数组。
const arr1 = ['a', 'b', 'c'];
const arr2 = ['d', 'e', 'f'];
const arr3 = arr1.concat(arr2);
console.log(arr3); // 输出 ['a', 'b', 'c', 'd', 'e', 'f']
Array.sort()
对数组的元素进行排序,默认排序顺序是根据字符串Unicode码点。可以提供一个比较函数来自定义排序逻辑。
const arr = ['banana', 'apple', 'orange'];
arr.sort();
console.log(arr); // 输出 ['apple', 'banana', 'orange']
const numbers = [40, 1, 5, 200];
numbers.sort((a, b) => a – b);
console.log(numbers); // 输出 [1, 5, 40, 200]
Array.splice()
通过删除或替换现有元素或者添加新元素来修改数组的内容。会改变原数组。
const arr = ['a', 'b', 'c', 'd'];
arr.splice(1, 2, 'x', 'y');
console.log(arr); // 输出 ['a', 'x', 'y', 'd']
Array.reverse()
反转数组中元素的顺序。会改变原数组。
const arr = ['a', 'b', 'c'];
arr.reverse();
console.log(arr); // 输出 ['c', 'b', 'a']
Array.findIndex()
返回数组中满足提供的测试函数的第一个元素的索引。如果没有找到则返回 -1。
const arr = [5, 12, 8, 130, 44];
const index = arr.findIndex(element => element > 10);
console.log(index); // 输出 1
Array.form()
伪数组切换成真数组
const nodeList = document.querySelectorAll('div');
const divArray = Array.from(nodeList); // 将 NodeList 转换为数组
3.String
上面有
4.Number
返回一个字符串,表示四舍五入后的数字,保留指定的小数位数。
let num = 123.456;
console.log(num.toFixed()); // "123"
console.log(num.toFixed(1)); // "123.5"
console.log(num.toFixed(2)); // "123.46"
console.log(num.toFixed(3)); // "123.456"
console.log(num.toFixed(4)); // "123.4560"
进阶03
面向过程编程:
分析好步骤,按照步骤解决问题
面向对象编程:
以对象功能来划分问题,由对象之间分工与合作
面向对象特性:继承性,封装性,多态性
原型
构造函数与原型对象的关系
构造函数通过prototype属性指向其原型对象。原型对象包含可由该构造函数创建的所有实例共享的属性和方法。
function Person(name) {
this.name = name; // 构造函数中的this指向实例
}
Person.prototype.greet = function() {
console.log(`Hello, ${this.name}!`); // 原型方法中的this也指向实例
};
公共属性写在 构造函数上,公共方法写在 原型对象上
this关键字的指向规则
无论在构造函数内部还是原型方法中,this始终指向当前调用时的实例对象。这种设计确保属性和方法能正确访问实例数据。
const person1 = new Person('Alice');
person1.greet(); // 输出: Hello, Alice! (this指向person1)
原型链的运作机制
当访问实例属性或方法时,JavaScript引擎会:
- 先检查实例自身是否拥有该属性
- 若不存在,则沿着原型链查找原型对象上的属性
console.log(person1.hasOwnProperty('name')); // true (实例自身属性)
console.log(person1.hasOwnProperty('greet')); // false (来自原型)
constructor 属性的作用
在 JavaScript 中,每个原型对象(prototype)都有一个 constructor 属性,该属性指向创建当前对象的构造函数。这一机制确保了对象实例可以通过原型链回溯到其构造函数。
默认的 constructor 关系
当创建一个构造函数时,其原型对象会自动获得 constructor 属性并指向构造函数本身。例如:
function Person(name) {
this.name = name;
}
// 原型对象的 constructor 默认指向 Person
console.log(Person.prototype.constructor === Person); // true
const person1 = new Person('Alice');
// 实例通过原型链访问 constructor
console.log(person1.constructor === Person); // true
手动修改原型时的注意事项
如果手动覆盖原型对象(例如通过 Object.assign 或直接赋值),可能会丢失原有的 constructor 属性。此时需要显式修复:
function Animal(type) {
this.type = type;
}
// 错误示例:直接覆盖原型会丢失 constructor
Animal.prototype = {
run: function() {
console.log('Running');
}
};
console.log(Animal.prototype.constructor === Animal); // false(指向 Object)
// 正确做法:重新指定 constructor
Animal.prototype = {
constructor: Animal, // 显式修复
run: function() {
console.log('Running');
}
};
proto 与 prototype 的关系
在 JavaScript 中,实例对象通过 __proto__ 属性指向其构造函数的原型对象 prototype。这种机制构成了 JavaScript 的原型继承体系。
- __proto__ 是实例对象的内部属性,指向其构造函数的原型对象。
- prototype 是构造函数的属性,包含可供实例继承的属性和方法。
原型链的工作原理
每个对象都有一个 __proto__ 属性,指向其原型对象。原型对象本身也有 __proto__,形成一条链式结构,直到最终指向 null。
function Person() {}
const person = new Person();
// 实例的 __proto__ 指向构造函数的 prototype
console.log(person.__proto__ === Person.prototype); // true
// 构造函数的 prototype 的 __proto__ 指向 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype); // true
// Object.prototype 的 __proto__ 指向 null
console.log(Object.prototype.__proto__ === null); // true
访问属性的查找过程
当访问对象的属性时,JavaScript 引擎会沿着原型链向上查找:
Person.prototype.sayHello = function() {
console.log('Hello');
};
person.sayHello(); // 输出 "Hello"
// person 自身没有 sayHello 方法,但通过原型链找到 Person.prototype 上的方法
- 构造函数:通过 new 生成实例,prototype 属性指向原型对象。
- 原型对象:包含共享方法,通过 constructor 属性反向引用构造函数。
- 实例对象:通过 __proto__ 隐式引用原型对象,形成原型链。
进阶04

