闭包,原型链
闭包
闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
- 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
- 从实践角度:以下函数才算是闭包:
- 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
- 在代码中引用了自由变量
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
function f1() {
let n = 999
nAdd = function () {
n += 1
}
function f2() {
alert(n)
}
return f2
}
const result = f1()
result() // 999
nAdd()
result() // 1000
result 函数就是闭包 f2 函数一共运行了两次,第一次的值是 999,第二次的值是 1000。这证明了,函数 f1 中的局部变量 n 一直保存在内存中,并没有在 f1 调用后被自动清除。
f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
nAdd 前面没有使用 var 关键字,因此 nAdd 是一个全局变量,而不是局部变量。其次,nAdd 的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以 nAdd 相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。
使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
原型&原型链
在 JavaScript 中,所有函数都有一个名为 prototype 的特殊属性,值是一个有 constructor 属性的对象.类似这样:
function doSomething() {}
doSomething.prototype.foo = 'bar'
console.log(doSomething.prototype)
在浏览器控制台打印这段代码可以看到下面这段:
{
foo: "bar",
constructor: ƒ doSomething(),
[[Prototype]]: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
prototype 是一个对象,创建函数的时候默认会添加.
下面__proto__
等同于[[Prototype]]
每个 JS 对象都有一个__proto__
属性.
因为 doSomething.prototype
是一个对象,对象的__proto__
指向构造函数的 prototype
doSomething.prototype.__proto__ === Object.prototype
// true
Object.prototype.__proto__ === null
// 原型链的顶端
把这个函数当作构造函数(constructor)调用(即通过 new 关键字调用),那么 JS 就会帮你创建该构造函数的实例,实例继承构造函数 prototype 的所有属性和方法(实例通过设置自己的__proto__
指向承构造函数的 prototype 来实现这种继承)。
JS 正是通过__proto__
和 prototype 的合作实现了原型链,以及对象的继承。
构造函数,通过 prototype 来存储要共享的属性和方法,也可以设置 prototype 指向现存的对象来继承该对象。
对象的__proto__
指向自己构造函数的 prototype。obj.__proto__.__proto__...
的原型链由此产生,包括我们的操作符 instanceof 正是通过探测obj.__proto__.__proto__... === Constructor.prototype
来验证 obj 是否是 Constructor 的实例。
- 原型链的尽头是 Object.prototype(
Object.prototype.__proto__ === null
)。所有对象均从 Object.prototype 继承属性。 - Function.prototype ===
Function.__proto__
为同一对象(Function 构造函数是一个函数对象)。 Object.__proto__
=== Function.prototype(Object 作为构造函数时,是 Function 的实例)
instanceof
object instanceof constructor
// object 某个实例对象
// constructor 某个构造函数
instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。
// 递归版
function myInstanceOf(obj, target) {
if (obj === null || typeof obj !== 'object') {
return false
}
if (!target.prototype) throw Error
const protot = Object.getPrototypeOf(obj)
return protot === target.prototype ? true : myInstanceOf(protot, target)
}
// 非递归版
function myInstanceOf(obj, target) {
if (obj === null || typeof obj !== 'object') {
return false
}
if (!target.prototype) throw Error
while (obj) {
const protot = Object.getPrototypeOf(obj)
if (protot === target.prototype) return true
obj = protot
}
return false
}
参考
https://github.com/mqyqingfeng/Blog/issues/9
https://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://github.com/creeperyang/blog/issues/9
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain