JavaScript公认符号与事件驱动编程
JavaScript 公认符号
基本语法符号
- 分号(;) 在 JavaScript 中,分号用于表示语句的结束。虽然在很多情况下,JavaScript 引擎可以自动插入分号(ASI,Automatic Semicolon Insertion),但显式地使用分号仍是一个良好的编程习惯。例如:
let num = 10;
console.log(num);
如果省略分号,在某些复杂的代码结构中可能会导致错误。比如:
function add(a, b) {
return
a + b;
}
let result = add(2, 3);
console.log(result); // 这里会输出 undefined,因为 ASI 将 return 后的换行符视为语句结束,实际返回的是 undefined
正确的写法应该是:
function add(a, b) {
return a + b;
}
let result = add(2, 3);
console.log(result); // 输出 5
- 花括号({}) 花括号在 JavaScript 中有多种用途。在函数定义中,用于包裹函数体:
function greet(name) {
console.log('Hello, ' + name);
}
greet('John');
在对象字面量定义中,用于定义对象的属性和方法:
let person = {
name: 'Jane',
age: 30,
sayHello: function() {
console.log('Hello, I\'m ' + this.name);
}
};
person.sayHello();
在代码块中,如 if - else
、for
、while
等语句中,花括号包裹需要执行的代码:
let num = 5;
if (num > 3) {
console.log(num + ' is greater than 3');
}
- 圆括号(()) 圆括号主要用于函数调用,传递参数:
function multiply(a, b) {
return a * b;
}
let product = multiply(4, 5);
console.log(product);
在表达式中,圆括号可以改变运算的优先级:
let result = (2 + 3) * 4;
console.log(result); // 输出 20
还用于定义函数参数列表:
function subtract(a, b) {
return a - b;
}
- 方括号([]) 方括号用于定义数组:
let numbers = [1, 2, 3, 4, 5];
console.log(numbers[2]); // 输出 3
在对象中,方括号可以用于访问属性,当属性名是变量或者包含特殊字符时:
let person = {
name: 'Bob'
};
let prop = 'name';
console.log(person[prop]); // 输出 Bob
操作符符号
- 算术操作符
- 加法(+):用于数字相加,也可用于字符串拼接。
let num1 = 5;
let num2 = 3;
let sum = num1 + num2;
console.log(sum); // 输出 8
let str1 = 'Hello';
let str2 = ' World';
let combinedStr = str1 + str2;
console.log(combinedStr); // 输出 Hello World
- **减法(-)**、**乘法(*)**、**除法(/)**、**取模(%)**:
let subResult = num1 - num2;
let mulResult = num1 * num2;
let divResult = num1 / num2;
let modResult = num1 % num2;
console.log(subResult); // 输出 2
console.log(mulResult); // 输出 15
console.log(divResult); // 输出 1.6666666666666667
console.log(modResult); // 输出 2
- 赋值操作符
- 简单赋值(=):将右边的值赋给左边的变量。
let x = 10;
- **复合赋值(+=, -=, *=, /=, %=)**:
let y = 5;
y += 3; // 等同于 y = y + 3
console.log(y); // 输出 8
y *= 2; // 等同于 y = y * 2
console.log(y); // 输出 16
- 比较操作符
- 等于(==)、严格等于(===):
==
会进行类型转换后比较,===
不会进行类型转换。
- 等于(==)、严格等于(===):
console.log(5 == '5'); // 输出 true
console.log(5 === '5'); // 输出 false
- **大于(>)**、**小于(<)**、**大于等于(>=)**、**小于等于(<=)**:
console.log(7 > 5); // 输出 true
console.log(3 <= 3); // 输出 true
- 逻辑操作符
- 逻辑与(&&):只有当两个操作数都为真时,结果才为真。
let a = true;
let b = false;
console.log(a && b); // 输出 false
- **逻辑或(||)**:只要有一个操作数为真,结果就为真。
console.log(a || b); // 输出 true
- **逻辑非(!)**:用于取反操作数的布尔值。
console.log(!a); // 输出 false
- 位操作符
- 按位与(&):对两个操作数的每一位进行与运算。
let num3 = 5; // 二进制 0101
let num4 = 3; // 二进制 0011
let bitAndResult = num3 & num4; // 二进制 0001,十进制 1
console.log(bitAndResult);
- **按位或(|)**:对两个操作数的每一位进行或运算。
let bitOrResult = num3 | num4; // 二进制 0111,十进制 7
console.log(bitOrResult);
- **按位异或(^)**:对两个操作数的每一位进行异或运算(相同为 0,不同为 1)。
let bitXorResult = num3 ^ num4; // 二进制 0110,十进制 6
console.log(bitXorResult);
- **按位非(~)**:对操作数的每一位进行取反操作。
let bitNotResult = ~num3; // 二进制 1010,十进制 -6
console.log(bitNotResult);
- **左移(<<)**:将操作数的二进制表示向左移动指定的位数。
let leftShiftResult = num3 << 2; // 二进制 0101 左移 2 位变为 010100,十进制 20
console.log(leftShiftResult);
- **右移(>>)**:将操作数的二进制表示向右移动指定的位数,保持符号位不变。
let rightShiftResult = num3 >> 1; // 二进制 0101 右移 1 位变为 0010,十进制 2
console.log(rightShiftResult);
- **无符号右移(>>>)**:将操作数的二进制表示向右移动指定的位数,不考虑符号位,高位补 0。
let unsignedRightShiftResult = -5 >>> 1; // -5 的二进制表示为 11111111111111111111111111111011,无符号右移 1 位变为 01111111111111111111111111111101,十进制 2147483645
console.log(unsignedRightShiftResult);
特殊符号
- 点号(.) 在 JavaScript 中,点号用于访问对象的属性和方法。
let person = {
name: 'Alice',
age: 25,
sayAge: function() {
console.log('I\'m ' + this.age + ' years old');
}
};
console.log(person.name);
person.sayAge();
- 逗号(,) 逗号可以用于在一条语句中分隔多个变量声明或函数参数。
let num5, num6;
num5 = 10;
num6 = 20;
function printValues(a, b) {
console.log(a + ' and ' + b);
}
printValues(num5, num6);
- 冒号(:) 在对象字面量中,冒号用于分隔属性名和属性值。
let settings = {
theme: 'dark',
fontSize: 14
};
在 switch - case
语句中,冒号用于标识 case
的值。
let day = 3;
switch (day) {
case 1:
console.log('Monday');
break;
case 2:
console.log('Tuesday');
break;
case 3:
console.log('Wednesday');
break;
default:
console.log('Other day');
}
- 问号(?)和冒号(:)组成的三元操作符
三元操作符是一种简洁的条件表达式,语法为
condition? valueIfTrue : valueIfFalse
。
let num7 = 15;
let resultStr = num7 > 10? 'Greater than 10' : 'Less than or equal to 10';
console.log(resultStr); // 输出 Greater than 10
- 双问号(??)操作符
空值合并操作符
??
返回其左侧操作数,如果左侧操作数为null
或undefined
,则返回其右侧操作数。
let value1 = null;
let value2 = 'default value';
let resultValue = value1?? value2;
console.log(resultValue); // 输出 default value
- 展开操作符(...)
- 数组展开:可以将数组展开为独立的元素。
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combinedArr = [...arr1, ...arr2];
console.log(combinedArr); // 输出 [1, 2, 3, 4, 5, 6]
- **函数参数展开**:在函数调用时,可以将数组作为参数展开。
function sum(...nums) {
return nums.reduce((acc, num) => acc + num, 0);
}
let numbersToSum = [1, 2, 3, 4];
let total = sum(...numbersToSum);
console.log(total); // 输出 10
- **对象展开**:可以将对象的属性展开到新对象中。
let obj1 = { a: 1, b: 2 };
let obj2 = { ...obj1, c: 3 };
console.log(obj2); // 输出 { a: 1, b: 2, c: 3 }
- 剩余参数(...) 在函数定义中,剩余参数用于将多个参数收集到一个数组中。
function printArgs(...args) {
args.forEach(arg => console.log(arg));
}
printArgs('Hello', 'World', 123);
事件驱动编程
事件驱动编程基础概念
- 什么是事件驱动编程
事件驱动编程是一种编程范式,程序的执行流程由事件来决定。在 JavaScript 中,事件通常与 DOM(文档对象模型)相关联,但也可用于其他环境,如 Node.js 中的服务器端编程。事件可以是用户操作(如点击按钮、滚动页面),也可以是系统操作(如页面加载完成、定时器触发)。
例如,当用户点击一个按钮时,会触发一个
click
事件,我们可以编写代码来响应这个事件,执行特定的操作。 - 事件流
在 DOM 中,事件流描述了从页面中接收事件的顺序。有两种主要的事件流模型:捕获阶段和冒泡阶段。
- 捕获阶段:事件从
window
对象开始,逐级向下传播到目标元素。例如,对于一个按钮点击事件,事件首先会到达window
,然后是document
,接着是包含按钮的父元素,直到按钮本身。 - 冒泡阶段:事件从目标元素开始,逐级向上传播到
window
对象。还是以按钮点击事件为例,事件首先在按钮上触发,然后传播到父元素,再到document
,最后到window
。 大多数现代浏览器默认使用冒泡阶段来处理事件,但可以通过设置addEventListener
的第三个参数来指定使用捕获阶段。
- 捕获阶段:事件从
在 JavaScript 中处理事件
- HTML 事件处理属性 在 HTML 标签中,可以直接使用事件处理属性来指定 JavaScript 代码。例如:
<button onclick="alert('Button clicked!')">Click me</button>
虽然这种方式简单直接,但会导致 HTML 和 JavaScript 代码紧密耦合,不利于代码的维护和复用。 2. DOM0 级事件处理程序 在 JavaScript 中,可以通过获取 DOM 元素,然后为其指定事件处理函数。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>DOM0 Events</title>
</head>
<body>
<button id="myButton">Click me</button>
<script>
let button = document.getElementById('myButton');
button.onclick = function () {
console.log('Button clicked via DOM0');
};
</script>
</body>
</html>
这种方式的优点是简单易懂,但一个元素只能绑定一个同类型的事件处理函数,如果多次赋值,后面的会覆盖前面的。
3. DOM2 级事件处理程序
addEventListener
方法是 DOM2 级事件处理的核心。它允许为一个元素添加多个同类型的事件处理函数,并且可以指定事件流阶段。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>DOM2 Events</title>
</head>
<body>
<button id="myButton2">Click me</button>
<script>
let button2 = document.getElementById('myButton2');
button2.addEventListener('click', function () {
console.log('First click handler');
});
button2.addEventListener('click', function () {
console.log('Second click handler');
});
// 使用捕获阶段
button2.addEventListener('click', function () {
console.log('Click handler in capture phase', true);
}, true);
</script>
</body>
</html>
removeEventListener
方法用于移除通过 addEventListener
添加的事件处理函数。需要注意的是,移除时传入的函数必须是与添加时相同的引用。
let clickHandler = function () {
console.log('Handler to be removed');
};
button2.addEventListener('click', clickHandler);
// 稍后移除
button2.removeEventListener('click', clickHandler);
常见的 DOM 事件
- 鼠标事件
- click:当用户点击元素时触发,包括鼠标左键、右键和中键。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Mouse Events</title>
</head>
<body>
<div id="clickDiv">Click me</div>
<script>
let clickDiv = document.getElementById('clickDiv');
clickDiv.addEventListener('click', function () {
console.log('Div clicked');
});
</script>
</body>
</html>
- **mousedown**:当鼠标按钮在元素上按下时触发。
- **mouseup**:当鼠标按钮在元素上释放时触发。
- **mousemove**:当鼠标指针在元素上移动时连续触发。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Mouse Move Event</title>
</head>
<body>
<div id="moveDiv" style="width: 200px; height: 200px; background - color: lightblue;"></div>
<script>
let moveDiv = document.getElementById('moveDiv');
moveDiv.addEventListener('mousemove', function (event) {
console.log('Mouse position: ' + event.clientX + ', ' + event.clientY);
});
</script>
</body>
</html>
- 键盘事件
- keydown:当键盘上的某个键被按下时触发。
- keyup:当键盘上的某个键被释放时触发。
- keypress:当键盘上的某个字符键被按下并释放时触发(不包括功能键,如 Shift、Ctrl 等)。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Keyboard Events</title>
</head>
<body>
<input type="text" id="keyInput">
<script>
let keyInput = document.getElementById('keyInput');
keyInput.addEventListener('keydown', function (event) {
console.log('Key ' + event.key + ' was pressed down');
});
keyInput.addEventListener('keyup', function (event) {
console.log('Key ' + event.key + ' was released');
});
keyInput.addEventListener('keypress', function (event) {
console.log('Character ' + event.key + ' was pressed and released');
});
</script>
</body>
</html>
- 表单事件
- submit:当表单被提交时触发,通常用于验证表单数据。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Form Events</title>
</head>
<body>
<form id="myForm">
<input type="text" name="username" placeholder="Username">
<input type="submit" value="Submit">
</form>
<script>
let myForm = document.getElementById('myForm');
myForm.addEventListener('submit', function (event) {
let username = myForm.elements['username'].value;
if (username === '') {
alert('Username cannot be empty');
event.preventDefault(); // 阻止表单提交
}
});
</script>
</body>
</html>
- **input**:当 `<input>` 或 `<textarea>` 元素的值发生变化时触发。
- **change**:当 `<input>`、`<textarea>` 或 `<select>` 元素的值发生改变且失去焦点时触发。
4. 页面加载与卸载事件 - load:当页面或资源(如图片)完全加载完成时触发。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Load Event</title>
</head>
<body>
<img src="example.jpg" id="myImage">
<script>
let myImage = document.getElementById('myImage');
myImage.addEventListener('load', function () {
console.log('Image loaded successfully');
});
window.addEventListener('load', function () {
console.log('Page fully loaded');
});
</script>
</body>
</html>
- **unload**:当页面即将被卸载(如用户导航离开页面)时触发。可以用于执行一些清理操作,如取消定时器。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Unload Event</title>
</head>
<body>
<script>
window.addEventListener('unload', function () {
console.log('Page is being unloaded');
});
</script>
</body>
</html>
事件对象
- 事件对象的属性和方法
当事件触发时,事件处理函数会接收到一个事件对象,该对象包含了与事件相关的信息。
- target:触发事件的目标元素。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Event Target</title>
</head>
<body>
<div id="parentDiv">
<button id="childButton">Click me</button>
</div>
<script>
let parentDiv = document.getElementById('parentDiv');
parentDiv.addEventListener('click', function (event) {
if (event.target.id === 'childButton') {
console.log('Button inside div was clicked');
} else {
console.log('Div was clicked');
}
});
</script>
</body>
</html>
- **type**:事件的类型,如 'click'、'mousemove' 等。
- **preventDefault()**:阻止事件的默认行为。例如,在链接点击事件中,阻止页面跳转到链接的目标地址。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Prevent Default</title>
</head>
<body>
<a href="https://www.example.com" id="myLink">Click me</a>
<script>
let myLink = document.getElementById('myLink');
myLink.addEventListener('click', function (event) {
event.preventDefault();
console.log('Link click prevented');
});
</script>
</body>
</html>
- **stopPropagation()**:阻止事件在 DOM 树中继续传播,既可以阻止捕获阶段也可以阻止冒泡阶段的传播。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Stop Propagation</title>
</head>
<body>
<div id="outerDiv">
<div id="innerDiv">Click me</div>
</div>
<script>
let outerDiv = document.getElementById('outerDiv');
let innerDiv = document.getElementById('innerDiv');
outerDiv.addEventListener('click', function () {
console.log('Outer div clicked');
});
innerDiv.addEventListener('click', function (event) {
console.log('Inner div clicked');
event.stopPropagation();
});
</script>
</body>
</html>
- 事件委托 事件委托是一种利用事件冒泡机制的编程技巧。通过将事件处理函数绑定到父元素,而不是每个子元素,来处理子元素的事件。这样可以减少内存占用,提高性能。 例如,有一个列表,每个列表项都需要点击事件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Event Delegation</title>
</head>
<body>
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<script>
let myList = document.getElementById('myList');
myList.addEventListener('click', function (event) {
if (event.target.tagName === 'LI') {
console.log('Clicked on item: ' + event.target.textContent);
}
});
</script>
</body>
</html>
这种方式不仅适用于已有的元素,对于动态添加的子元素同样有效,因为事件冒泡机制不受元素添加时间的影响。
自定义事件
- 创建和触发自定义事件 在 JavaScript 中,可以创建并触发自定义事件。这在组件化编程中非常有用,允许组件之间进行松耦合的通信。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Custom Events</title>
</head>
<body>
<div id="customDiv"></div>
<script>
let customDiv = document.getElementById('customDiv');
// 创建自定义事件
let myCustomEvent = new CustomEvent('myCustomEvent', {
detail: {
message: 'This is a custom event'
}
});
// 绑定事件处理函数
customDiv.addEventListener('myCustomEvent', function (event) {
console.log(event.detail.message);
});
// 触发自定义事件
customDiv.dispatchEvent(myCustomEvent);
</script>
</body>
</html>
- 使用 CustomEvent 构造函数
CustomEvent
构造函数接受两个参数,第一个是事件名称,第二个是一个可选的对象,用于定义事件的详细信息(detail
属性)。
// 创建一个带参数的自定义事件
let myEvent = new CustomEvent('newEvent', {
detail: {
data: 'Some data',
value: 42
}
});
- 在不同组件间使用自定义事件 在更复杂的应用中,可以在不同的组件间使用自定义事件进行通信。例如,在一个简单的模块化应用中:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Component Custom Events</title>
</head>
<body>
<div id="component1"></div>
<div id="component2"></div>
<script>
let component1 = document.getElementById('component1');
let component2 = document.getElementById('component2');
// Component 1 触发自定义事件
let component1Event = new CustomEvent('component1Event', {
detail: {
message: 'Component 1 says hello'
}
});
component1.addEventListener('click', function () {
component1.dispatchEvent(component1Event);
});
// Component 2 监听自定义事件
component2.addEventListener('component1Event', function (event) {
console.log(event.detail.message);
});
</script>
</body>
</html>
这样,通过自定义事件,不同的组件之间可以进行灵活的通信,而不需要紧密耦合的依赖关系。
事件驱动编程在实际项目中的应用
- 单页应用(SPA)中的事件处理 在单页应用中,事件驱动编程起着关键作用。例如,在一个基于 Vue.js 或 React 的 SPA 中,用户的交互(如按钮点击、表单提交)会触发事件,从而导致视图的更新。 以 Vue.js 为例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>SPA Event Handling</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="increment">Increment</button>
<p>Count: {{ count }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
count: 0
},
methods: {
increment: function () {
this.count++;
}
}
});
</script>
</body>
</html>
这里的 @click
是 Vue.js 中用于绑定点击事件的语法糖,当按钮被点击时,会触发 increment
方法,从而更新视图中的 count
值。
2. 实时应用中的事件处理
在实时应用(如聊天应用、实时仪表盘)中,事件驱动编程用于处理实时数据的更新。例如,在一个基于 WebSocket 的聊天应用中:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Real - Time Chat</title>
</head>
<body>
<input type="text" id="messageInput">
<button id="sendButton">Send</button>
<div id="chatWindow"></div>
<script>
let socket = new WebSocket('ws://localhost:8080');
let messageInput = document.getElementById('messageInput');
let sendButton = document.getElementById('sendButton');
let chatWindow = document.getElementById('chatWindow');
socket.addEventListener('open', function () {
console.log('Connected to server');
});
socket.addEventListener('message', function (event) {
chatWindow.innerHTML += '<p>' + event.data + '</p>';
});
sendButton.addEventListener('click', function () {
let message = messageInput.value;
socket.send(message);
messageInput.value = '';
});
</script>
</body>
</html>
在这个例子中,WebSocket
的 open
事件表示连接成功,message
事件用于接收服务器发送的消息,按钮的 click
事件用于发送用户输入的消息。
3. 游戏开发中的事件处理
在 JavaScript 游戏开发中,事件驱动编程用于处理玩家的输入,如按键操作、鼠标移动等。例如,在一个简单的 Canvas 游戏中:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>Canvas Game</title>
</head>
<body>
<canvas id="gameCanvas" width="800" height="600"></canvas>
<script>
let canvas = document.getElementById('gameCanvas');
let ctx = canvas.getContext('2d');
let playerX = 400;
let playerY = 300;
document.addEventListener('keydown', function (event) {
if (event.key === 'ArrowLeft') {
playerX -= 10;
} else if (event.key === 'ArrowRight') {
playerX += 10;
} else if (event.key === 'ArrowUp') {
playerY -= 10;
} else if (event.key === 'ArrowDown') {
playerY += 10;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(playerX, playerY, 20, 0, 2 * Math.PI);
ctx.fillStyle ='red';
ctx.fill();
});
</script>
</body>
</html>
这里通过监听键盘的 keydown
事件,根据用户按下的方向键来移动游戏中的角色。
通过对 JavaScript 公认符号和事件驱动编程的深入理解和应用,开发者可以创建出交互性强、响应灵敏的 Web 应用和其他 JavaScript 项目。从简单的页面交互到复杂的单页应用、实时应用和游戏开发,这些知识都是不可或缺的基础。