文章目录
序章
一、 数据类型梳理
JavaScript中的变量可能包含两种不同数据类型的值:基本数据类型和引用数据类型。
1. 基本数据类型
JavaScript中一共有5种基本数据类型:String、Number、 Boolean、Undefined、Null。
基本数据类型的值是无法修改的,是不可变的。
基本数据类型的比较是值的比较,也就是只要两个变量的值相等,我们就认为这两个变量相等。
- 引用数据类型(Object)
引用类型的值是保存在内存中的对象。
当一个变量是一个对象时,实际上变量中保存的并不是对象本身,而是对象的引用。
当从一个变量向另一个变量复制引用类型的值时,会将对象的引用复制到变量中,并不是创建一个新的对象。这时,两个变量指向的是同一个对象。因此,改变其中一个变量会影响另一个。
二、堆与栈的梳理
JavaScript在运行时数据是保存到栈内存和堆内存当中的。
简单来说栈内存用来保存变量和基本类型,堆内存是用来保存对象。
我们在声明一个变量时,实际上就是在栈内存中创建了一个空间用来保存变量。
如果是基本类型则在栈内存中直接保存,如果是引用类型则会在堆内存中保存,变量中保存的实际上对象在堆内存中的地址。
当我们写了下边这几句代码的时候,栈内存和堆内存的结构如下:
var a = 123;
var b = true;
var c = "hello";
var d = {name: 'sunwukong', age: 18};
栈的特点:先进后出,后进先出
javaScript 基础语法(对象基础)
一、对象
1. 对象的分类
1.1 内建对象
由ES标准中定义的对象,在任何的ES的实现中都可以使用
比如:Math、String、Number、Boolean、Function、Object…
1.2 宿主对象
由js的运行环境提供的对象,主要指由浏览器提供的对象
例如:BOM、DOM
1.3 自定义对象
由开发人员自己创建的对象
2. 创建对象
2.1 创建对象的方式
创建对象有两种方式:
第一种方式:
var person = new Object();
person.name = "张三";
person.age = 18;
console.log(person);
第二种方式:
var person = {
name:"张三",
age:18
}
console.log(person);
2.2 访问属性
访问属性的两种方式:
第一种方式:使用.来访问
对象.属性名
第二种方式:使用[]来访问
对象[‘属性名’]
2.3 删除属性
删除对象的属性可以使用delete关键字 如下:
delete 对象.属性名
案例演示:
var person = new Object();
person.name = "张三";
person.age = 18;
console.log(person);
delete person.name
console.log(person);
2.4 遍历对象
枚举遍历对象中的属性,可以使用for … in语句循环,对象中有几个属性,循环体就会执行几次。
语法:
for(var 变量名 in 对象名)
{
console.log(变量名);
}
案例演示:
var person = {
name: "zhangsan",
age: 18
}
for (var personKey in person) {
var personVal = person[personKey];
console.log(personKey + ":" + personVal);
}
二、函数
1. 什么是函数?
函数是由一连串的子程序(语句的集合)所组成的,可以被外部程序调用,向函数传递参数之后,函数可以返回一定的值。
通常情况下,JavaScript代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行了声明,其中的代码并不会执行,只有在调用函数时才会执行函数体内部的代码。
这里要注意的是JavaScript中的函数也是一个对象,使用typeof检查一个函数对象时,会返回function。
2. 函数创建
(1)使用 函数对象 来创建一个函数(使用较少,几乎不用)
语法:
var 函数名 = new function(
内容
);
代码示例:
var fun = new function(
console.log("啊我被执行了");
);
(2) 使用 函数声明 来创建一个函数(常用)
语法:
function 函数名([形参1,形参2,…,形参n]){
内容
}
代码示例:
function fun(){
console.log("啊我被执行了");
}
(3)使用 函数表达式 来创建一个函数(常用)
语法:
var 函数名 = function([形参1,形参2,…,形参n]){
内容
}
代码示例:
var fun = function(){
console.log("啊我被执行了");
}
3. 函数调用
3.1 无参形式调用
//这里通过函数声明的方式创建函数
var fun = function(){
console.log("啊我被执行了~");
}
//这里开始调用函数
fun();
3.2 有参形式调用
//这里通过函数声明的方式创建函数
var fun = function(num1,num2){
console.log("num1+num2" + num1 + num2);
}
//这里开始调用函数
fun(11,12);
4. 函数参数
-
js中的所有的参数传递都是按值传递的,也就是说把函数外部的值赋值给函数内部的参数,就和把值从一个变量赋值给另一个变量是一样的,在调用函数时,可以在()中指定实参(实际参数),实参将会赋值给函数中对应的形参
-
调用函数时,解析器不会检查实参的类型,所以要注意,是否有可能会接收到非法的参数,如果有可能,则需要对参数进行类型的检查,函数的实参可以是任意的数据类型
-
调用函数时,解析器也不会检查实参的数量,多余实参不会被赋值,如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined
5. 函数返回值
可以使用 return 来设置函数的返回值,return后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果。
注:在函数中return后的语句都不会执行,如果return语句后不跟任何值就相当于返回一个undefined,如果函数中不写return,则也会返回undefined,return后可以跟任意类型的值
语法:
var fun = function(num1,num2){
return num1 + num2;
}fun(11,12);
6. 函数嵌套
嵌套函数:在函数中声明的函数就是嵌套函数,嵌套函数只能在当前函数中可以访问,在当前函数外无法访问。
代码示例:
function fu() {
function zi() {
console.log("我是fu()函数中嵌套的子级");
}
zi();
}
fu();
7. 匿名函数
匿名函数:没有名字的函数就是匿名函数,它可以让一个变量来接收,也就是用 “函数表达式” 方式创建和接收。
代码示例:
var fun = function(){
console.log("我是匿名函数");
}
fun();
8. 立即执行函数
立即执行函数:函数定义完,立即被调用,这种函数叫做立即执行函数,立即执行函数往往只会执行一次。
代码示例:
(function(){
console.log("我是一个匿名函数");
})
9. 对象当中的函数
对象的属性值可以是任何的数据类型,也可以是个函数。
如果一个函数作为一个对象的属性保存,那么我们称这个函数是这个对象的方法,调用这个函数就说调用对象的方法(method)。
代码示例:
var person = {
name:"张三",
age:18,
func: function(){
console.log("hello,我是" + name);
}
}
console.log(person.func());
10. this对象
解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称为函数执行的上下文对象,根据函数的调用方式的不同,this会指向不同的对象
-
以函数的形式调用时,this永远都是window
-
以方法的形式调用时,this就是调用方法的那个对象
代码示例:
//创建一个全局变量
var name = "我是一个全局变量";
//创建一个函数
function fun(){
console.log(this.name);
}
//创建一个对象
var obj = {
name:"张三",
sayName:fun
};
//我们希望调用obj.sayName()时可以输出obj的名字而不是全局变量name的名字
obj.sayName();
三、 对象进阶
1. 使用工厂模式创建对象
我们之前已经学习了如何创建一个对象,那我们要是想要创建多个对象又该怎么办?聪明的人可能会说,直接在写几个对象不就好了吗?比如下边的代码:
var person1 = {
name: "孙悟空",
age: 18,
sayName: function () {
console.log(this.name);
}
};
var person2 = {
name: "猪八戒",
age: 19,
sayName: function () {
console.log(this.name);
}
};
var person3 = {
name: "沙和尚",
age: 20,
sayName: function () {
console.log(this.name);
}
};
console.log(person1);
console.log(person2);
console.log(person3);
上述代码确实可以创建多个对象,但是,这样的解决方案真的好吗?对于少量对象可能使用,我们假设说,要用循环创建1000个对象,那你这种办法似乎就没用了,我们可以这么做,如下代码:
// 使用工厂模式创建对象
function createPerson() {
// 创建新的对象
var obj = new Object();
// 设置对象属性
obj.name = "孙悟空";
obj.age = 18;
// 设置对象方法
obj.sayName = function () {
console.log(this.name);
};
//返回新的对象
return obj;
}
var person1 = createPerson();
var person2 = createPerson();
var person3 = createPerson();
console.log(person1);
console.log(person2);
console.log(person3);
上述代码看起来更加简洁,但是你会发现每一个人都是孙悟空,我们要是想要给每一个人不同的属性值,请参考:
// 使用工厂模式创建对象
function createPerson(name, age) {
// 创建新的对象
var obj = new Object();
// 设置对象属性
obj.name = name;
obj.age = age;
// 设置对象方法
obj.sayName = function () {
console.log(this.name);
};
//返回新的对象
return obj;
}
var person1 = createPerson("孙悟空", 18);
var person2 = createPerson("猪八戒", 19);
var person3 = createPerson("沙和尚", 20);
console.log(person1);
console.log(person2);
console.log(person3);
现在再看上述代码,发现好像已经完美的解决了创建多个对象的难题,那我们是不是可以用循环批量创建1000个对象了呢?那我们就来试试:
// 使用工厂模式创建对象
function createPerson(name, age) {
// 创建新的对象
var obj = new Object();
// 设置对象属性
obj.name = name;
obj.age = age;
// 设置对象方法
obj.sayName = function () {
console.log(this.name);
};
//返回新的对象
return obj;
}
for (var i = 1; i <= 1000; i++) {
var person = createPerson("person" + i, 18);
console.log(person);
}
2. 使用构造函数创建对象
我们学会了使用工厂模式创建对象,但是,你会发现我们所创建的对象类型都是Object,具体代码如下:
// 使用工厂模式创建对象
function createPerson(name, age) {
// 创建新的对象
var obj = new Object();
// 设置对象属性
obj.name = name;
obj.age = age;
// 设置对象方法
obj.sayName = function () {
console.log(this.name);
};
//返回新的对象
return obj;
}
for (var i = 1; i <= 1000; i++) {
var person = createPerson("person" + i, 18);
console.log(typeof person);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0C15HH9n-1681875673780)(JavaScript图片资源\工厂模式对象示例图.png)]
那这有问题吗?看起来有,看起来好像又没有,每创建一个都是对象,但是在实际生活中,人应该是一个确定的类别,属于人类,对象是一个笼统的称呼,万物皆对象,它并不能确切的指明当前对象是人类,那我们要是既想实现创建对象的功能,同时又能明确所创建出来的对象是人类,那么似乎问题就得到了解决,这就用到了构造函数,每一个构造函数你都可以理解为一个类别,用构造函数所创建的对象我们也成为类的实例,那我们来看看是如何做的:
// 使用构造函数来创建对象
function Person(name, age) {
// 设置对象的属性
this.name = name;
this.age = age;
// 设置对象的方法
this.sayName = function () {
console.log(this.name);
};
}
var person1 = new Person("孙悟空", 18);
var person2 = new Person("猪八戒", 19);
var person3 = new Person("沙和尚", 20);
console.log(person1);
console.log(person2);
console.log(person3);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hMCDNgen-1681875673781)(JavaScript图片资源\构造函数对象示例图.png)]
2.1 什么是构造函数?
构造函数:构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写,构造函数和普通函数的还有一个区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。
2.2 构造函数创建对象的执行过程
- 调用构造函数,它会立刻创建一个新的对象
- 将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
- 逐行执行函数中的代码
- 将新建的对象作为返回值返回
你会发现构造函数有点类似工厂方法,但是它创建对象和返回对象都给我们隐藏了,使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。我们将通过一个构造函数创建的对象,称为是该类的实例。
现在,this又出现了一种新的情况,为了不让大家混淆,我再来梳理一下:
- 当以函数的形式调用时,this是window
- 当以方法的形式调用时,谁调用方法this就是谁
- 当以构造函数的形式调用时,this就是新创建的那个对象
我们可以使用 instanceof 运算符检查一个对象是否是一个类的实例,它返回true或false
语法:
对象 instanceof 构造函数
代码示例:
console.log(person1 instanceof Person); // 返回true
3. 原型
我们学习了使用构造函数的方式进行创建对象,但是,它还是存在一个问题,那就是,你会发现,每一个对象的属性不一样这是一定的,但是它的方法似乎好像是一样的,如果我创建1000个对象,那岂不是内存中就有1000个相同的方法,那要是有10000个,那对内存的浪费可不是一点半点的,我们有没有什么好的办法解决,没错,我们可以把函数抽取出来,作为全局函数,在构造函数中直接引用就可以了,上代码:
// 使用构造函数来创建对象
function Person(name, age) {
// 设置对象的属性
this.name = name;
this.age = age;
// 设置对象的方法
this.sayName = sayName
}
// 抽取方法为全局函数
function sayName() {
console.log(this.name);
}
var person1 = new Person("孙悟空", 18);
var person2 = new Person("猪八戒", 19);
var person3 = new Person("沙和尚", 20);
person1.sayName();
person2.sayName();
person3.sayName();
但是,在全局作用域中定义函数却不是一个好的办法,为什么呢?因为,如果要是涉及到多人协作开发一个项目,别人也有可能叫sayName这个方法,这样在工程合并的时候就会导致一系列的问题,污染全局作用域,那该怎么办呢?有没有一种方法,我只在Person这个类的全局对象中添加一个函数,然后在类中引用?答案肯定是有的,这就需要原型对象了,我们先看看怎么做的,然后在详细讲解原型对象。
// 使用构造函数来创建对象
function Person(name, age) {
// 设置对象的属性
this.name = name;
this.age = age;
}
// 在Person类的原型对象中添加方法
Person.prototype.sayName = function() {
console.log(this.name);
};
var person1 = new Person("孙悟空", 18);
var person2 = new Person("猪八戒", 19);
var person3 = new Person("沙和尚", 20);
person1.sayName();
person2.sayName();
person3.sayName();
3.1 原型到底是什么?
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象,即显式原型,原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
如果函数作为普通函数调用prototype没有任何作用,当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过proto(隐式原型)来访问该属性。当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。
4. 原型链
4.1 什么是原型链?
访问一个对象的属性时,先在自身属性中查找,找到返回, 如果没有,再沿着__proto__这条链向上查找,找到返回,如果最终没找到,返回undefined,这就是原型链,又称隐式原型链,它的作用就是查找对象的属性(方法)。
对应名称
prototype : 原型
_proto_ : 原型链(原型的连接点)
从属关系
prototype —> 函数的一个属性 : 对象{}
_proto_ —> 对象的一个属性:对象{}
对象的__proto__保存着该对象的构造函数的prototype
4.2 原型链示例图
我们使用一张图来梳理一下上面原型案例的代码:
5. toString()方法
toString()函数用于将当前对象以字符串的形式返回。该方法属于Object对象,由于所有的对象都"继承"了Object的对象实例,因此几乎所有的实例对象都可以使用该方法,所有主流浏览器均支持该函数。
// 使用构造函数来创建对象
function Person(name, age) {
// 设置对象的属性
this.name = name;
this.age = age;
}
//创建对象的一个实例对象
var p = new Person("张三", 20);
console.log(p.toString());
数据类型 | 描述 |
---|---|
String | 返回String对象的值 |
Number | 返回Number的字符串表示 |
Boolean | 如果bool值是true,则返回true,否则返回false |
Object(默认) | 返回[object ObjectName],其中ObjectName是对象类型的名称 |
Array | 将 Array 的每个元素转换为字符串,并将它们依次连接起来,两个元素之间用英文逗号作为分隔符进行拼接 |
Date | 返回日期的文本表示 |
Error | 返回一个包含相关错误信息的字符串 |
Function | 返回如下格式的字符串,其中 functionname 是一个函数的名称 此函数的 toString 方法被调用: function functionname() { [native code] } |
代码示例:
注:这里我们只是演示toString()方法
// 字符串
var str = "Hello";
console.log(str.toString());
// 数字
var num = 15.26540;
console.log(num.toString());
// 布尔
var bool = true;
console.log(bool.toString());
// Object
var obj = {name: "张三", age: 18};
console.log(obj.toString());
// 数组
var array = ["CodePlayer", true, 12, -5];
console.log(array.toString());
// 日期
var date = new Date(2013, 7, 18, 23, 11, 59, 230);
console.log(date.toString());
// 错误
var error = new Error("自定义错误信息");
console.log(error.toString());
// 函数
console.log(Function.toString());
6. hasOwnProperty()方法
上面我们学过,如何遍历一个对象所有的属性和值,那我们要是判断当前对象是否包含指定的属性或方法可以使用 in 运算符来检查,如下代码演示:
// 创造一个构造函数
function MyClass() {
}
// 向MyClass的原型中添加一个name属性
MyClass.prototype.name = "我是原型中的名字";
// 创建一个MyClass的实例
var mc = new MyClass();
mc.age = 18;
// 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log("age" in mc);
console.log("name" in mc);
有些人可能会有疑问,我的这个MyClass类对象中没有hasOwnProperty这个方法啊,它是哪来的?对了,就是原型中的,在执行方法的时候它会通过原型链进行查找,这个方法是Object的特有方法,如下代码演示:
// 创造一个构造函数
function MyClass() {
}
// 向MyClass的原型中添加一个name属性
MyClass.prototype.name = "我是原型中的名字";
// 创建一个MyClass的实例
var mc = new MyClass();
mc.age = 18;
// 检查当前对象
console.log(mc.hasOwnProperty("hasOwnProperty"));
// 检查当前对象的原型对象
console.log(mc.__proto__.hasOwnProperty("hasOwnProperty"));
// 检查当前对象的原型对象的原型对象
console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty"));
7. 对象继承
7.1 什么是对象继承?它的作用是什么?它是如何实现的?
面向对象的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是在JavaScript中没有类的概念,前边我们说所的类只是我们自己这么叫,大家要清楚。因此它的对象也与基于类的对象有所不同。实际上,JavaScript语言是通过一种叫做原型(prototype)的方式来实现面向对象编程的。
那实现继承有一个最大的好处就是子对象可以使用父对象的属性和方法,从而简化了一些代码。
7.2 JavaScript的六种继承方式
- 原型链继承
- 借用构造函数继承
- 组合继承(重点)
- 原型式继承
- 寄生式继承
- 寄生组合式继承
7.2.1 原型链继承
核心思想:子类型的原型为父类型的一个实例对象
基本写法:
- 定义父类型构造函数
- 给父类型的原型添加方法
- 定义子类型的构造函数
- 创建父类型的对象并且赋值给之类型的原型
- 将子类型的原型构造属性设置为子类型
- 给子类型的原型添加方法
- 创建子类型的对象:可以调用父类型的方法
代码示例:
// 1.定义父类型构造函数
function SupperType() {
this.supProp = 'Supper property';
}
// 2.给父类型的原型添加方法
SupperType.prototype.showSupperProp = function () {
console.log(this.supProp);
};
// 3.定义子类型的构造函数
function SubType() {
this.subProp = 'Sub property';
}
// 4.创建父类型的对象赋值给子类型的原型
SubType.prototype = new SupperType();
// 5.将子类型原型的构造属性设置为子类型
SubType.prototype.constructor = SubType;
// 6.给子类型原型添加方法
SubType.prototype.showSubProp = function () {
console.log(this.subProp)
};
// 7.创建子类型的对象: 可以调用父类型的方法
var subType = new SubType();
subType.showSupperProp();
subType.showSubProp();
它的缺点:
- 原型链继承多个实例的引用类型属性指向相同,一个实例修改了原型属性,另一个实例的原型属性也会被修改
- 不能传递参数
- 继承单一
7.2 借用构造函数继承
核心思想:使用call()与apply()将父类构造函数引入子类函数,使用父类的构造函数来增强子类实例,等同于父类的实例复制给子类
基本写法:
- 定义父类型构造函数
- 定义子类型构造函数
- 给子类型的原型添加方法
- 创建子类型的对象,调用
代码示例:
借用构造函数继承的重点就在于SuperType**.call(this, name)**,调用了SuperType构造函数,这样,SubType的每个实例都会将SuperType中的属性复制一份。
// 1.定义父类型构造函数
function SuperType(name) {
this.name = name;
this.showSupperName = function () {
console.log(this.name);
};
}
// 2.定义子类型的构造函数
function SubType(name, age) {
// 在子类型中调用call方法继承自SuperType
SuperType.call(this, name);
this.age = age;
}
// 3.给子类型的原型添加方法
SubType.prototype.showSubName = function () {
console.log(this.name);
};
// 4.创建子类型的对象然后调用
var subType = new SubType("孙悟空", 20);
subType.showSupperName();
subType.showSubName();
console.log(subType.name);
console.log(subType.age);
它的缺点:
- 只能继承父类的实例属性和方法,不能继承原型属性和方法
- 无法实现构造函数的重复使用,每个子类都拥有父类实例函数的副本,影响性能、代码臃肿
7.3 组合继承
核心思想:原型链+借用构造函数的组合继承
基本写法:
- 利用原型链实现对父类型对象方法的继承
- 利用super()借用弗雷西构建函数初始化相同属性
代码示例:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name;
};
function Student(name, age, price) {
Person.call(this, name, age); // 为了得到父类型的实例属性和方法
this.price = price; // 添加子类型私有的属性
}
Student.prototype = new Person(); // 为了得到父类型的原型属性和方法
Student.prototype.constructor = Student; // 修正constructor属性指向
Student.prototype.setPrice = function (price) { // 添加子类型私有的方法
this.price = price;
};
var s = new Student("孙悟空", 24, 15000);
console.log(s.name, s.age, s.price);
s.setName("猪八戒");
s.setPrice(16000);
console.log(s.name, s.age, s.price);
它的缺点:
父类中的实例属性和方法既存在于子类的实例中,也存在于子类的原型中,但仅是内存占用,因此,在使用子类创建实例对象的时候,其原型当中会存在两份相同的属性和方法
注:这个方法是JavaScript当中最常用的继承模式
8. 垃圾回收机制
8.1 什么是垃圾回收?
**垃圾回收(GC):**就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢,所以我们需要一个垃圾回收的机制,来处理程序运行过程中产生垃圾。
当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理。
在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作,我们需要做的只是要将不再使用的对象设置null即可。
8.2 代码示例:
// 使用构造函数来创建对象
function Person(name, age) {
// 设置对象的属性
this.name = name;
this.age = age;
}
var person1 = new Person("孙悟空", 18);
var person2 = new Person("猪八戒", 19);
var person3 = new Person("沙和尚", 20);
person1 = null;
person2 = null;
person3 = null;
9. 作用域
9.1 什么是作用域?
作用域指一个变量的作用的范围,在JS中一共有两种作用域:
- 全局作用域
- 函数作用域
9.1.1 全局作用域
- 直接编写在script标签里的JavaScript代码,就在全局作用域里面
- 全局作用域在页面打开时创建,在页面关闭时销毁
- 在全局作用域当中有一个全局对象window,它代表的是一个浏览器窗口,它由浏览器创建,我们可以直接使用
- 在全局作用域当中
- 创建的变量都会作为window对象的属性保存
- 创建的函数都会作为window对象的方法保存
- 全局作用域中的变量都是全局变量,在页面的任意部位都访问的到
9.1.2 函数作用域
- 调用函数时创建函数作用域,函数执行完毕后,函数作用域便会被销毁
- 同上,每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的
- 在函数作用域中可以访问到全局作用域的变量,在全局作用域中无法访问到函数作用域的变量
- 在函数中要访问全局变量可以使用window对象来操作
- 作用域链:在函数作用域操作一个变量时,它会先在自身作用域当中寻找,如果有那就直接使用,如果没有,则向上一级作用域查询,直到找到该变量,如果在全局作用域当中仍然没有找到该变量,那么会触发报错信息:ReferenceError(读取不存在变量)
9.2 作用域链
9.2.1 什么是作用域链?
多个上下级关系的作用域形成的链,它的方向上由上自下(由内到外)的,查找变量时就是沿着作用域链来进行查找的
9.2.2 作用域链的查找规则
查找一个变量的查找规则:
- 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,如果没找到该属性则进入 2
- 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,如果没找到该属性则进入 3
- 再次执行 2 的操作,直到全局作用域,如果还找不到该属性,就会出发异常:ReferenceError(读取不存在变量)
9.3 声明提前
9.3.1 声明提前的分类
声明提前分为两种类型:
- 变量的声明提前
- 函数的声明提前
9.3.2 变量的声明提前
变量的声明提前:使用var关键字声明的变量,会在所有代码执行前被声明(但不会被赋值),但是如果声明变量时使用let关键字,则改 变量不会被提前声明
9.3.3 函数的声明提前
函数的声明提前:使用函数声明形式创建的函数function函数名(){} ,它会在所有代码执行前被创建,所以我们可以在函数声明前来调用 函数。使用函数表达式创建的函数不会被提前声明,所以不能再声明前调用