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

JavaScript在Web编程中的核心要点

2023-02-163.3k 阅读

一、JavaScript 的基础语法

(一)变量声明与作用域

在 JavaScript 中,变量声明主要通过 varletconst 关键字来实现。var 是 ES5 及以前使用的声明方式,它存在函数作用域和变量提升的特性。例如:

function varScopeExample() {
    console.log(x); // 输出 undefined,变量提升
    var x = 10;
    console.log(x); // 输出 10
}
varScopeExample();

letconst 是 ES6 引入的声明方式,它们具有块级作用域。let 声明的变量可以重新赋值,const 声明的常量一旦赋值就不能再改变。

function letScopeExample() {
    if (true) {
        let y = 20;
        console.log(y); // 输出 20
    }
    console.log(y); // 报错,y 在此作用域未定义
}
letScopeExample();
function constExample() {
    const PI = 3.14159;
    // PI = 3.14; // 报错,常量不能重新赋值
    console.log(PI); // 输出 3.14159
}
constExample();

(二)数据类型

  1. 基本数据类型 JavaScript 有六种基本数据类型:stringnumberbooleannullundefinedsymbol(ES6 新增)。
  • 字符串(string):用于表示文本数据,用单引号或双引号包裹。
let str1 = 'Hello';
let str2 = "World";
  • 数字(number):可以表示整数和浮点数。
let num1 = 10;
let num2 = 3.14;
  • 布尔值(boolean):只有 truefalse 两个值。
let isDone = true;
let isFailed = false;
  • 空值(null):表示一个空的对象引用,通常用于手动释放内存或初始化变量。
let myObject = null;
  • 未定义(undefined):当变量声明但未赋值时,其值为 undefined
let someVar;
console.log(someVar); // 输出 undefined
  • 符号(symbol):ES6 新增的数据类型,每个 symbol 值都是唯一的。常用于创建对象的唯一属性。
let sym1 = Symbol('description');
let obj = {};
obj[sym1] = 'value';
console.log(obj[sym1]); // 输出 'value'
  1. 引用数据类型 主要包括 ObjectArrayFunction 等。引用数据类型的值存储在堆内存中,变量保存的是指向堆内存中实际数据的引用。
  • 对象(Object):是键值对的集合,用于组织和存储数据。
let person = {
    name: 'John',
    age: 30,
    sayHello: function() {
        console.log('Hello, I\'m'+ this.name);
    }
};
person.sayHello(); // 输出 'Hello, I'm John'
  • 数组(Array):用于存储有序的元素集合。
let numbers = [1, 2, 3, 4, 5];
console.log(numbers[0]); // 输出 1
  • 函数(Function):是可执行的代码块,可以接收参数并返回值。
function add(a, b) {
    return a + b;
}
let result = add(3, 5);
console.log(result); // 输出 8

(三)操作符

  1. 算术操作符 包括 +-*/% 等,用于基本的数学运算。
let a = 10;
let b = 3;
console.log(a + b); // 输出 13
console.log(a - b); // 输出 7
console.log(a * b); // 输出 30
console.log(a / b); // 输出 3.3333333333333335
console.log(a % b); // 输出 1
  1. 比较操作符><>=<======!=!== 等,用于比较两个值的大小或相等性。
let x = 5;
let y = 10;
console.log(x > y); // 输出 false
console.log(x < y); // 输出 true
console.log(x == y); // 输出 false
console.log(x === y); // 输出 false
console.log(x!= y); // 输出 true
console.log(x!== y); // 输出 true
  1. 逻辑操作符 &&(逻辑与)、||(逻辑或)、!(逻辑非)。
let isTrue = true;
let isFalse = false;
console.log(isTrue && isFalse); // 输出 false
console.log(isTrue || isFalse); // 输出 true
console.log(!isTrue); // 输出 false
  1. 赋值操作符 = 用于赋值,还有复合赋值操作符如 +=-=*=/= 等。
let num = 5;
num += 3; // 相当于 num = num + 3;
console.log(num); // 输出 8
  1. 位操作符 &(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)、>>>(无符号右移)。位操作符对二进制表示的数字进行操作。
let num1 = 5; // 二进制 00000101
let num2 = 3; // 二进制 00000011
console.log(num1 & num2); // 按位与,输出 1,二进制 00000001
console.log(num1 | num2); // 按位或,输出 7,二进制 00000111
console.log(num1 ^ num2); // 按位异或,输出 6,二进制 00000110
console.log(~num1); // 按位取反,输出 -6,二进制 11111010
console.log(num1 << 2); // 左移 2 位,输出 20,二进制 00010100
console.log(num1 >> 1); // 右移 1 位,输出 2,二进制 00000010
console.log(num1 >>> 1); // 无符号右移 1 位,输出 2,二进制 00000010

二、JavaScript 的函数与作用域链

(一)函数定义与调用

  1. 函数声明
function addNumbers(a, b) {
    return a + b;
}
let sum = addNumbers(2, 3);
console.log(sum); // 输出 5
  1. 函数表达式
let subtractNumbers = function(a, b) {
    return a - b;
};
let difference = subtractNumbers(5, 3);
console.log(difference); // 输出 2
  1. 箭头函数 ES6 引入的箭头函数语法更加简洁。
let multiplyNumbers = (a, b) => a * b;
let product = multiplyNumbers(4, 3);
console.log(product); // 输出 12

(二)函数的参数与返回值

  1. 参数 函数可以接受零个或多个参数。在 ES6 中,还支持默认参数值。
function greet(name = 'Guest') {
    console.log('Hello,'+ name);
}
greet(); // 输出 'Hello, Guest'
greet('John'); // 输出 'Hello, John'
  1. 返回值 函数可以通过 return 语句返回一个值。如果没有 return 语句,函数默认返回 undefined
function square(x) {
    return x * x;
}
let result = square(4);
console.log(result); // 输出 16

(三)作用域链

JavaScript 采用词法作用域(静态作用域),即函数的作用域在定义时就确定了,而不是在调用时确定。作用域链是由多个执行上下文的变量对象组成的链表。当访问一个变量时,JavaScript 引擎会首先在当前执行上下文的变量对象中查找,如果找不到,就会沿着作用域链向上查找,直到全局执行上下文。

let globalVar = 'global';
function outer() {
    let outerVar = 'outer';
    function inner() {
        let innerVar = 'inner';
        console.log(globalVar); // 输出 'global'
        console.log(outerVar); // 输出 'outer'
        console.log(innerVar); // 输出 'inner'
    }
    inner();
}
outer();

在这个例子中,inner 函数可以访问到自己作用域内的 innerVar,以及其父作用域 outerouterVar 和全局作用域的 globalVar

三、JavaScript 的对象与原型链

(一)对象的创建与属性操作

  1. 对象字面量创建
let car = {
    brand: 'Toyota',
    model: 'Corolla',
    year: 2020
};
console.log(car.brand); // 输出 'Toyota'
  1. 使用 new 关键字创建对象
function Person(name, age) {
    this.name = name;
    this.age = age;
}
let person1 = new Person('Alice', 25);
console.log(person1.name); // 输出 'Alice'
  1. 属性操作 可以使用点语法(.)或方括号语法([])来访问和修改对象的属性。
let obj = {
    key1: 'value1',
    key2: 'value2'
};
console.log(obj.key1); // 输出 'value1'
console.log(obj['key2']); // 输出 'value2'
obj.key1 = 'new value1';
obj['key2'] = 'new value2';
console.log(obj.key1); // 输出 'new value1'
console.log(obj['key2']); // 输出 'new value2'

(二)原型链

每个 JavaScript 对象都有一个 [[Prototype]] 内部属性(在现代 JavaScript 中可以通过 __proto__ 访问),它指向其原型对象。原型对象本身也是一个对象,也有自己的原型,这样就形成了一条原型链。当访问对象的属性或方法时,如果在当前对象中找不到,就会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(Object.prototype)。

function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() {
    console.log(this.name +'makes a sound.');
};
function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
    console.log(this.name +'barks.');
};
let myDog = new Dog('Buddy', 'Golden Retriever');
myDog.speak(); // 输出 'Buddy makes a sound.'
myDog.bark(); // 输出 'Buddy barks.'

在这个例子中,Dog 继承自 AnimalmyDog 对象可以访问 Animal.prototype 上的 speak 方法和 Dog.prototype 上的 bark 方法。

四、JavaScript 在 DOM 操作中的应用

(一)获取 DOM 元素

  1. 通过 ID 获取 使用 document.getElementById() 方法,通过元素的 id 属性获取单个元素。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <div id="myDiv">This is a div</div>
    <script>
        let myDiv = document.getElementById('myDiv');
        console.log(myDiv.textContent); // 输出 'This is a div'
    </script>
</body>

</html>
  1. 通过标签名获取 document.getElementsByTagName() 方法返回一个包含所有指定标签名元素的 HTMLCollection。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
    <script>
        let listItems = document.getElementsByTagName('li');
        for (let i = 0; i < listItems.length; i++) {
            console.log(listItems[i].textContent);
        }
    </script>
</body>

</html>
  1. 通过类名获取 document.getElementsByClassName() 方法返回一个包含所有指定类名元素的 HTMLCollection。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <div class="myClass">Div 1</div>
    <div class="myClass">Div 2</div>
    <script>
        let divs = document.getElementsByClassName('myClass');
        for (let i = 0; i < divs.length; i++) {
            console.log(divs[i].textContent);
        }
    </script>
</body>

</html>
  1. 使用 querySelectorquerySelectorAll document.querySelector() 方法返回匹配指定 CSS 选择器的第一个元素,document.querySelectorAll() 方法返回所有匹配指定 CSS 选择器的元素的 NodeList。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <p class="highlight">Paragraph 1</p>
    <p>Paragraph 2</p>
    <script>
        let firstHighlighted = document.querySelector('.highlight');
        console.log(firstHighlighted.textContent); // 输出 'Paragraph 1'
        let allParagraphs = document.querySelectorAll('p');
        for (let i = 0; i < allParagraphs.length; i++) {
            console.log(allParagraphs[i].textContent);
        }
    </script>
</body>

</html>

(二)修改 DOM 元素

  1. 修改文本内容 通过修改元素的 textContentinnerHTML 属性来改变元素的文本内容。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <p id="myPara">Original text</p>
    <script>
        let myPara = document.getElementById('myPara');
        myPara.textContent = 'New text';
        console.log(myPara.textContent); // 输出 'New text'
    </script>
</body>

</html>
  1. 修改属性 使用 setAttribute() 方法来修改元素的属性。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <img id="myImg" src="old.jpg" alt="Old Image">
    <script>
        let myImg = document.getElementById('myImg');
        myImg.setAttribute('src', 'new.jpg');
        myImg.setAttribute('alt', 'New Image');
    </script>
</body>

</html>
  1. 添加和移除类 可以通过操作元素的 classList 属性来添加和移除类。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
    <style>
       .highlight {
            background - color: yellow;
        }
    </style>
</head>

<body>
    <p id="myPara">Some text</p>
    <script>
        let myPara = document.getElementById('myPara');
        myPara.classList.add('highlight');
        setTimeout(() => {
            myPara.classList.remove('highlight');
        }, 3000);
    </script>
</body>

</html>

(三)创建与插入 DOM 元素

  1. 创建元素 使用 document.createElement() 方法创建新的 DOM 元素。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <div id="parentDiv"></div>
    <script>
        let newPara = document.createElement('p');
        newPara.textContent = 'This is a new paragraph';
        let parentDiv = document.getElementById('parentDiv');
        parentDiv.appendChild(newPara);
    </script>
</body>

</html>
  1. 插入元素 除了 appendChild() 方法,还可以使用 insertBefore() 方法在指定元素之前插入新元素。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <ul id="myList">
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
    <script>
        let newItem = document.createElement('li');
        newItem.textContent = 'Item 1';
        let myList = document.getElementById('myList');
        let firstChild = myList.firstChild;
        myList.insertBefore(newItem, firstChild);
    </script>
</body>

</html>

(四)事件处理

  1. 事件绑定 可以通过多种方式绑定事件处理函数。
  • 直接在 HTML 标签中绑定
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <button onclick="handleClick()">Click me</button>
    <script>
        function handleClick() {
            console.log('Button clicked');
        }
    </script>
</body>

</html>
  • 通过 JavaScript 属性绑定
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <button id="myButton">Click me</button>
    <script>
        let myButton = document.getElementById('myButton');
        myButton.onclick = function() {
            console.log('Button clicked');
        };
    </script>
</body>

</html>
  • 使用 addEventListener 方法绑定
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <button id="myButton">Click me</button>
    <script>
        let myButton = document.getElementById('myButton');
        myButton.addEventListener('click', function() {
            console.log('Button clicked');
        });
    </script>
</body>

</html>
  1. 事件对象 事件处理函数会接收到一个事件对象,该对象包含了与事件相关的信息,如鼠标位置、键盘按键等。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</title>
</head>

<body>
    <div id="myDiv">Click me</div>
    <script>
        let myDiv = document.getElementById('myDiv');
        myDiv.addEventListener('click', function(event) {
            console.log('Client X:'+ event.clientX);
            console.log('Client Y:'+ event.clientY);
        });
    </script>
</body>

</html>
  1. 事件冒泡与捕获 事件在 DOM 树中传播有两种方式:冒泡和捕获。冒泡是从最具体的元素(触发事件的元素)开始,向上传播到 DOM 树的根节点;捕获则相反,从根节点开始,向下传播到最具体的元素。addEventListener 方法的第三个参数可以指定是否使用捕获模式(true 表示捕获,false 表示冒泡,默认 false)。
<!DOCTYPE html>
<html>

<head>
    <title>DOM Example</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 (bubbling)');
        });
        innerDiv.addEventListener('click', function() {
            console.log('Inner div clicked');
        }, true);
        outerDiv.addEventListener('click', function() {
            console.log('Outer div clicked (capturing)', true);
        }, true);
    </script>
</body>

</html>

在这个例子中,当点击 innerDiv 时,会先触发 outerDiv 的捕获阶段事件,然后触发 innerDiv 的事件,最后触发 outerDiv 的冒泡阶段事件。

五、JavaScript 的异步编程

(一)回调函数

回调函数是 JavaScript 异步编程的基础。它是作为参数传递给另一个函数,并在该函数完成操作后被调用的函数。

function getData(callback) {
    setTimeout(() => {
        let data = { message: 'Data fetched' };
        callback(data);
    }, 2000);
}
getData((result) => {
    console.log(result.message); // 输出 'Data fetched'
});

(二)Promise

Promise 是 ES6 引入的用于处理异步操作的对象。它有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。

function asyncOperation() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let success = true;
            if (success) {
                resolve('Operation successful');
            } else {
                reject('Operation failed');
            }
        }, 2000);
    });
}
asyncOperation()
   .then((result) => {
        console.log(result); // 输出 'Operation successful'
    })
   .catch((error) => {
        console.log(error);
    });

(三)async/await

async/await 是基于 Promise 的异步编程语法糖,使异步代码看起来更像同步代码。

async function main() {
    try {
        let result = await asyncOperation();
        console.log(result); // 输出 'Operation successful'
    } catch (error) {
        console.log(error);
    }
}
main();

(四)事件循环

JavaScript 是单线程语言,但通过事件循环机制实现异步操作。事件循环不断检查调用栈是否为空,如果为空,就从任务队列中取出一个任务放入调用栈执行。任务队列分为宏任务队列和微任务队列,微任务会在宏任务之前执行。

console.log('Start');
setTimeout(() => {
    console.log('Timeout');
}, 0);
Promise.resolve().then(() => {
    console.log('Promise');
});
console.log('End');
// 输出:Start, End, Promise, Timeout

在这个例子中,console.log('Start')console.log('End') 首先执行,然后 Promise.resolve().then() 中的回调函数被放入微任务队列,setTimeout() 的回调函数被放入宏任务队列。由于微任务先于宏任务执行,所以输出顺序为 StartEndPromiseTimeout

六、JavaScript 的模块化

(一)ES6 模块

ES6 引入了官方的模块化系统。模块可以导出变量、函数、类等,其他模块可以通过 import 语句导入。

  1. 导出模块
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
  1. 导入模块
// main.js
import { add, subtract } from './math.js';
console.log(add(3, 5)); // 输出 8
console.log(subtract(5, 3)); // 输出 2

(二)CommonJS 模块

CommonJS 是 Node.js 中使用的模块化规范。通过 exportsmodule.exports 导出模块,使用 require 函数导入模块。

  1. 导出模块
// math.js
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
// 或者
module.exports = {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b
};
  1. 导入模块
// main.js
const math = require('./math.js');
console.log(math.add(3, 5)); // 输出 8
console.log(math.subtract(5, 3)); // 输出 2

(三)AMD 模块(Asynchronous Module Definition)

AMD 是为浏览器环境设计的模块化规范,主要用于异步加载模块。常用的实现库有 RequireJS。

  1. 定义模块
// math.js
define(['exports'], function(exports) {
    exports.add = function(a, b) {
        return a + b;
    };
    exports.subtract = function(a, b) {
        return a - b;
    };
});
  1. 加载模块
<!DOCTYPE html>
<html>

<head>
    <title>AMD Example</title>
    <script data-main="main.js" src="require.js"></script>
</head>

<body>

</body>

</html>
// main.js
require(['math'], function(math) {
    console.log(math.add(3, 5)); // 输出 8
    console.log(math.subtract(5, 3)); // 输出 2
});

(四)UMD 模块(Universal Module Definition)

UMD 模块旨在兼容多种模块规范,既可以在 Node.js 环境中作为 CommonJS 模块使用,也可以在浏览器环境中作为 AMD 模块或全局变量使用。

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD 环境
        define(['exports'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS 环境
        factory(exports);
    } else {
        // 全局变量环境
        root.math = factory({});
    }
}(this, function (exports) {
    exports.add = function (a, b) {
        return a + b;
    };
    exports.subtract = function (a, b) {
        return a - b;
    };
}));

在不同的环境中,可以根据模块加载器的类型来加载 UMD 模块。这样的设计使得代码可以在多种环境下复用,提高了代码的可移植性。

通过对以上核心要点的深入理解和掌握,开发者能够更加熟练地运用 JavaScript 进行高效、健壮的 Web 编程。无论是构建简单的网页交互,还是复杂的单页应用程序,这些知识都将是坚实的基础。同时,随着 JavaScript 不断发展,如 ES 新特性的持续推出,开发者需要保持学习,以跟上技术的步伐,充分发挥 JavaScript 在 Web 编程中的强大能力。