JavaScript的初步学习
JavaScript简介
JavaScript(简称JS)是一种广泛使用的动态脚本语言,主要用于网页开发,赋予网页动态交互功能。它由网景公司(Netscape)的布兰登·艾奇(Brendan Eich)在1995年开发,最初名为LiveScript,后来改名为JavaScript。
主要特点
-
客户端脚本:主要在浏览器中执行,使网页具有动态效果和交互能力(如表单验证、动画、实时更新内容等)。
-
动态类型:变量类型在运行时确定,不需要预定义。
-
弱类型:类型转换自动进行,使用灵活。
-
事件驱动:响应用户操作(点击、悬停、提交等)。
-
多范式:支持面向对象、函数式、命令式编程。
主要用途
-
网页交互:实现按钮点击、表单验证、动画效果。
-
网页开发框架:如React、Vue、Angular。
-
服务器端编程:通过Node.js,JavaScript还能在服务器执行。
-
移动应用:如React Native。
-
桌面应用、游戏开发:通过特殊平台和引擎实现。
JavaScript的基本特性
-
简洁灵活的语法:类似C、Java的语法,但比它们简单易用。
-
原生支持事件处理:可以直接在HTML或JavaScript中绑定事件。
-
DOM操作:可以直接操作网页结构(Document Object Model)。
-
异步编程:支持Promise、async/await等技术,方便处理异步任务。
JavaScript的引入方式
1. 内联方式 (Inline JavaScript)
概念: 将JavaScript代码直接作为HTML标签的属性值使用。通常用于处理特定的事件,例如点击按钮时执行一段JS代码。
优点:
-
简单直接,适用于非常短小、针对特定元素的逻辑。
-
无需额外的文件或
<script>
标签。
缺点:
-
极其不推荐! 严重违反了结构(HTML)、样式(CSS)、行为(JavaScript)分离的原则。
-
代码可读性差,难以维护。
-
代码复用性差。
-
增加了HTML文件的体积。
<!DOCTYPE html>
<html>
<head><title>内联JavaScript示例</title>
</head>
<body><button onclick="alert('你点击了我,内联方式!');">点击我</button><a href="#" onmouseover="this.style.color='red';"onmouseout="this.style.color='black';">鼠标悬停</a>
</body>
</html>
2. 内部方式 (Internal JavaScript / Embedded JavaScript)
概念: 将JavaScript代码放置在HTML文件的<script>
标签内部。<script>
标签可以放在HTML文档的<head>
部分,也可以放在<body>
部分。
优点:
-
代码都在一个HTML文件中,方便小型项目或单页面应用。
-
避免了额外的HTTP请求。
缺点:
-
不利于代码复用。如果多个HTML页面需要相同的JavaScript代码,就需要复制代码。
-
增加了HTML文件的体积,下载时需要加载整个JS代码。
-
如果JS代码量很大,可能会阻塞页面内容的渲染(尤其是放在
<head>
里)。
建议放置位置: 通常推荐将 <script>
标签放置在 <body>
标签的闭合标签 </body>
之前。这样做的原因是:
-
避免阻塞渲染: 浏览器在解析HTML时,遇到
<script>
标签会暂停HTML的解析和渲染,转而去下载和执行JavaScript代码。如果JavaScript在页面的顶部(<head>
中)且代码量较大,用户可能会看到空白页面的时间延长。 -
确保DOM可用: JavaScript代码通常需要操作HTML元素(DOM)。如果JavaScript在元素加载之前就执行,可能会找不到要操作的元素,导致错误。将JavaScript放置在
</body>
之前,可以保证它执行时,页面上的所有HTML元素已经被浏览器解析并创建为DOM树。
<!DOCTYPE html>
<html lang="zh-CN">
<!-- HTML文档的语言设置为中文 -->
<head><!-- 字符集为UTF-8 --><meta charset="UTF-8"><!-- 设置浏览器兼容性 --><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>内部JavaScript示例</title><script>// 这是放置在 head 标签内的 JavaScript 代码function showHeadMessage() {console.log("这是来自 head 标签内的 JavaScript。");}</script>
</head>
<body><h1>内部JavaScript</h1><p id="myParagraph">这是一个段落。</p><button onclick="changeText()">点击改变段落</button><script>// 这是放置在 body 标签末尾的 JavaScript 代码function changeText() {document.getElementById("myParagraph").innerText = "段落内容已被JavaScript改变!";}// 页面加载完成后执行window.onload = function() {showHeadMessage(); // 调用 head 中的函数console.log("页面已加载完成,这是来自 body 标签末尾的 JavaScript。");};</script>
</body>
</html>
3. 外部方式 (External JavaScript)
概念: 将JavaScript代码单独保存在一个.js
文件中,然后在HTML文件中通过<script>
标签的 src
属性引入。
优点:
-
最推荐的方式! 实现了结构、样式、行为的完全分离,代码清晰,易于维护。
-
代码复用性高: 多个HTML页面可以引用同一个
.js
文件,避免重复编写代码。 -
浏览器缓存:
.js
文件可以被浏览器缓存,当用户再次访问或访问其他页面时,无需重新下载,加快页面加载速度。 -
优化加载性能:
-
可以使用
defer
属性:脚本会延迟到文档解析完成后才执行,但会按照它们在文档中出现的顺序执行。不会阻塞HTML解析。 -
可以使用
async
属性:脚本会异步加载,并在加载完成后立即执行,不保证执行顺序,也会阻塞HTML解析(但只在脚本下载期间)。 -
推荐:将
<script>
标签放在</body>
闭合标签之前。
-
缺点:
-
增加了一个HTTP请求(但可以通过浏览器缓存弥补)。
// script.js
function sayHello() {alert("你好,这是来自外部JavaScript文件!");
}
function updateDiv() {document.getElementById("messageDiv").innerText = "外部JS已修改此内容。";
}
// 页面加载完成后执行
window.onload = function() {console.log("外部JavaScript文件已加载并执行。");
};
<!DOCTYPE html>
<html lang="zh-CN">
<head><!-- 字符集为UTF-8 --><meta charset="UTF-8"><!-- 设置浏览器兼容性 --><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>外部JavaScript示例</title><!-- 推荐使用 defer 或放在 body 底部 --><script src="script.js" defer></script>
</head>
<body><h1>外部JavaScript</h1><p>这是来自HTML页面的内容。</p><div id="messageDiv">原始内容</div><button onclick="sayHello()">点击问候</button><button onclick="updateDiv()">更新内容</button>
</body>
</html>
JavaScript的语法介绍
1.书写语法
2.输出语句
3.变量
(1)变量的声明关键词
JavaScript中声明变量主要使用三种关键词:var
、let
和const
。这三者在作用域、可变性等方面存在差异。
a. var
-
最早的变量声明方式。
-
作用域为函数作用域(函数内部有效,函数外面不可见);
-
可以重复声明同一变量,后声明会覆盖前面。
b. let
-
ES6(ECMAScript 2015)引入。
-
作用域为块作用域(比如在
if
、for
、while
块内有效); -
不允许重复声明同一变量。
c. const
-
也是ES6引入。
-
作用域为块作用域;
-
必须初始化,声明后不可再赋值;
-
对于对象和数组,虽然引用不可变,但对象内容还是可变的。
(2) 变量的命名规则
-
只能以字母、
$
、_
开头; -
后续可以使用字母、数字、
$
、_
; -
不能使用JavaScript关键字(如
if
,var
,function
等)作为变量名; -
推荐命名具有描述性(语义清晰)。
(3) 变量的作用域
-
var
声明的变量:函数作用域或全局作用域(如果在函数外部声明) -
let
和const
声明的变量:块作用域(例如在if
、for
、while
内有效)
(4) 变量的提升(Hoisting)
-
var
声明的变量会提升:变量声明会被提升到作用域顶部,但赋值不会。 -
let
和const
不会提升,会在块内形成“暂时性死区(TDZ)”,访问会报错。
console.log(x); // undefined,因为var会提升声明
var x = 5;console.log(y); // 报错:Cannot access 'y' before initialization
let y = 10;console.log(z); // 报错:Cannot access 'z' before initialization
const z = 15;
4.数据类型
(1)基本数据类型(原始类型)
a. 数字(Number)
-
表示整数和浮点数。
-
JavaScript中的数字采用双精度浮点(IEEE 754标准)。
-
特殊值:
Infinity
,-Infinity
,NaN
(非数字)
let integerNum = 42; // 整数
let floatNum = 3.1415; // 浮点数
let infinity = Infinity; // 正无穷
let nanValue = NaN; // 非数字
注意:NaN
表示“非数值”,且NaN
本身不等于任何值,包括自己。
console.log(NaN === NaN); // false
console.log(isNaN(NaN)); // true
b. 字符串(String)
-
用单引号
''
或双引号""
(也支持反引号 -
支持转义字符和模板字符串。
let str1 = 'Hello';
let str2 = "World";
let str3 = `Hello, ${str2}!`; // 模板字符串,支持插值
c. 布尔值(Boolean)
-
只有两个值:
true
和false
。
d. undefined
-
表示变量未定义或未赋值。
e. null
-
表示“空值”,需要显式赋值。
f. Symbol(符号)
-
在ES6引入。
-
用于创建唯一的标识符。
const sym1 = Symbol('foo');
const sym2 = Symbol('foo');
console.log(sym1 === sym2); // false,符号唯一
(2)复杂数据类型(引用类型)
a. 对象(Object)
-
键值对集合,属性名为字符串(或Symbol),值可以为任何类型。
-
字面量定义
let obj = {name: 'Alice',age: 25,greet: function() { return 'Hello'; }
};
b. 数组(Array)
-
数对象的特殊类型,存储有序元素。
let arr = [1, 2, 3, 'a', true];
c. 特殊对象:Date
, RegExp
, Error
等
let dateNow = new Date();
let regex = /ab+c/;
(3)类型转换
JavaScript会在需要时自动进行类型转换(隐式转换),也可以手动转换。
a. 转字符串
String(123); // '123'
(123).toString(); // '123'
b. 转数字
Number('456'); // 456
parseInt('123', 10); // 123
parseFloat('3.14'); // 3.14
parseInt('12abc'); //12
parseInt('abc'); //NaN
c. 转布尔值
Boolean(0); // false
Boolean(''); // false
Boolean('hello'); // true
Boolean(undefined); // false
注意: 以下值在转换为布尔值时为false
,称为“假值”或“false值”:
-
false
-
0
-
-0
-
0n
(BigInt零) -
null
-
undefined
-
NaN
-
空字符串
''
其他值为真(true)。
(4)值得注意的细节
a. typeof
运算符
用来判断变量的数据类型。
typeof 123; // 'number'
typeof 'hello'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof null; // 'object'(这是一个历史遗留问题,null实际上是原始类型)
typeof Symbol('foo'); // 'symbol'
typeof {a:1}; // 'object'
typeof [1,2,3]; // 'object'(数组也是对象)
typeof function(){}; // 'function'(函数也是对象的一种)
b.instanceof
判断对象是否是某个类的实例。
let arr = [1,2,3];
console.log(arr instanceof Array); // true
console.log({} instanceof Object); // true
5.运算符
6.流程控制语句
和Java相近。
7.函数
(1) 函数的定义方式
a.函数声明(Function Declaration)
function 函数名(参数列表) {// 函数体
}
特点:
-
函数会被提升(hoisted),可以在声明之前调用。
-
适合定义在代码顶层或块内的标准函数。
b.函数表达式(Function Expression)
const func = function(参数) {// 函数体
};
特点:
-
不会被提升,只能在声明后使用。
-
通常用作回调函数。
c.箭头函数(Arrow Function)
const func = (参数) => {// 函数体
};
特点:
-
语法简洁,尤其适合写短函数。
-
不绑定自己的
this
,常用于回调或匿名函数。
(2)函数参数的语法和细节
a. 默认参数(Default Parameters)
function greet(name='Guest') {console.log('Hello, ' + name);
}
greet(); // 'Hello, Guest'
b. 剩余参数(Rest Parameters)
收集多余参数为数组:
function sum(...numbers) {return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
c. 参数解构(Destructuring)
解构数组或对象参数:
function display({name, age}) {console.log(name, age);
}
display({name: 'Alice', age: 30});
(3) 函数作用域与闭包
a. 作用域
-
函数定义的变量在函数内部有效,外部不可访问(除非返回或传递)。
-
函数内部可以访问外部作用域。
b. 闭包
-
函数配合其定义环境形成闭包,能“记住”当时的作用域。
function outer() {let count = 0;return function inner() {count++;console.log(count);}
}
const increment = outer();
increment(); // 1
increment(); // 2
(4)函数的this
绑定规律
a. 普通函数
-
在严格模式(
'use strict'
)下,this
为undefined
。 -
在非严格模式下,
this
指向全局对象(浏览器window
)。
function show() {console.log(this);
}
show(); // 在非严格模式:window;严格模式下:undefined
b. 箭头函数
-
没有自己的
this
,继承自定义声明时所在的作用域。
const obj = {value: 42,getValue: function() {const arrowFunc = () => this.value;return arrowFunc();}
};
console.log(obj.getValue()); // 42
c. 更复杂的绑定规则
-
使用
call
、apply
、bind
可以强制指定this
。
const obj = {name: 'Bob'};
function showName() {console.log(this.name);
}
showName.call(obj); // 'Bob'
(5)函数的返回值
-
默认返回
undefined
。 -
使用
return
语句返回值。 -
函数可以返回任何类型的数据。
注意:
-
一旦遇到
return
,函数立即结束。 -
可以返回对象、数组、函数等。
(6) 生成器函数(Generator Functions)
-
使用
function*
定义。 -
产生可迭代的值。
function* gen() {yield 1;yield 2;yield 3;
}
const iterator = gen();
console.log(iterator.next()); // { value: 1, done: false }
8.对象
(1)常用对象
(2)自定义对象
在JavaScript中,对象是键值对
的无序集合。键(key)是字符串(或Symbol),值(value)可以是任何数据类型(包括原始类型、其他对象、函数等)。
a. 对象的创建方式
对象字面量(Object Literal)- 最常用
这是创建对象最简单、最常用的方式。
// 语法:
let objectName = {key1: value1,key2: value2,// ...
};// 示例:
let person = {name: "Alice", // 属性(property)age: 30, // 属性isStudent: false, // 属性greet: function() { // 方法(method)console.log("Hello, my name is " + this.name);}
};
细节:
-
属性名可以是标识符(不带引号,如
name
),也可以是字符串(带引号,如"user-id"
)。如果属性名是无效的标识符(如包含连字符、空格或数字开头),则必须用引号。 -
属性值可以是任何数据类型。
-
方法是值为函数的属性。
-
this
关键字在对象方法中指向当前对象本身。
new Object()
构造函数
不常用,但也是一种创建方式。
let person = new Object();
person.name = "Bob";
person.age = 25;
person.greet = function() {console.log("Hi, I'm " + this.name);
};
构造函数(Constructor Function)- 创造类实例的“模板”
用于创建具有相同属性和方法的多个对象。
// 语法:
function ConstructorName(param1, param2) {this.property1 = param1;this.property2 = param2;this.method = function() {// ...};
}// 示例:
function Person(name, age) {this.name = name;this.age = age;this.greet = function() {console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");};
}// 使用 new 关键字创建实例
let person1 = new Person("Charlie", 40);
let person2 = new Person("Diana", 28);person1.greet(); // Hello, my name is Charlie and I am 40 years old.
person2.greet(); // Hello, my name is Diana and I am 28 years old.
细节:
-
函数名通常首字母大写(约定)。
-
this
关键字在构造函数中指代新创建的实例。 -
必须使用
new
关键字来调用构造函数,否则this
会指向全局对象(非严格模式下)。 -
每个实例都会有自己的
greet
方法,这可能会造成内存浪费(因为方法相同)。
使用原型(Prototype)优化构造函数 - 共享方法
为了避免每个实例都拥有独立的方法副本,可以将方法定义在构造函数的原型上。
function Person(name, age) {this.name = name;this.age = age;
}// 将方法添加到 Person.prototype
Person.prototype.greet = function() {console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
};let person3 = new Person("Eve", 35);
person3.greet(); // Hello, my name is Eve and I am 35 years old.
细节:
-
Person.prototype
是一个对象,所有由Person
构造函数创建的实例都会通过原型链继承Person.prototype
上的属性和方法。 -
greet
方法现在只存在一份,被所有Person
实例共享,节约内存。
ES6 class
语法 - 现代JS推荐的面向对象方式
class
是JavaScript中用来创建对象的语法糖,底层依然是原型和构造函数。
class Person {constructor(name, age) {this.name = name;this.age = age;}greet() { // 方法直接定义在类内部,会被添加到原型上console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);}// 静态方法(属于类本身,不属于实例)static sayHello() {console.log("Hello from the Person class!");}
}let person4 = new Person("Frank", 50);
person4.greet(); // Hello, my name is Frank and I am 50 years old.
Person.sayHello(); // Hello from the Person class!
细节:
-
constructor
方法是类默认的构造函数,当使用new
关键字创建实例时,会自动调用它。 -
在
constructor
之外定义的方法会自动添加到原型上。 -
static
关键字用于定义静态方法,只能通过类本身调用,不能通过实例调用。
b. 访问和修改对象属性/方法
点表示法(Dot Notation)
console.log(person.name); // Alice
person.age = 31;
person.greet();
方括号表示法(Bracket Notation)
当属性名包含特殊字符、空格、或是一个变量时,必须使用方括号表示法。
console.log(person["name"]); // Alicelet propName = "age";
console.log(person[propName]); // 31person["favorite-color"] = "blue"; // 可以添加带连字符的属性
console.log(person["favorite-color"]);
c. 删除对象属性
使用delete
运算符。
delete person.isStudent;
console.log(person.isStudent); // undefined
d. 检查属性是否存在
in
运算符
检查属性是否在对象或其原型链上。
console.log("name" in person); // true
console.log("toString" in person); // true (继承自Object.prototype)
console.log("job" in person); // false
hasOwnProperty()
方法
检查属性是否是对象自身的属性(非继承)。
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("toString")); // false
e. 遍历对象属性
for...in
循环
遍历对象可枚举的属性(包括原型链上的)。
for (let key in person) {console.log(`${key}: ${person[key]}`);
}
// 注意:for...in 会遍历原型链上的可枚举属性,需要结合 hasOwnProperty() 筛选
for (let key in person) {if (person.hasOwnProperty(key)) {console.log(`Own property - ${key}: ${person[key]}`);}
}
Object.keys()
返回一个包含对象自身所有可枚举属性名的数组。
let keys = Object.keys(person);
console.log(keys); // ['name', 'age', 'greet', 'favorite-color']keys.forEach(key => {console.log(`${key}: ${person[key]}`);
});
Object.values()
返回一个包含对象自身所有可枚举属性值的数组。
let values = Object.values(person);
console.log(values); // ['Alice', 31, Function, 'blue']
Object.entries()
返回一个包含对象自身所有可枚举属性的[key, value]
对数组。
let entries = Object.entries(person);
console.log(entries); // [['name', 'Alice'], ['age', 31], ['greet', Function], ['favorite-color', 'blue']]for (let [key, value] of entries) {console.log(`${key}: ${value}`);
}
f. 短属性名和短方法名(ES6)
当变量名和属性名相同时,可以简写。
let myName = "Grace";
let myAge = 22;let user = {myName, // 等同于 myName: myNamemyAge, // 等同于 myAge: myAgewalk() { // 等同于 walk: function() { ... }console.log("Walking...");}
};
console.log(user.myName); // Grace
user.walk(); // Walking...
g. 计算属性名(Computed Property Names)(ES6)
属性名可以是一个表达式的结果。
let prop = "dynamicKey";
let obj = {[prop + "1"]: "value1",['another' + 'Key']: "value2"
};
console.log(obj.dynamicKey1); // value1
console.log(obj.anotherKey); // value2
9.JSON
(1)什么是JSON
-
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于读写和解析。
-
它是基于JavaScript对象字面量语法的文本格式,但不仅限于JavaScript,可以在多种编程语言中使用。
(2)JSON的语法规则
a.基本结构
-
数据是键值对的集合
-
可以是对象(用
{}
包围)或数组(用[]
包围)
b.JSON对象(Object)
{"key1": value1,"key2": value2,...
}
c. JSON数组(Array)
[value1,value2,...
]
d. 数值(Number)
-
支持整数和浮点数
e. 字符串(String)
-
必须用双引号(
"
)包围 -
支持转义字符(如
\n
、\t
、\"
、\\
)
f. 布尔值(Boolean)
g. null值
h. 不能出现
-
单引号(字符串必须双引号)
-
函数、日期对象、undefined(这些在JSON中不被支持)
-
不能有函数作为值
{"name": "John","age": 25,"isStudent": false,"scores": [95, 82, 88],"address": {"city": "New York","zip": "10001"},"skills": null
}
(3)在JavaScript中操作JSON
a.转换为JSON字符串:JSON.stringify
将JavaScript对象转成JSON字符串(常用于数据传输或存储)
const obj = { name: "Alice", age: 30 };
const jsonStr = JSON.stringify(obj);
console.log(jsonStr); // '{"name":"Alice","age":30}'
b. 解析JSON字符串:JSON.parse
将JSON字符串转成JavaScript对象(常用于接收数据)
const jsonStr = '{"name":"Bob","age":25}';
const obj = JSON.parse(jsonStr);
console.log(obj.name); // "Bob"
(4)扩展:自定义对象转JSON的细节
当调用JSON.stringify()
时,可以传入replacer
参数,用于控制序列化过程。
const obj = {name: "Tom",age: 28,greet() { return "Hi"; }
};
console.log(JSON.stringify(obj)); // 无`greet`方法,输出:{"name":"Tom","age":28}// 只序列化特定字段
JSON.stringify(obj, ['name', 'age']);// 美化输出,添加空格缩进
JSON.stringify(obj, null, 2);
-
注意:除数据外,函数和符号会被忽略。