一.闭包
- 闭包是什么,闭包怎么产生以及闭包的作用下面一一解释
1)使用闭包的经典,下面代码是实现三个按钮,当点击哪一个按钮的时候,提示是对应的按钮,但是下面的代码,无论点击按钮哪一个按钮输出的结果都是第4个,是因为当点击按钮的时候,循环已经执行完,i==3,然后结果都是第4个。
var btns = document.getElementsByTagName('button')
//遍历加监听
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
btn.onclick = function () {
alert('第'+(i+1)+'个')
}
}
当使用闭包之后就可以解决该问题(当然还有别的方法解决,比如用let声明i或将对应的i保存下来)
//利用闭包
for (var i = 0,length=btns.length; i < length; i++) {
(function (j) {
var btn = btns[j]
btn.onclick = function () {
alert('第'+(j+1)+'个')
}
})(i)
}
-理解闭包(闭包的产生)
当嵌套在内部的函数引用外部函数的变量时,就产生了闭包。
function fn1 () {
var a = 2
var b = 'abc'
function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
console.log(a)
}
// fn2()
}
fn1()
- 闭包的作用
1)函数执行完之后,内部的变量不会销毁,而是一直存在
2)函数外部能够操作函数内部的变量
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
- 常见的闭包
1)将函数作为另一个函数的返回值
2)将函数作为实参传递给另一个函数调用
// 1. 将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
// 2. 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)// 产生闭包,还有msg,没有time
}, time)
}
showDelay('atguigu', 2000)
- 闭包的生命周期
1)产生: 内部函数定义执行完时就产生了(不用调用)
2)死亡: 内部函数成为垃圾对象时
function fn1() {
//此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
var a = 2
function fn2 () {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
-闭包的缺点和解决
缺点:因为将函数内部的变量保存下来没有销毁所以占用内存和容易内存泄露。
解决:当变量没有用处的时候将变量进行销毁,释放内存。
上一步代码中有说道如何销毁闭包中的变量
- 解释内存溢出和内存泄露
1)内存溢出:类似生活中的水杯,水杯的空间只有那么大,当你倒入大于水杯的容量的水,就会流出来,同理,内存也是,当你运行程序所占的内存大于你所剩下的内存时,就会内存溢出,程序就运行不了。(比如,循环创建长度为10000的数组10万次或者更多你的电脑就会发生溢出)
2)内存泄露: 本来想在函数内部创建一个局部变量,函数执行完之后就会自动销毁,但是直接a=2,变成了意外的全局变量,造成一直占用着内存,这种类似的行为多了之后,就会造成内存慢慢的泄露,可运行的内存就会变少就有产生内存溢出。
常见的内存泄露:
1.意外的全局变量
2.没有及时清理的计时器或回调函数
3.闭包
// 内存泄露
// 意外的全局变量
function fn() {
a = new Array(10)
console.log(a)
}
fn()
// 没有及时清理的计时器或回调函数
var intervalId = setInterval(function () { //启动循环定时器后不清理
console.log('----')
}, 1000)
// clearInterval(intervalId)
// 闭包
function fn1() {
var a = 4
function fn2() {
console.log(++a)
}
return fn2
}
var f = fn1()
f()
// f = null
- 测试题
题一:
//代码片段一
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); // the window
object.getNameFunc()执行完之后返回函数function(){return this.name;};然后再执行内部的函数,这个时候要区别后面再执行这个函数是谁在执行,this就是谁,很明显就是window
题二:
//代码片段二
var name2 = "The Window";
var object2 = {
name2 : "My Object",
getNameFunc : function(){
var that = this;// 将object2对象地址赋值给that
return function(){
return that.name2;
};
}
};
alert(object2.getNameFunc()()); // my object
题三:
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n)
}
}
}
var a = fun(0)//undefined
a.fun(1) // 0
a.fun(2) // 0
a.fun(3) // 0
var b = fun(0).fun(1).fun(2).fun(3)
// undefined
// 0
// 1
// 2
var c = fun(0).fun(1) //undefined 0
c.fun(2) // 1
c.fun(3) // 1
该题主要看是否创建新的闭包
二.对象高级组合继承
- 原型链+借用构造函数的组合继承
1)利用原型链实现对父类型对象的方法继承
2)利用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指向
this.price = price
}
Student.prototype = new Person() // 为了能看到父类型的方法
Student.prototype.constructor = Student //修正constructor属性
Student.prototype.setPrice = function (price) {
this.price = price
}
var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)
下面是实现的图解