javascript入门
1 js基本语法
1.1 基础
1. var 和 let 的核心区别详解
```javascript
// 函数作用域(var)
function testVar() {for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100); // 输出 3, 3, 3}
}// 块级作用域(let)
function testLet() {for (let i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100); // 输出 0, 1, 2}
}// 常量(const)
const PI = 3.14;
// PI = 3.1415; // 报错:Assignment to constant variable
testLet();
testVar();
console 是 JavaScript 中的一个内置全局对象,用于向浏览器控制台或 Node.js 终端输出信息
一、作用域范围不同
-
var:函数作用域(Function Scope)
在函数内部使用var
声明的变量,仅在该函数内可见,不会被块级作用域限制。function example() {if (true) {var x = 10; // var 在函数内有效,而非块内}console.log(x); // 输出 10 }
-
let:块级作用域(Block Scope)
使用let
声明的变量仅在最近的大括号{}
内有效(如if
、for
、while
块)。function example() {if (true) {let y = 20; // let 仅在 if 块内有效}console.log(y); // 报错:y is not defined }
二、变量提升机制不同
-
var:存在变量提升(Hoisting)
用var
声明的变量会被提升到作用域顶部,但赋值不会提升。console.log(a); // 输出 undefined(变量已提升,但未赋值) var a = 10;
-
let:不存在变量提升,存在“暂时性死区”(Temporal Dead Zone, TDZ)
用let
声明的变量在声明前无法访问,访问会报错。console.log(b); // 报错:Cannot access 'b' before initialization let b = 20;
三、重复声明处理不同
-
var:允许在同一作用域内重复声明同一变量
var c = 1; var c = 2; // 合法,c 的值变为 2
-
let:禁止在同一作用域内重复声明同一变量
let d = 1; let d = 2; // 报错:Identifier 'd' has already been declared
四、在循环中的表现差异
-
var:在循环中声明的变量会被共享到整个函数作用域
function testVar() {for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100); // 输出 3, 3, 3} }
原因:3 个定时器回调共享同一个
i
,循环结束后i
的值为 3,回调执行时读取的是最终值。 -
let:在循环中会为每次迭代创建独立的变量
function testLet() {for (let i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100); // 输出 0, 1, 2} }
原因:每次循环都会生成一个独立的
i
,回调函数捕获的是当前迭代的i
值。
总结对比表
特性 | var | let |
---|---|---|
作用域 | 函数作用域 | 块级作用域 |
变量提升 | 存在,初始化为 undefined | 不存在,存在暂时性死区 |
重复声明 | 允许 | 禁止 |
循环中的表现 | 所有迭代共享同一变量 | 每次迭代创建独立变量 |
是否推荐使用 | 不推荐(ES6 后) | 推荐 |
2. 基本数据类型
- 值类型:Number、String、Boolean、null、undefined、Symbol(ES6)
- 引用类型:Object(包括数组、函数、日期等)
// 值类型
const num = 42;
const str = "Hello";
const isDone = false;
const nothing = null;
let unknown; // undefined// 引用类型
const obj = { name: "John", age: 30 };
const arr = [1, 2, 3];
const func = function() { return 42; };
JavaScript 和 Python 都是动态类型语言,变量的类型是在运行时确定的,而不是在编写代码时强制指定
3.控制
// if-else
const age = 18;
if (age < 18) {console.log("未成年");
} else if (age >= 18 && age < 60) {console.log("成年");
} else {console.log("老年");
}// switch
const day = 3;
switch (day) {case 1:console.log("周一");break;case 2:console.log("周二");break;default:console.log("其他");
}
4.循环
// for 循环
for (let i = 0; i < 3; i++) {console.log(i); // 输出 0, 1, 2
}// while 循环
let j = 0;
while (j < 3) {console.log(j); // 输出 0, 1, 2j++;
}// for...of 遍历数组
const colors = ["red", "green", "blue"];
for (const color of colors) {console.log(color); // 输出 "red", "green", "blue"
}// for...in 遍历对象
const person = { name: "John", age: 30 };
for (const key in person) {console.log(`${key}: ${person[key]}`); // 输出 "name: John", "age: 30"
}
1.2 中级-函数
一、函数定义与调用
- 函数声明(Function Declaration)
- 语法:
function 函数名(参数) { ... }
- 特点:函数声明会被提升(Hoisting),可在定义前调用
示例
// 声明函数
function greet(name) {return `Hello, ${name}!`;
}// 调用函数
console.log(greet('John')); // 输出 "Hello, John!"
- 函数表达式(Function Expression)
- 语法:
const 变量名 = function(参数) { ... }
- 特点:函数作为值赋给变量,不会被提升
示例
// 匿名函数表达式
const add = function(a, b) {return a + b;
};// 具名函数表达式(便于递归或调试)
const factorial = function fact(n) {return n <= 1 ? 1 : n * fact(n - 1);
};console.log(factorial(5)); // 120
- 箭头函数(Arrow Function)
- 语法:
(参数) => { ... }
或(参数) => 表达式
- 特点:语法更简洁,无独立的
this
、arguments
示例
// 基本箭头函数
const multiply = (a, b) => a * b;// 单参数省略括号
const square = x => x * x;// 无参数用空括号
const getRandom = () => Math.random();// 多行代码用大括号
const sumArray = arr => {let total = 0;for (const num of arr) {total += num;}return total;
};
二、参数与返回值
- 参数默认值
- 语法:
function 函数名(参数 = 默认值) { ... }
示例
function greet(name = 'Guest') {return `Hello, ${name}!`;
}console.log(greet()); // 输出 "Hello, Guest!"
console.log(greet('John')); // 输出 "Hello, John!"
- 剩余参数(Rest Parameters)
- 语法:
function 函数名(...参数名) { ... }
- 作用:将多个参数收集为数组
示例
function sum(...numbers) {return numbers.reduce((total, num) => total + num, 0);
}console.log(sum(1, 2, 3)); // 输出 6
console.log(sum(1, 2, 3, 4, 5)); // 输出 15
数组.reduce(callback(累积值, 当前值, 当前索引, 原数组), 初始值); 缺省了
三、异步函数
1. 回调函数(Callback)
定义:作为参数传递给另一个函数的函数
应用:处理异步操作(如定时器、AJAX 请求)
示例
function fetchData(callback) {setTimeout(() => {const data = { message: 'Data loaded' };callback(data);}, 1000);
}fetchData(data => {console.log(data.message); // 1秒后输出 "Data loaded"
});
//setTimeout 这个内置函数
//setTimeout(callback, delay, arg1, arg2, ...);
2. Promise
定义:表示异步操作的最终完成或失败
方法:then()、catch()、finally()
// 创建一个 Promise
function readFilePromise(filePath) {return new Promise((resolve, reject) => {// 模拟异步操作(如文件读取)setTimeout(() => {// 模拟文件内容const content = `这是 ${filePath} 的内容`;// 随机决定成功或失败const success = Math.random() > 0.3;if (success) {resolve(content); // 操作成功,返回数据} else {reject(new Error('读取文件失败')); // 操作失败,返回错误}}, 1000);});
}// 使用 Promise
readFilePromise('example.txt').then((data) => {console.log('成功:', data);//date==contentreturn data.toUpperCase(); // 链式调用,传递处理后的数据 内置函数}).then((uppercaseData) => {console.log('转换后:', uppercaseData);}).catch((error) => {console.error('错误:', error.message);}).finally(() => {console.log('无论成功失败都执行');});
- 创建 Promise
new Promise((resolve, reject) => {// 异步操作...if (成功) {resolve(结果); // 传递成功数据} else {reject(错误); // 传递失败原因}
});
- resolve:异步操作成功时调用,传递结果。
- reject:异步操作失败时调用,传递错误对象。
3. async/await
语法糖:基于 Promise,使异步代码更像同步代码
规则:
async 函数返回 Promise
await 暂停函数执行,等待 Promise 解决
示例
async function fetchData() {return new Promise(resolve => {setTimeout(() => resolve({ message: 'Async/await' }), 1000);});
}async function main() {try {const data = await fetchData();console.log(data.message); // 1秒后输出 "Async/await"} catch (error) {console.error(error);}
2 对象
- JavaScript:基于原型的动态对象
- 特性:
- 动态类型:对象属性可随时添加、删除、修改。
- 基于原型:通过原型链实现继承,无严格类定义。
- 弱类型:属性值类型不固定。
示例:
// 创建对象(无需类定义)
const person = {name: 'John',age: 30,greet() {return `Hello, I'm ${this.name}`;}
};// 动态添加属性
person.job = 'Engineer';// 修改方法
person.greet = function() {return `Hi, I'm ${this.name}, a ${this.job}`;
};console.log(person.greet()); // "Hi, I'm John, a Engineer"
2 、对象创建与初始化
// 1. 对象字面量(最常用)
const obj1 = {key: 'value',method() { return this.key; }
};// 2. 构造函数
function User(name) {this.name = name;this.sayHi = function() {return `Hi, ${this.name}`;};
}const user = new User('Alice');// 3. 类语法(ES6 糖衣)
class Car {constructor(model) {this.model = model;}drive() {return `${this.model} is driving`;}
}const car = new Car('Tesla');
3 JavaScript:原型链与组合继承
// 1. 原型链继承
class Animal {constructor(name) {this.name = name;}speak() {return `${this.name} makes a sound`;}
}class Dog extends Animal {constructor(name) {super(name); // 调用父类构造函数}speak() {return `${this.name} barks`; // 重写方法}
}const dog = new Dog('Buddy');
console.log(dog.speak()); // "Buddy barks"// 2. 组合继承(混入 mixin)
const flyMixin = {fly() {return `${this.name} is flying`;}
};Object.assign(Dog.prototype, flyMixin); // 动态添加能力
console.log(dog.fly()); // "Buddy is flying"
4 JavaScript 的 prototype
一、prototype
就像「家族遗传」
想象一个家族:
- 爷爷(基础模板)有一些特质(如蓝眼睛、高个子)。
- 爸爸(继承爷爷)继承了这些特质,还发展了自己的技能(如弹钢琴)。
- 你(继承爸爸)继承了爷爷和爸爸的特质,还增加了自己的爱好(如编程)。
JavaScript 中:
- 爷爷 =
Object.prototype
(所有对象的最终原型)。 - 爸爸 = 你创建的一个对象(作为另一个对象的原型)。
- 你 = 基于原型创建的新对象。
示例:
// 爷爷(基础模板)
const grandFather = {eyeColor: 'blue',height: 'tall'
};// 爸爸(继承爷爷)
const father = Object.create(grandFather);
father.playPiano = function() {return "Playing Mozart...";
};// 你(继承爸爸)
const you = Object.create(father);
you.code = function() {return "Coding JavaScript...";
};// 你可以使用所有继承的特质和技能
console.log(you.eyeColor); // "blue"(来自爷爷)
console.log(you.playPiano()); // "Playing Mozart..."(来自爸爸)
console.log(you.code()); // "Coding JavaScript..."(你自己的)
二、prototype
的核心作用:
1. 共享属性和方法
- 原型就像一个「共享仓库」,所有继承它的对象都能从中获取资源。
- 优点:节省内存(属性和方法只需在原型中存储一次)。
类比:
- 家族有一个「共享工具箱」(原型),里面有锤子、螺丝刀等工具。
- 家族成员(对象)不需要每个人都买一套工具,直接从工具箱拿即可。
- 动态更新
- 修改原型会影响所有继承它的对象(就像家族传统改变,所有后代都会受影响)。
示例:
const person = {species: 'Human'
};const alice = Object.create(person);
const bob = Object.create(person);console.log(alice.species); // "Human"
console.log(bob.species); // "Human"// 修改原型
person.species = 'Homo Sapiens';console.log(alice.species); // "Homo Sapiens"(自动更新)
console.log(bob.species); // "Homo Sapiens"(自动更新)
三、prototype
vs 「类」的区别
1. 传统「类」的思维
- 类(如 C++、Java)是「蓝图」,对象是「复制」。
- 每个对象都有自己的属性副本。
类比:
- 汽车工厂有一份设计图(类),生产的每辆汽车(对象)都是设计图的独立复制。
- JavaScript 的原型思维
- 对象直接关联其他对象(原型),无需复制。
- 访问属性时,先看自己有没有,没有就去原型里找。
类比:
- 你开了一家奶茶店(对象),没有自己的配方(属性)。
- 你直接用隔壁店(原型)的配方,顾客点单时,先看自己有没有记录,没有就去隔壁查。
四、构造函数与 prototype
的关系
当你用 new
创建对象时:
- JavaScript 先创建一个空对象。
- 将这个空对象的原型(
__proto__
)指向构造函数的prototype
属性。 - 执行构造函数,并将
this
绑定到新对象。
示例:
function Cat(name) {this.name = name; // 为新对象添加属性
}// 向 Cat 的原型添加共享方法
Cat.prototype.meow = function() {return `${this.name} says Meow!`;
};const whiskers = new Cat('Whiskers');
console.log(whiskers.meow()); // "Whiskers says Meow!"// 实际上 whiskers 的原型指向 Cat.prototype
console.log(whiskers.__proto__ === Cat.prototype); // true
五、原型链的查找过程
当你访问 obj.property
时:
- 先看
obj
自己有没有property
。 - 如果没有,去
obj.__proto__
里找。 - 如果还没有,去
obj.__proto__.__proto__
里找。 - 以此类推,直到找到或到达
Object.prototype
(如果还没有,返回undefined
)。
类比:
- 你想知道家族祖传的红烧肉做法:
- 先问自己(有没有记录)。
- 没有就问爸爸。
- 爸爸也不知道就问爷爷。
- 爷爷也不知道就问曾祖父。
- 如果所有人都不知道,那就没有这个配方。
六、ES6 类与原型的关系
ES6 的 class
只是原型的「语法糖」,让代码更像传统的类。
示例:
// ES6 类(实际基于原型)
class Dog {constructor(name) {this.name = name;}bark() {return `${this.name} barks!`;}
}const buddy = new Dog('Buddy');// 本质上与原型方式相同
console.log(buddy.__proto__ === Dog.prototype); // true
总结
JavaScript 的 prototype
就像:
- 共享工具箱:多个对象共用一套工具。
- 家族遗传:后代继承前代的特质和技能。
- 查找链:先看自己有没有,没有就去原型里找。
记住:
- 原型是对象之间的「关联关系」,不是复制。
- 理解
prototype
,就能理解 JavaScript 的「继承」和「多态」。
Cat.prototype 是一个对象,所有 new Cat() 创建的实例都会继承它的属性和方法。
继承通过 proto 实现:cat.proto === Cat.prototype。