property
)和方法(method
)
数据集或功能集
。Object Oriented Programming
,简称 OOP
,是一种编程开发思想。procedural programming
),更适合多人合作的大型软件项目。面向过程:
面向对象的特性:
在 JavaScript 中,所有数据类型都可以视为对象,当然也可以自定义对象。
自定义的对象数据类型就是面向对象中的类( Class
)的概念。
我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。
面向过程:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
var stu1 = {
name: "张三",
score: 80,
};
var stu2 = {
name: "李四",
score: 90,
};
function printScore(student) {
console.log(`姓名:${student.name},成绩:${student.score}`);
}
printScore(stu1);
printScore(stu2);
script>
html>
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,
而是 Student
这种数据类型应该被视为一个对象,这个对象拥有 name
和 score
这两个属性(Property
)。
如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给调用 printScore
,将成绩打印。
抽象数据行为模板(Class):
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 创建学生类 name score 形参
function Student(name, score) {
// this 表示 student 对象
// this.name 表示的是 student 的属性
this.name = name;
this.score = score;
// 方法
this.printScore = function () {
console.log(`姓名:${this.name},成绩:${this.score}`);
};
}
// 创建学生对象 (实例)
var stu1 = new Student("张三", 80);
var stu2 = new Student("李四", 90);
console.log(stu1);
console.log(stu2);
// 对象调用方法
stu1.printScore();
stu2.printScore();
script>
html>
Class
)和实例(Instance
)的概念是很自然的。Class
是一种抽象概念,比如我们定义的 Class
——Student
,是指学生这个概念Instance
)则是一个个具体的 Student ,比如, 张三
和李四
是两个具体的 Student
。Class
Class`` 创建
Instance`Instance
得结果简单方式
通过 new Object()
创建
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 创建对象的方式一 new Object
var dog = new Object();
dog.name = "泰迪";
dog.weight = "15";
dog.talk = function () {
console.log(`${this.name}:汪汪汪`);
};
console.log(dog);
dog.talk();
script>
html>
每次创建通过 new Object()
比较麻烦,所以可以通过它的简写形式对象字面量来创建:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 创建对象的方式二 字面量 方式创建对象
var dog2 = {
name: "哈士奇",
weight: "30",
talk: function () {
console.log(`${this.name}:汪汪汪`);
},
};
console.log(dog2);
dog2.talk();
script>
html>
对于上面的写法固然没有问题,但是假如我们要生成两个 dog
实例对象呢?
通过上面的代码我们不难看出,这样写的代码太过冗余(如果要创建多条dog
),重复性太高。
简单方式的改进:工厂函数
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
function createDog(name, weight) {
return {
name: name,
weight: weight,
talk: function () {
console.log(`${this.name}:汪汪汪`);
},
};
}
var taidi = createDog("泰迪", 15);
var hashiqi = createDog("哈士奇", 30);
console.log(taidi);
console.log(hashiqi);
taidi.talk();
hashiqi.talk();
script>
html>
这样封装确实爽多了,通过工厂模式我们解决了创建多个相似对象代码冗余的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
一种更优雅的工厂函数就是下面这样,构造函数:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 构造函数
// 定义 Person
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHi = function () {
console.log(`${this.name}:'hi'`);
};
}
// 创建对象 new 构造函数()
var zhangsan = new Person("张三", 18);
var lisi = new Person("李四", 20);
console.log(zhangsan);
zhangsan.sayHi();
console.log(lisi);
lisi.sayHi();
script>
html>
Person()
函数取代了 createPerson()
函数,但是实现效果是一样的。这是为什么呢?我们注意到,Person()
中的代码与 createPerson()
有以下几点不同之处:
this
对象return
语句Person
Person
实例,则必须使用 new
操作符。以这种方式调用构造函数会经历以下 4 个步骤:
使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型了。在每一个实例对象中的__proto__
中同时有一个constructor
属性,该属性指向创建该实例的构造函数:
console.log(zhangsan.constructor == Person); // true
console.log(lisi.constructor == Person); // true
console.log(zhangsan.constructor == lisi.constructor); // true
对象的 constructor
属性最初是用来标识对象类型的,但是,如果要检测对象的类型,还是使用 instanceof
操作符更可靠一些:
console.log("张三的原型Person? ", zhangsan instanceof Person); // 张三的原型Person? true
console.log("李四的原型Person? ", lisi instanceof Person); // 李四的原型Person? true
总结:
构造函数是根据具体的事物抽象出来的抽象模板
实例对象是根据构造函数new出来的
每一个实例对象都具有一个 constructor
属性,指向创建该实例的构造函数
constructor
是实例的属性的说法不严谨,具体后面的原型会讲到constructor
属性判断实例和构造函数之间的关系instanceof
操作符使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题:
那就是对于每一个实例对象,type
和 sayHello
都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存,如果实例对象很多,会造成极大的内存浪费。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 构造函数
// 定义 Person
function Person(name, age) {
this.name = name;
this.age = age;
this.type = "human";
this.sayHi = function () {
console.log(`${this.name}:'hi'`);
};
}
// 创建对象 new 构造函数()
var zhangsan = new Person("张三", 18);
var lisi = new Person("李四", 20);
console.log(zhangsan.constructor == lisi.constructor); // true
console.log(zhangsan.sayHi === lisi.sayHi); // false
script>
html>
对于这种问题我们可以把需要共享的函数定义到构造函数外部:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
function sayHi() {
console.log(`${this.name}:hi`);
}
function Person(name, age) {
this.name = name;
this.age = age;
this.type = "human";
this.sayHi = sayHi;
}
var p1 = new Person("张三", 18);
var p2 = new Person("李四", 20);
console.log(p1);
console.log(p2);
p1.sayHi();
p2.sayHi();
console.log(p1.sayHi === p2.sayHi); // true
script>
html>
这样确实可以了,但是如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题。
更好的解决方案:prototype
Javascript
规定,每一个构造函数都有一个 prototype
属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype
对象上。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// Person 类
function Person(name, age) {
this.name = name;
this.age = age;
}
// 在原型上添加属性和方法
Person.prototype.type = "human";
Person.prototype.sayHi = function () {
console.log(`${this.name}:hi`);
};
var p1 = new Person("张三", 18);
var p2 = new Person("李四", 20);
console.log(p1, p2);
p1.sayHi();
// 引用地址一致 避免浪费内存
console.log(p1.sayHi === p2.sayHi); // 指向同一个地址
script>
html>
这时所有实例的 type
属性和 sayHi()
方法,其实都是同一个内存地址,指向 prototype
对象,因此就提高了运行效率。
任何函数都具有一个 prototype
属性,该属性是一个对象。
function Foo() {}
console.log(Foo.prototype); // Object
Foo.prototype.sayHi = function () {
console.log("hi");
};
构造函数的 prototype
对象默认都有一个 constructor
属性,指向 prototype
对象所在函数。
console.log(Foo.prototype.constructor === Foo); // true
通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype
对象的__proto__
。
// 创建 foo 的实例对象
var foo = new Foo();
console.log(foo.__proto__ === Foo.prototype); // true
__proto__
是非标准属性。
实例对象可以直接访问原型对象成员。
总结:
prototype
属性,该属性是一个对象prototype
对象默认都有一个 constructor
属性,指向 prototype
对象所在函数prototype
对象的指针 __proto__
我们注意到,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype
。
为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// Person 类
function Person(name, age) {
this.name = name;
this.age = age;
}
// prototype 是一个 Object
Person.prototype = {
type: "human",
sayHi: function () {
console.log(`${this.name}:hi`);
},
};
var p1 = new Person("张三", 18);
var p2 = new Person("李四", 20);
console.log(p1, p2);
p1.sayHi();
// 引用地址一致 避免浪费内存
console.log(p1.sayHi === p2.sayHi); // 指向同一个地址
script>
html>
在该示例中,我们将 Person.prototype
重置到了一个新的对象。
这样做的好处就是为 Person.prototype
添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 constructor
成员。
所以,我们为了保持 constructor
的指向正确,建议的写法是:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// Person 类
function Person(name, age) {
this.name = name;
this.age = age;
}
// prototype 是一个 Object
Person.prototype = {
// 如果将 prototype 写出一个对象 需要重写 constructor
constructor: Person,
type: "human",
sayHi: function () {
console.log(`${this.name}:hi`);
},
};
var p1 = new Person("张三", 18);
var p2 = new Person("李四", 20);
console.log(p1, p2);
p1.sayHi();
// 引用地址一致 避免浪费内存
console.log(p1.sayHi === p2.sayHi); // 指向同一个地址
script>
html>
所有函数都有 prototype
属性对象。
Object.prototype
Function.prototype
Array.prototype
String.prototype
Number.prototype
Date.prototype
作业:为数组对象和字符串对象扩展原型方
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 在原型上添加方法
Array.prototype.sayHi = function () {
console.log("你好,我是数组");
};
var a = new Array();
a.sayHi();
String.prototype.sayHi = function () {
console.log("你好,我是字符串");
};
var b = new String();
b.sayHi();
script>
html>
prototype
记得修正 constructor
的指向DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 定义 Person 类
function Person(name, age) {
this.name = name;
this.age = age;
}
// 定义 Student 类
function Student(name, age) {
// 继承
Person.call(this, name, age);
}
var sdt = new Student("张三", 18);
console.log(sdt);
script>
html>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 定义 Person 类
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function () {
console.log(`${this.name}:'Hi'`);
};
// 定义 Student 类
function Student(name, age) {
// 继承属性
Person.call(this, name, age);
}
// copy 继承 继承原型的属性
for (var key in Person.prototype) {
console.log(key); // sayHi
Student.prototype[key] = Person.prototype[key];
}
var sdt = new Student("张三", 18);
console.log(sdt);
sdt.sayHi();
script>
html>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 定义 Person 类
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function () {
console.log(`${this.name}:'Hi'`);
};
// 定义 Student 类
function Student(name, age) {
// 继承属性
Person.call(this, name, age);
}
// 将 Person 对象赋值给 Student 原型
Student.prototype = Person.prototype;
// Student.prototype = new Person();
var stu = new Student("张三", 20);
console.log(stu);
script>
html>
函数声明
函数表达式
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 函数的定义方式
// 声明式
foo(); // 不会报错 存在变量提升
function foo() {}
// 表达式
// bar(); // 报错 表达式没有变量提升
var bar = function () {};
script>
html>
函数声明与函数表达式的区别
普通函数
构造函数
对象方法
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 普通函数
function foo() {}
foo();
// 构造函数
function F() {}
var a = new F();
// 对象中的函数
var c = {
sayHi: function () {},
};
c.sayHi();
script>
html>
this
指向的不同场景函数的调用方式决定了 this
指向的不同:
调用方式 | 非严格模式 | 备注 |
---|---|---|
普通函数调用 | window | 严格模式下是 undefined |
构造函数调用 | 实例对象 | 原型方法中 this 也是实例对象 |
对象方法调用 | 该方法所属对象 | 紧挨着的对象 |
事件绑定方法 | 绑定事件对象 | |
定时器函数 | window |
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>
<button id="btn">按钮button>
body>
<script>
// 普通函数
function foo() {
console.log(this); // Window
}
foo();
// 构造函数
function Person() {
console.log(this); // Person {}
}
var p = new Person();
// 对象方法调用
var obj = {
name: "张三",
sayHi: function () {
console.log(this); // 张三 等于所属对象
},
girlFriend: {
name: "小红",
talk: function () {
console.log(this); // 小红
},
},
};
obj.sayHi();
obj.girlFriend.talk();
// 事件绑定方法
document.querySelector("#btn").onclick = function () {
console.log(this); //
};
// 定时器函数
setTimeout(function () {
console.log(this); // Window
}, 2000);
script>
html>
call
、apply
、bind
那了解了函数 this
指向的不同场景之后
我们知道有些情况下我们为了使用某种特定环境的 this
引用,这时候时候我们就需要采用一些特殊手段来处理了,例如我们经常在定时器外部备份 this
引用,然后在定时器函数内部使用外部 this
的引用。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
var obj = {
name: "张三",
sayHi: function () {
// 保存 this
var _this = this;
console.log(this); // Object
setTimeout(function () {
console.log(_this.name); // 张三
}, 200);
},
};
obj.sayHi();
script>
html>
然而实际上对于这种做法我们的 JavaScript
为我们专门提供了一些函数方法用来帮我们更优雅的处理函数内部 this
指向问题。这就是接下来我们要学习的 call
、apply
、bind
三个函数方法。
call
call()
方法调用一个函数, 其具有一个指定的 this
值和分别地提供的参数(参数的列表)。
注意:该方法的作用和 apply()
方法类似,只有一个区别,就是 call()
方法接受的是若干个参数的列表,而 apply()
方法接受的是一个包含多个参数的数组。
语法:fun.call(thisArg,arg1, arg2)
参数:
thisArg
fun
函数运行时指定的 this
值null
或者 undefined
则内部 this
指向 `window``arg1, arg2, ...
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
var obj = {
name: "admin",
};
function foo(a, b) {
// console.log(this); // {name: 'admin'}
console.log(this.name); // admin
console.log(a + b); // 30
}
foo.call(obj, 10, 20);
script>
html>
apply
apply()
方法调用一个函数, 其具有一个指定的 this
值,以及作为一个数组(或类似数组的对象)提供的参数。
注意:该方法的作用和 call()
方法类似,只有一个区别,就是 call()
方法接受的是若干个参数的列表,而 apply()
方法接受的是一个包含多个参数的数组。
语法:fun.apply(thisArg, [argsArray])
参数:
thisArg
argsArray
apply()
与 call()
非常相似,不同之处在于提供参数的方式。
apply()
使用参数数组而不是一组参数列表。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
var obj = {
name: "admin",
};
function foo(a, b) {
// console.log(this); // {name: 'admin'}
console.log(this.name); // admin
console.log(a + b);
}
foo.apply(obj, [10, 20]);
// call 和 apply 很相似 只是call传递参数是列表 而apply是数组
script>
html>
bind
bind()
函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体。
当目标函数被调用时 this 值绑定到 bind()
的第一个参数,该参数不能被重写。绑定函数被调用时,bind()
也接受预设的参数提供给原函数。
一个绑定函数也能使用new
操作符创建对象:这种行为就像把原函数当成构造器。提供的 this
值被忽略,同时调用时的参数被提供给模拟函数。
语法:fun.bind(thisArg, arg1, arg2 )
参数:
thisArg
this
指向。当使用new
操作符调用绑定函数时,该参数无效。arg1, arg2, ...
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
var obj = {
name: "admin",
};
function foo(a, b, c, d) {
// console.log(this); // {name: 'admin'}
console.log(this.name); // admin
console.log(a + b); // 30
console.log(c + d); // 60
}
// 最后一个括号表示调用
// 会将两个括号里面的参数进行合并 然后全部传递
foo.bind(obj, 10, 20)(20, 40);
script>
html>
call
和 apply
特性一样
this
的指向call
调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可apply
调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递null
或者 undefined
则内部 this
指向 window
bind
this
的指向,然后生成一个改变了 this
指向的新的函数call
、apply
最大的区别是:bind
不会调用bind
支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递
bind
的同时,以参数列表的形式进行传递bind
的时候传递的参数为准呢还是以调用的时候传递的参数为准bind
的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部arguments
length
name
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
function a() {
// 打印出来的是一个伪数组 并不是真正的数组
console.log(arguments); // Arguments(5) [10, 20, 30, 40, 50, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments instanceof Array); // false
}
a(10, 20, 30, 40, 50);
console.log(a.length); // 5
console.log(a.name);
var foo = "Foo";
var obj = {
[foo]: function () {
console.log("123456");
},
};
obj[foo](); // 123456
console.log(obj[foo].name); // Foo
script>
html>
函数可以作为参数
函数可以作为返回值
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 高阶函数
// 第一种 参数是一个函数 map filter reduce forEach
var a = [1, 2, 3];
var b = a.map(function (item, index, arr) {
return item * 2;
});
console.log(b);
var c = a.filter(function (item, index, arr) {
return item == 3;
});
console.log(c);
var sum = a.reduce(function (pre, item, index, arr) {
return pre + item;
}, 0);
console.log(sum); // 6
// 第二种 返回值是一个函数
function foo() {
return function () {
console.log(10);
};
}
// var f = foo();
// f();
foo()();
script>
html>
一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包
(closure
)
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在JavaScript
中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
function foo() {
var counter = 10;
return {
printCounter: function () {
console.log(counter); // 10
},
};
}
// 调用函数 返回一个对象
// var obj = foo();
// 对象调方法
// obj.printCounter();
// 链式调用
foo().printCounter();
script>
html>
全局作用域
函数作用域
没有块级作用域
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
// 代码块
{
var a = 10; // 全局作用域
}
console.log(a); // 10
// 函数作用域
// function foo() {
// console.log(a); // 10
// }
// foo();
// 立即调用
(function foo() {
console.log(a); // 10
})();
script>
html>
内层作用域可以访问外层作用域,反之不行
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>body>
<script>
var a = 10;
function fn() {
var b = 20;
function fn1() {
var c = 30;
console.log(a + b + c); // 60
}
function fn2() {
var d = 40;
console.log(c + d); // 报错
}
fn1();
fn2();
}
fn();
script>
html>