变量与数据类型
变量声明:var、let和const的区别
var: 全局作用域下声明的变量是全局变量,可以被整个脚本访问。
函数作用域下声明的变量是局部变量,只能在该函数内部访问。
var声明的变量有变量提升(hoisting)现象,即变量在声明之前就已经存在,只是值是
undefined。
let: let声明的变量也有变量提升现象,但是声明的变量在声明之前不能被访问,其值依然是
undefined。
let声明的变量在块级作用域内有效,可以防止变量被意外修改。
const: const声明的变量在声明时必须赋值,并且一旦赋值就不能再被修改。
const声明的变量也有变量提升现象,但是在声明之前不能被访问,其值依然是undefined。
数据类型:JavaScript的七种数据类型
JavaScript的七种数据类型 | |
数据类型 | 说明 |
Undefined | 变量声明后没有赋值时,默认值为undefined。 |
Null | 表示空对象指针,即一个空值。 |
Boolean | 表示布尔值,true或false。 |
String | 表示字符串,由单引号或双引号括起来的字符序列。 |
Number | 表示数字,包括整数和浮点数。 |
Object | 表示对象,可以是自定义对象或内置对象。 |
Symbol | 表示独一无二的值,用于创建对象的私有属性。 |
类型转换的规则
显式类型转换:使用String()、Number()、Boolean()等函数来显式转换数据类型。
隐式类型转换:在运算过程中,JavaScript会自动进行类型转换。例如,字符串和数字相加
时,字符串会被转换为数字。
运算符
1.算术运算符
优先级:算术运算符的优先级由高到低为:括号、指数、乘除、加减(即 括号 >指数>乘除>
加减)。
特殊行为:加法运算符可以用于字符串拼接(和python相似),例如"hello" + "world" =
"helloworld"。
2. 比较运算符
工作原理:比较运算符用于比较两个值,返回布尔值true或false。
浮点数比较: 由于浮点数的表示精度问题,直接比较浮点数可能不准确,
可以使用Math.abs(a b) < 0.00001来近似比较。
类型比较:在比较运算符中,如果两个操作数的数据类型不同,JavaScript会自动将它们转换
为同一类型再进行比较。
逻辑运算符
短路逻辑:当逻辑运算符中包含函数调用时,如果第一个参数为false,则不会执行第二个参
数的函数。
条件赋值:使用逻辑运算符进行条件赋值,例如
a = (b > c) ? x : y
这个表达是的执行过程如下:
① 首先计算条件(b > c)。
② 如果条件 (b > c)
为真(即 b
大于 c
),则将 x
的值赋给 a
。
③ 如果条件 (b > c)
为假(即 b
不大于 c
),则将 y
的值赋给 a
。
下面是这个条件赋值语句对应的 if-else
形式:
if (b > c) {
a = x;
} else {
a = y;
}
控制结构
条件语句:switch语句
使用场景:switch语句适用于多分支条件判断,当有多个条件需要判断时使用。
注意事项:switch语句中的每个case必须有break语句,否则会继续执行下一个case。
循环语句:for、while、do-while
for 循环:for 循环通常用于循环次数已知的情况,可以通过设置初始值、循环条件和迭代语句
来控制循环。
while 循环:while循环适用于循环次数不确定的情况,当条件为true时继续执行循环体内的代
码。
do-while 循环:do-while循环与while循环类似,但它的循环条件在循环体之后判断,至少会
执行一次循环体。
控制循环流程( break和continue )
break:break语句用于立即退出循环,执行循环体后面的代码。
continue:continue语句用于跳过当前循环的剩余代码,直接进入下一次循环。
函数的高级应用
匿名函数与IIFE
匿名函数:在JavaScript中,匿名函数是指没有命名(没有函数名)的函数。
这些函数通常通过赋值给一个变量或作为回调函数传递给其他函数来使用。
匿名函数的一个常见应用是在事件监听器中,例如:
document.getElementById("button").addEventListener("click", function() {
console.log("按钮被点击了!");
});
在这个例子中,function() { ... }就是一个匿名函数,它被赋值给addEventListener函数的第一
个参数。
立即执行函数表达式(IIFE)
IIFE是一个在定义时立即执行的函数。
它通常用于创建一个局部作用域,避免全局命名空间的污染。
IIFE的语法如下:
(function() {
// 局部作用域内的代码
})();
代码部分在这个例子中,function() { ... }是一个匿名函数,它被立即执行。
由于它是一个匿名函数,它不会污染全局命名空间。
箭头函数
箭头函数:箭头函数是ES6( ECMAScript 2015 的简称,它是JavaScript语言的第六个正式版
本,由Ecma International在2015年6月发布)引入的一种新的函数定义方式,它比传统的函数声明
更简洁。箭头函数的语法如下:
const add = (a, b) => a + b;
箭头函数与普通函数的主要区别在于:
① 没有自己的this:箭头函数没有自己的this,它继承自外层作用域的this。
这意味着箭头函数中的this值取决于它被创建的作用域,而不是它在何处被调用。
② 没有自己的arguments:箭头函数没有自己的arguments对象,它使用剩余参数
(rest parameter)...args来获取所有参数。
③ 没有new.target:箭头函数没有new.target属性,因此不能被用作构造函数。
高阶函数
概念:高阶函数是指那些至少满足以下条件之一的函数:
① 接受一个或多个函数作为参数。
② 返回一个函数作为结果。
举个栗子:
1. map:对数组中的每个元素执行一个函数,并返回一个新数组。
const numbers = [1, 2, 3, 4, 5];
const squared = numbers.map(number => number * number);
console.log(squared); // 输出: [1, 4, 9, 16, 25]
2. filter:创建一个新数组,包含所有通过指定函数测试的元素。
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(number => number % 2 === 0);
console.log(evenNumbers); // 输出: [2, 4]
3.reduce:将数组中的所有元素通过一个函数累加为一个单一值。
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 输出: 15
闭包
概念: 指那些能够访问自己外部作用域变量的函数。
闭包可以让我们从内部函数访问外部函数作用域中的变量。
闭包的形成:当一个函数在另一个函数内部定义时,内部函数可以访问外部函数作用域中的
变量,即使外部函数已经执行完毕。
举个栗子:
function outer() {
let outerVar = 'I am from outer function';
function inner() {
let innerVar = 'I am from inner function';
console.log(outerVar); // 输出: I am from outer function
}
return inner;
}
const myClosure = outer();
myClosure();
闭包的应用:闭包常用于创建私有变量和封装代码,以避免全局命名空间的污染。
function createCounter() {
let count = 0;
return function() {
// 这是一个闭包
count += 1;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 输出: 1
console.log(counter()); // 输出: 2
在这个例子中,createCounter函数返回了一个闭包,它能够访问并修改createCounter作用域
内的count变量。
这样,count变量就被封装在闭包内部,不会污染全局作用域。
DOM操作的深度技巧
添加和删除节点
1. 创建节点
使用createElement方法可以创建一个新的HTML元素。
模板字符串不能直接用于createElement方法。
const newElement = document.createElement('div');
// 设置类名
newElement.className = 'container';
2. 添加节点
appendChild:将新创建的节点添加到父节点的子节点列表的末尾。
const parentElement = document.getElementById('parent');
parentElement.appendChild(newElement);
insertBefore:将新创建的节点插入到指定的子节点之前。
const referenceElement = document.getElementById('reference');
parentElement.insertBefore(newElement, referenceElement);
3. 删除节点:使用removeChild方法可以删除父节点的子节点。
parentElement.removeChild(newElement);
修改节点
1. 属性操作
使用.setAttribute方法可以设置元素的属性。
newElement.setAttribute('class', 'container');
使用.getAttribute方法可以获取元素的属性值。
const classValue = newElement.getAttribute('class');
2. 内容更新
文本更新:使用.textContent或.innerText方法可以更新元素的文本内容。
newElement.textContent = '这是一个新的div元素';
HTML更新:使用.innerHTML方法可以更新元素的HTML内容。
newElement.innerHTML = '<span>这是一个新的span元素</span>';
SVG更新:同样使用.innerHTML方法可以更新SVG元素的内容。
newElement.innerHTML = '<svg width="100" height="100"><circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow"/></svg>';
深入理解事件处理
事件冒泡与捕获
在 JavaScript 中,事件流描述了事件在DOM树中传播的路径。
事件流分为三个阶段
1. 事件捕获阶段
事件从document对象开始,向下遍历DOM树,直到到达目标元素。
在这个过程中,事件监听器在祖先元素上依次被触发。
2. 目标阶段
事件到达目标元素,触发在目标元素上注册的事件监听器。
3. 事件冒泡阶段
事件从目标元素开始,向上遍历DOM树,直到document对象。
在这个过程中,事件监听器在祖先元素上依次被触发。
事件冒泡与捕获的顺序是:事件首先经历捕获阶段,然后是目标阶段,最后是冒泡阶段。
事件委托
事件委托是一种利用事件冒泡机制来高效处理事件的技巧。
它允许我们在父元素上设置一个事件监听器,用以管理所有子元素的相同事件。
实现方法
在父元素上添加事件监听器,并利用事件对象的target属性来确定是哪个子元素触发了事
件。以下是一个栗子:
const parentElement = document.getElementById('parent');
parentElement.addEventListener('click', function(event) {
const target = event.target;
if (target.tagName === 'BUTTON') {
console.log('按钮被点击了!');
}
});
在这个例子中,无论哪个子按钮被点击,事件都会冒泡到父元素,并由父元素上的事件处理
程序捕获。
事件对象
事件对象包含了与事件相关的所有信息,例如:
target:表示触发事件的元素。
currentTarget:表示事件监听器绑定的元素。
type:表示事件的类型。
阻止事件的默认行为
某些事件,如点击链接或提交表单,具有默认行为。
例如,点击链接会导航到新的页面。我们可以使用事件对象的preventDefault方法来阻止这些
默认行为。下面是一个示例:
const form = document.getElementById('form');
form.addEventListener('submit', function(event) {
event.preventDefault();
// 处理表单数据
});