文章目录
- 1. 变量声明和作用域:
- 使用 var 关键字声明变量
- 函数作用域和全局作用域
- 变量提升
- 2. 数据类型:
- 基本数据类型:Number、String、Boolean、null、undefined
- 引用数据类型:Object、Array、Function、Date
- 3. 函数:
- 函数的定义和调用
- 函数参数和参数传递
- 函数作为值和回调函数
- 闭包和作用域链
- 4. 对象和原型:
- 对象的创建和属性访问
- 构造函数和 new 操作符
- 原型和原型链
- 原型链
- 原型
- 原型继承
- 继承实现方式
- 5. 数组操作:
- 数组的创建和访问
- 数组的常用方法:push、pop、shift、unshift、splice、slice、join、concat、reverse、sort、map、filter、reduce 等
- 6. 错误处理:
- try-catch-finally 语句
- throw 抛出异常
- 7. JSON:
- JSON 的字符串化和解析
- JSON 对象的使用
- 8. 正则表达式:
- 正则表达式的基本语法和匹配规则
- 正则表达式对象的方法
- 9. 严格模式:
- 使用 "use strict" 启用严格模式
- 严格模式下的限制和变化
1. 变量声明和作用域:
使用 var 关键字声明变量
在 ES5 中,可以使用 var
关键字来声明变量。以下是使用 var
关键字声明变量的示例:
var x = 10; // 声明一个名为 x 的变量,并赋值为 10
console.log(x); // 输出 10function myFunction() {var y = 20; // 声明一个名为 y 的变量,并赋值为 20console.log(y); // 输出 20
}myFunction(); // 调用函数,输出 20
console.log(y); // 报错,y 在函数外部不可见
在上面的示例中,通过使用 var
关键字,在不同的作用域中声明了两个变量 x
和 y
。在函数内部声明的变量 y
只在函数作用域中有效,而在全局作用域中声明的变量 x
则可以在整个代码中访问。
需要注意的是,使用 var
关键字声明的变量会存在变量提升(hoisting)的特性,即在其所在作用域中的声明被提升至作用域顶部。因此,可以在变量声明之前使用变量,但其值会是 undefined
。
例如:
console.log(a); // 输出 undefined
var a = 10;
在上面的示例中,a
变量的声明被提升至作用域顶部,因此 console.log(a)
不会报错,但输出的是 undefined
。
函数作用域和全局作用域
函数作用域和全局作用域是 JavaScript 中的两种不同的作用域类型。
函数作用域:
- 函数作用域是指在函数内部声明的变量在函数内部有效。
- 函数作用域的变量可以在函数内部自由访问,但在函数外部是不可见的。
- 在函数作用域中,变量可以有相同的名称,彼此之间互不影响。
示例:
function myFunction() {var x = 10; // x在函数作用域内console.log(x); // 输出 10
}myFunction();
console.log(x); // 报错,x在函数外部不可见
全局作用域:
- 全局作用域是指在函数外部声明的变量在整个代码中都有效。
- 全局作用域的变量可以在代码的任何位置访问,包括函数内部和外部。
- 在全局作用域中,变量可以被所有函数和代码块访问。
示例:
var y = 20; // y在全局作用域内function myFunction() {console.log(y); // 输出 20,函数可以访问全局作用域中的变量
}myFunction();
console.log(y); // 输出 20,全局作用域中的变量可以在代码的任何位置访问
需要注意的是,在函数内部如果没有使用 var
、let
或 const
关键字声明变量,则该变量会成为全局变量,即使在函数内部声明,也会污染全局命名空间,因此在函数内部尽量使用 var
、let
或 const
关键字声明变量,避免意外的全局变量定义。
变量提升
变量提升(hoisting)是 JavaScript 中的一个特性,它允许在变量声明之前访问变量。
当 JavaScript 代码执行时,变量的声明会被提前至其所在作用域的顶部,这就是变量提升
。
具体来说,变量提升分为两个阶段:声明阶段和初始化阶段。
-
声明阶段:
- 在作用域中,通过使用
var
关键字、let
关键字或function
声明的变量和函数都会在其所在作用域的顶部进行声明。 - 变量的声明被提前,但变量的初始化(赋值)并不提前。
- 在作用域中,通过使用
-
初始化阶段:
- 在代码运行时,变量会按照代码中的顺序进行初始化。如果变量在声明前被使用,其值会是
undefined
。 - 函数的声明会被整体提前,因此可以在声明之前调用函数。
- 在代码运行时,变量会按照代码中的顺序进行初始化。如果变量在声明前被使用,其值会是
示例 1(变量声明提前):
console.log(x); // 输出 undefined
var x = 10;
console.log(x); // 输出 10
示例 2(函数声明提前):
myFunction(); // 调用函数,输出 "Hello"
function myFunction() {console.log("Hello");
}
需要注意的是,虽然函数的声明会被提前,但函数表达式(通过将函数赋值给变量)不会提升。
示例 3(函数表达式不会提升):
myFunction(); // 报错,myFunction is not a function
var myFunction = function() {console.log("Hello");
};
为了避免变量提升可能引发的问题,建议在使用之前先声明变量,并养成良好的代码书写习惯。
2. 数据类型:
基本数据类型:Number、String、Boolean、null、undefined
引用数据类型:Object、Array、Function、Date
3. 函数:
函数的定义和调用
在编程中,函数是一段可重复使用的代码块,用于执行特定的任务或计算。通过函数的定义和调用,我们可以把代码分成小块,使得程序结构更清晰,易于维护和扩展。
函数的定义包括以下几个部分:
- 函数名称:给函数取一个可识别的名称,用于后续的函数调用。
- 参数列表:定义函数需要接收的参数,可以是零个或多个参数。每个参数都有一个名称和类型。
- 函数体:函数体是函数的实际执行部分,包含一系列的语句和算法,用于完成特定的任务。
- 返回值:函数可以选择性地返回一个值作为结果。返回值的类型可以是任意类型,也可以是空。
函数的调用是指使用函数名称和参数列表来执行函数体中的代码。当程序需要执行函数时,可以通过函数名称后面加上括号和参数列表的方式来调用函数。例如,假设我们有一个名为add
的函数,接受两个整数参数,可以通过调用 add(2, 3)
来计算两个整数的和。
函数的调用将会暂时中断当前的执行流程,转到函数体中执行对应的代码。一旦函数执行完成,将会返回结果(如果有的话)并继续执行函数调用的下一条语句。
函数的定义和调用是模块化编程的重要概念,可以提高代码的可读性、可维护性和复用性。在大多数编程语言中,函数都是基本的编程构造之一。
函数参数和参数传递
在 ES5 中,函数的参数和参数传递可以通过以下方式进行:
- 命名参数(Named Parameters):在函数定义时,可以为函数指定一些命名参数,这些参数可以在函数体内使用。调用函数时,可以根据参数的名称来传递参数值。例如:
function greet(name) {console.log("Hello, " + name + "!");
}// 调用函数时传递参数值
greet("Alice"); // 输出:Hello, Alice!
- 默认参数(Default Parameters):在 ES5 中,可以为函数的参数指定默认值。如果调用函数时没有为该参数传递值,那么将会使用默认值。例如:
function greet(name, message) {name = name || "Anonymous"; // 设置默认值为 "Anonymous"message = message || "Hello"; // 设置默认值为 "Hello"console.log(message + ", " + name + "!");
}greet(); // 输出:Hello, Anonymous!
greet("Alice"); // 输出:Hello, Alice!
greet("Bob", "Hi"); // 输出:Hi, Bob!
- 参数传递方式:在 ES5 中,参数的传递是通过值传递的。简单来说,即将参数值赋值给函数定义时的参数。如果传递的参数是基本类型(如字符串、数字等),那么函数内部对该参数值的改变不会影响到函数外部的值。如果传递的参数是对象,那么函数内部对该参数的修改会影响到函数外部传递的对象。
function updateName(obj) {obj.name = "Bob"; // 修改传递的对象的属性值
}var person = { name: "Alice" };
updateName(person);
console.log(person.name); // 输出:Bob
需要注意的是,在 ES5 中没有提供特定的方式来声明函数参数的类型。因此,在函数内部需要对参数类型进行检查或转换时,需要手动处理。
函数作为值和回调函数
在ES5中,函数被视为一种特殊的值,可以像其他值一样赋给变量、作为参数传递给函数或从函数中返回。这种能力使得函数可以被用作回调函数,即在某个事件发生后被调用的函数。
回调函数在异步编程中非常常用。当需要在某个事件完成后执行一些操作时,可以将回调函数作为参数传递给触发该事件的函数。例如,在处理AJAX请求时,可以将成功回调函数和失败回调函数作为参数传递给XMLHttpRequest
对象的相关方法。
下面是一个简单的示例,展示了如何使用回调函数:
function fetchData(url, onSuccess, onError) {var xhr = new XMLHttpRequest();xhr.open("GET", url, true);xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {onSuccess(xhr.responseText);} else {onError(xhr.status);}}};xhr.send();
}function onSuccess(response) {console.log("成功获取数据:", response);
}function onError(status) {console.log("获取数据失败,状态码:", status);
}fetchData("https://api.example.com/data", onSuccess, onError);
在这个示例中,fetchData
函数接受一个URL参数和两个回调函数参数onSuccess
和onError
。fetchData
函数会发送AJAX请求,并根据请求的结果调用相应的回调函数。
这个例子只是回调函数的一种应用方式,你可以根据具体的需求来决定在什么时候以及如何使用回调函数。
闭包和作用域链
闭包是指能够访问其自身作用域以及外部作用域中的变量的函数。在ES5中,闭包是一种强大且常见的设计模式,它可以用于创建私有变量、模块化代码以及封装数据。
作用域链是指在函数创建时确定的变量查找顺序。当函数中访问一个变量时,JavaScript引擎会首先搜索作用域链中的当前函数的作用域
,如果找不到,则继续搜索外部函数的作用域,直到找到该变量或者抵达全局作用域。
下面是一个闭包和作用域链的示例:
function outerFunction() {var outerVariable = "外部变量";function innerFunction() {var innerVariable = "内部变量";console.log(outerVariable + "和" + innerVariable + "可在内部函数访问");}return innerFunction;
}var inner = outerFunction();
inner(); // 输出:外部变量和内部变量可在内部函数访问
在这个示例中,innerFunction
内部函数可以访问outerVariable
外部函数的变量。当outerFunction
函数执行后,它会返回innerFunction
函数,形成一个闭包。这样,内部函数就可以在其自身作用域内以及外部函数的作用域内访问变量。
闭包的一个重要特性是它可以让变量在函数执行后继续存在,并且可以保留其值。在上述示例中,即使outerFunction
已经执行完毕,innerFunction
仍然可以通过闭包访问到outerVariable
变量。
通过理解闭包和作用域链的概念,你可以更好地设计和组织代码,创建私有变量、模块化代码以及避免全局命名空间的污染。
4. 对象和原型:
对象的创建和属性访问
在ES5中,我们可以使用两种方式来创建对象:对象字面量语法和构造函数。对象字面量语法是一种简洁的方式,用于直接创建对象。构造函数则允许我们使用自定义构造函数创建对象。
- 对象字面量语法:
var person = {name: "John",age: 30,sayHello: function() {console.log("Hello, I'm " + this.name);}
};console.log(person.name); // 输出:"John"
person.sayHello(); // 输出:"Hello, I'm John"
在对象字面量语法中,我们使用花括号 {}
来定义对象,然后在里面以键值对的形式定义属性和方法。属性的访问可以通过对象名加点号的方式(例如 person.name
)进行。
- 构造函数:
function Person(name, age) {this.name = name;this.age = age;this.sayHello = function() {console.log("Hello, I'm " + this.name);};
}var person = new Person("John", 30);
console.log(person.name); // 输出:"John"
person.sayHello(); // 输出:"Hello, I'm John"
在这个例子中,我们定义了一个名为 Person
的构造函数,通过 new
关键字调用构造函数来创建对象。在构造函数内部,使用 this
关键字来指代新创建的对象,并通过赋值语句来定义对象的属性和方法。
无论是对象字面量语法还是构造函数,我们都可以使用点号来访问对象的属性和方法。
在ES5中,我们还可以使用Object.defineProperty
方法来定义属性的特性,例如设置属性为只读或设置属性的getter和setter函数。
这些是ES5中对象的创建和属性访问的常见方式,它们可以帮助我们更方便地创建和操作对象。
构造函数和 new 操作符
构造函数是一种特殊的函数,用于创建和初始化对象。在 JavaScript 中,使用构造函数和 new
操作符可以实例化一个对象。
构造函数的命名通常以大写字母开头,以与普通函数区分开来。构造函数的目的是用于创建对象,并设置对象的初始状态。构造函数可以包含属性和方法,这些属性和方法将成为实例对象的属性和方法。
使用 new
操作符来调用构造函数,会执行以下几个步骤:
- 创建一个空对象。
- 将这个空对象的原型指向构造函数的
prototype
属性,以继承构造函数的属性和方法。 - 在新对象上下文中执行构造函数,即将构造函数的
this
绑定到新对象上。 - 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象。
下面是一个使用构造函数和 new
操作符创建对象的示例:
function Person(name, age) {this.name = name;this.age = age;this.sayHello = function() {console.log("Hello, I'm " + this.name);};
}var person = new Person("John", 30);
console.log(person.name); // 输出:"John"
person.sayHello(); // 输出:"Hello, I'm John"
在这个例子中,Person
是一个构造函数,通过 new
操作符创建了一个 person
对象。构造函数内部使用 this
关键字来引用新创建的对象,并设置对象的属性和方法。
使用构造函数和 new
操作符可以方便地创建多个相似的对象,同时实现代码的重用性和可维护性。
原型和原型链
在 JavaScript 中,每个对象都有一个特殊的属性称为原型(prototype
),它指向另一个对象。该对象被称为原型对象(prototype object
)。原型对象拥有属性和方法,这些属性和方法可以被继承到对象实例中。
原型链
原型链是一个对象与它的原型对象之间的链接,如果一个对象无法在自身上找到属性或方法,它就会沿着原型链向上查找,直到找到对应的属性或方法,或者达到原型链的顶端 - Object.prototype
(即 null
)。
var person = {name: "John",sayHello: function() {console.log("Hello, I'm " + this.name);}
};var john = Object.create(person);
john.age = 30;console.log(john.name); // 输出:"John"
john.sayHello(); // 输出:"Hello, I'm John"
在这个例子中,我们创建了一个 person
对象作为 john
对象的原型。因此,john
对象继承了 person
对象的属性和方法。当 john
对象在自身上找不到 name
属性或 sayHello
方法时,它会沿着原型链向上查找,最终从 person
对象中找到这些属性和方法。
原型
每个 JavaScript 对象都有一个原型对象。我们可以通过访问对象的 prototype
属性来获取其原型对象。原型对象是创建该对象的函数的 prototype
属性的值。例如:
function Person(name) {this.name = name;
}var john = new Person("John");console.log(Person.prototype); // 输出:{ constructor: f Person(name) }
console.log(john.prototype); // 输出:undefined
在这个例子中,构造函数 Person
有一个原型对象,它存储在 Person.prototype
中。当我们使用 new
操作符创建 Person
对象时,该对象的原型将成为 Person.prototype
。
对象的原型对象也可以通过 Object.getPrototypeOf()
方法来获取。
console.log(Object.getPrototypeOf(john) === Person.prototype); // 输出:true
原型继承
通过原型,我们可以实现对象之间的继承。一个对象可以从另一个对象继承属性和方法。
function Animal(name) {this.name = name;
}Animal.prototype.sayHello = function() {console.log("Hello, I'm " + this.name);
};function Dog(name) {Animal.call(this, name); // 调用 Animal 构造函数,继承属性
}Dog.prototype = Object.create(Animal.prototype); // 继承原型方法
Dog.prototype.constructor = Dog; // 修复原型链的 constructorvar dog = new Dog("Max");
dog.sayHello(); // 输出:"Hello, I'm Max"
在这个例子中,我们定义了 Animal
和 Dog
两个构造函数。Dog
构造函数通过调用 Animal
构造函数来继承 name
属性。然后,我们使用 Object.create()
方法将 Dog
对象的原型设置为 Animal
对象的原型,从而继承了 Animal
对象的方法。最后,我们修复了原型链的 constructor
属性,使其指向 Dog
构造函数。
通过原型继承,我们可以有效地实现对象之间的共享和代码重用,并形成灵活的继承结构。
继承实现方式
- 原型链继承
- 构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 混合继承
继承方式 | 描述 |
---|---|
原型链继承 | 通过设置对象的 prototype 属性链实现继承 |
构造函数继承 | 通过在构造函数内部调用父类构造函数并使用 call 或 apply 方法实现 |
组合继承 | 结合了原型链继承和构造函数继承的方式 |
原型式继承 | 通过克隆一个对象作为新对象的原型实现继承 |
寄生式继承 | 在原型式继承的基础上增加对新对象进行扩展的操作 |
混合继承 | 结合了多种继承方式,以达到最佳的效果 |
这些继承方式都有各自的优缺点,具体使用哪种继承方式取决于实际需求和个人偏好。
5. 数组操作:
数组的创建和访问
在JavaScript中,可以使用几种方法创建和访问数组。
创建数组:
-
使用数组字面量
[]
创建一个空数组:let arr = [];
-
使用数组字面量并提供初始值来创建一个包含元素的数组:
let arr = [1, 2, 3];
-
使用
new
关键字和Array
构造函数创建一个空数组:let arr = new Array();
-
使用
new
关键字和Array
构造函数并提供初始值来创建一个包含元素的数组:let arr = new Array(1, 2, 3);
数组访问:
可以使用索引来访问数组元素。数组的索引从0开始,可以使用方括号或点号来访问数组元素。
let arr = [1, 2, 3];
console.log(arr[0]); // 输出 1
console.log(arr[2]); // 输出 3// 使用点号访问数组元素
console.log(arr.length); // 输出数组的长度
需要注意的是,如果使用一个不存在的索引来访问数组元素,将会返回 undefined
。
let arr = [1, 2, 3];
console.log(arr[5]); // 输出 undefined
还可以使用数组的一些内置方法来访问和操作数组,例如 push
、pop
、shift
、unshift
、splice
、slice
等等。这些方法可以在数组上进行增加、删除、替换等操作。
数组的常用方法:push、pop、shift、unshift、splice、slice、join、concat、reverse、sort、map、filter、reduce 等
6. 错误处理:
try-catch-finally 语句
throw 抛出异常
7. JSON:
JSON 的字符串化和解析
JSON 是一种用于将 JavaScript 对象表示法(JavaScript Object Notation)数据格式化并表示为字符串的方式。JSON 字符串化是将 JavaScript 对象转换为 JSON 字符串的过程
,而 JSON 解析是将 JSON 字符串转换回 JavaScript 对象的过程
。
在 JavaScript 中,可以使用 JSON.stringify()
方法将一个 JavaScript 对象转换为 JSON 字符串。例如:
const obj = { name: "John", age: 25, city: "New York" };
const jsonStr = JSON.stringify(obj);
console.log(jsonStr);
// 输出: {"name":"John","age":25,"city":"New York"}
JSON 解析则是将 JSON 字符串转换回 JavaScript 对象的过程,可以使用 JSON.parse()
方法来实现。例如:
const jsonString = '{"name":"John","age":25,"city":"New York"}';
const obj = JSON.parse(jsonString);
console.log(obj);
// 输出: { name: "John", age: 25, city: "New York" }
使用 JSON 字符串化和解析可以方便地在 JavaScript 中处理和传输数据,特别是在与后端服务器进行交互时,可以将 JavaScript 对象转换为 JSON 字符串进行传输,并在接收到服务器响应时将 JSON 字符串解析为 JavaScript 对象进行处理。
JSON 对象的使用
8. 正则表达式:
正则表达式的基本语法和匹配规则
正则表达式是一种强大的文本匹配工具,它使用简洁的语法来描述字符串的模式。以下是正则表达式的基本语法和匹配规则:
-
字符匹配:
- 单个字符: 使用单个字符来匹配它自身。例如,正则表达式
a
将匹配字符串中的字符 “a”。 - 字符类: 使用方括号 [] 来定义一个字符类,可以匹配字符类中的任何一个字符。例如,正则表达式
[aeiou]
将匹配字符串中的任何一个元音字母。 - 范围字符类: 在字符类内使用连字符 - 来表示一个字符范围。例如,正则表达式
[a-z]
将匹配任何一个小写字母。 - 反转字符类: 在字符类内使用脱字符 ^ 来表示排除某些字符。例如,正则表达式
[^0-9]
将匹配任何一个非数字字符。
- 单个字符: 使用单个字符来匹配它自身。例如,正则表达式
-
重复匹配:
- 重复次数: 使用量词来指定重复次数,常用的有:
*
:匹配前面的元素零次或多次。+
:匹配前面的元素一次或多次。?
:匹配前面的元素零次或一次。{n}
:匹配前面的元素恰好 n 次。{n,}
:匹配前面的元素至少 n 次。{n,m}
:匹配前面的元素至少 n 次,至多 m 次。
- 重复次数: 使用量词来指定重复次数,常用的有:
-
特殊字符:
\d
:匹配一个数字字符。\w
:匹配一个字母、数字、下划线字符。\s
:匹配一个空白字符。\b
:匹配一个单词边界。
-
边界匹配:
^
:匹配输入字符串的开头。$
:匹配输入字符串的结尾。\b
:匹配一个单词边界。
-
分组和捕获:
(pattern)
:将 pattern 包含在一个分组中。(?:pattern)
:只是用于分组,不进行捕获。
以上仅是正则表达式的基本语法和匹配规则的介绍,正则表达式还有更多的功能和语法,包括使用特殊字符、修饰符、反向引用等。在实际使用中,可以通过的文档或教程来学习正则表达式的更高级用法。