MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript公认符号与事件驱动编程

2021-08-243.2k 阅读

JavaScript 公认符号

基本语法符号

  1. 分号(;) 在 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
  1. 花括号({}) 花括号在 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 - elseforwhile 等语句中,花括号包裹需要执行的代码:

let num = 5;
if (num > 3) {
    console.log(num + ' is greater than 3');
}
  1. 圆括号(()) 圆括号主要用于函数调用,传递参数:
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;
}
  1. 方括号([]) 方括号用于定义数组:
let numbers = [1, 2, 3, 4, 5];
console.log(numbers[2]); // 输出 3

在对象中,方括号可以用于访问属性,当属性名是变量或者包含特殊字符时:

let person = {
    name: 'Bob'
};
let prop = 'name';
console.log(person[prop]); // 输出 Bob

操作符符号

  1. 算术操作符
    • 加法(+):用于数字相加,也可用于字符串拼接。
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
  1. 赋值操作符
    • 简单赋值(=):将右边的值赋给左边的变量。
let x = 10;
- **复合赋值(+=, -=, *=, /=, %=)**:
let y = 5;
y += 3; // 等同于 y = y + 3
console.log(y); // 输出 8

y *= 2; // 等同于 y = y * 2
console.log(y); // 输出 16
  1. 比较操作符
    • 等于(==)严格等于(===)== 会进行类型转换后比较,=== 不会进行类型转换。
console.log(5 == '5'); // 输出 true
console.log(5 === '5'); // 输出 false
- **大于(>)**、**小于(<)**、**大于等于(>=)**、**小于等于(<=)**:
console.log(7 > 5); // 输出 true
console.log(3 <= 3); // 输出 true
  1. 逻辑操作符
    • 逻辑与(&&):只有当两个操作数都为真时,结果才为真。
let a = true;
let b = false;
console.log(a && b); // 输出 false
- **逻辑或(||)**:只要有一个操作数为真,结果就为真。
console.log(a || b); // 输出 true
- **逻辑非(!)**:用于取反操作数的布尔值。
console.log(!a); // 输出 false
  1. 位操作符
    • 按位与(&):对两个操作数的每一位进行与运算。
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);

特殊符号

  1. 点号(.) 在 JavaScript 中,点号用于访问对象的属性和方法。
let person = {
    name: 'Alice',
    age: 25,
    sayAge: function() {
        console.log('I\'m ' + this.age + ' years old');
    }
};
console.log(person.name);
person.sayAge();
  1. 逗号(,) 逗号可以用于在一条语句中分隔多个变量声明或函数参数。
let num5, num6;
num5 = 10;
num6 = 20;

function printValues(a, b) {
    console.log(a + ' and ' + b);
}
printValues(num5, num6);
  1. 冒号(:) 在对象字面量中,冒号用于分隔属性名和属性值。
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');
}
  1. 问号(?)和冒号(:)组成的三元操作符 三元操作符是一种简洁的条件表达式,语法为 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
  1. 双问号(??)操作符 空值合并操作符 ?? 返回其左侧操作数,如果左侧操作数为 nullundefined,则返回其右侧操作数。
let value1 = null;
let value2 = 'default value';
let resultValue = value1?? value2;
console.log(resultValue); // 输出 default value
  1. 展开操作符(...)
    • 数组展开:可以将数组展开为独立的元素。
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 }
  1. 剩余参数(...) 在函数定义中,剩余参数用于将多个参数收集到一个数组中。
function printArgs(...args) {
    args.forEach(arg => console.log(arg));
}
printArgs('Hello', 'World', 123);

事件驱动编程

事件驱动编程基础概念

  1. 什么是事件驱动编程 事件驱动编程是一种编程范式,程序的执行流程由事件来决定。在 JavaScript 中,事件通常与 DOM(文档对象模型)相关联,但也可用于其他环境,如 Node.js 中的服务器端编程。事件可以是用户操作(如点击按钮、滚动页面),也可以是系统操作(如页面加载完成、定时器触发)。 例如,当用户点击一个按钮时,会触发一个 click 事件,我们可以编写代码来响应这个事件,执行特定的操作。
  2. 事件流 在 DOM 中,事件流描述了从页面中接收事件的顺序。有两种主要的事件流模型:捕获阶段和冒泡阶段。
    • 捕获阶段:事件从 window 对象开始,逐级向下传播到目标元素。例如,对于一个按钮点击事件,事件首先会到达 window,然后是 document,接着是包含按钮的父元素,直到按钮本身。
    • 冒泡阶段:事件从目标元素开始,逐级向上传播到 window 对象。还是以按钮点击事件为例,事件首先在按钮上触发,然后传播到父元素,再到 document,最后到 window。 大多数现代浏览器默认使用冒泡阶段来处理事件,但可以通过设置 addEventListener 的第三个参数来指定使用捕获阶段。

在 JavaScript 中处理事件

  1. 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 事件

  1. 鼠标事件
    • 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>
  1. 键盘事件
    • 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>
  1. 表单事件
    • 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>

事件对象

  1. 事件对象的属性和方法 当事件触发时,事件处理函数会接收到一个事件对象,该对象包含了与事件相关的信息。
    • 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>
  1. 事件委托 事件委托是一种利用事件冒泡机制的编程技巧。通过将事件处理函数绑定到父元素,而不是每个子元素,来处理子元素的事件。这样可以减少内存占用,提高性能。 例如,有一个列表,每个列表项都需要点击事件:
<!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>

这种方式不仅适用于已有的元素,对于动态添加的子元素同样有效,因为事件冒泡机制不受元素添加时间的影响。

自定义事件

  1. 创建和触发自定义事件 在 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>
  1. 使用 CustomEvent 构造函数 CustomEvent 构造函数接受两个参数,第一个是事件名称,第二个是一个可选的对象,用于定义事件的详细信息(detail 属性)。
// 创建一个带参数的自定义事件
let myEvent = new CustomEvent('newEvent', {
    detail: {
        data: 'Some data',
        value: 42
    }
});
  1. 在不同组件间使用自定义事件 在更复杂的应用中,可以在不同的组件间使用自定义事件进行通信。例如,在一个简单的模块化应用中:
<!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>

这样,通过自定义事件,不同的组件之间可以进行灵活的通信,而不需要紧密耦合的依赖关系。

事件驱动编程在实际项目中的应用

  1. 单页应用(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>

在这个例子中,WebSocketopen 事件表示连接成功,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 项目。从简单的页面交互到复杂的单页应用、实时应用和游戏开发,这些知识都是不可或缺的基础。