闭包
闭包就是 一个函数可以在另一个函数的作用域外 访问 该函数的作用域。
function foo(){
var x = 3;
function fn(){
console.log(x);
return x;
}
return fn;
}
var b = foo(); // fn
b(); // 3
此处有一个函数foo,函数内部定义了一个变量x和一个函数fn,这个函数的作用是返回变量x;
函数外通过变量b拿到foo函数执行的结果,实际就是拿到函数fn的返回值;
最后通过执行 b() ,可以拿到foo函数里的变量x为3。(通过函数fn访问到了函数foo里的变量x,即形成了闭包)
闭包的特征:
-
是一个函数
-
一定是一个函数的作用域外访问的时候才会形成的
-
闭包导致的问题:
会阻止垃圾回收,原因是foo是fn的父函数,而fn被赋给了一个全局变量,这导致fn始终在内存中,而fn的存在依赖于foo,因此foo也始终在内存中,不会在调用结束之后,被垃圾回收机制回收,会一直保留在内存中。
此时可能面试官会问:那怎么解决垃圾回收的阻塞呢?
答:使用完b之后,可以把b重新赋值为null。此时b就已经去掉了引用,foo就可以被回收了。
闭包会出现在哪些场景下:(怎样判断一个闭包?)
-
一个函数里面 返回另一个函数 作为返回值(上面的例子就是如此)
-
函数作为参数值,即回调函数,比如说setTimeout(() => {...},1000) 闭包有两个常用用途:
-
闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在 外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
-
闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
作用域链
函数内部可以访问到函数外部声明的一些变量,如果要再访问的话还能访问到再外部的变量,最外部可以访问到全局,当然优先访问的是当前自己作用域的,如果没有会再往外找。这样一层一层就形成了作用域链。最后仍未找到要访问的变量x的话,就认为这段代码的作用域链上不存在x变量,并抛出一个引用错误(ReferenceError)的异常。
原型链
JavaScript中的每个对象都有一个prototype属性,我们称之为原型,而原型的值也是一个对象,因此它也有自己的原型,这样就串联起来了一条原型链,原型链的链头是object,它的prototype比较特殊,值为null。
原型链的作用是用于对象继承,函数A的原型属性(prototype property)是一个对象,当这个函数被用作构造函数来创建实例时,该函数的原型属性将被作为原型赋值给所有对象实例,比如我们新建一个数组,数组的方法便从数组的原型上继承而来。
当访问对象的一个属性时, 首先查找对象本身, 找到则返回; 若未找到, 则继续查找其原型对象的属性(如果还找不到实际上还会沿着原型链向上查找, 直至到根). 只要没有被覆盖的话, 对象原型的属性就能在所有的实例中找到,若整个原型链未找到则返回undefined。
- 构造函数、原型对象、实例的三角关系图
- 原型链如何形成 每一个实例对象上有一个proto属性,指向的构造函数的原型对象,构造函数的原型 对象也是一个对象,也有proto属性,这样一层一层往上找的过程就形成了原型链