闭包、原型、原型链
仅记录自己的学习过程
携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 2 天,点击查看活动详情
1.闭包
闭包的概念:当一个函数能够记住并访问到其所在的词法作用域及作用域链,特别强调是在其定义的作用域外进行的访问,此时该函数和其上层执行上下文共同构成闭包。
需要明确的几点:
- 闭包一定是函数对象
- 闭包和词法作用域、作用域链、垃圾回收机制等息息相关
- 函数内保持对上层作用域的引用
- 当函数在其定义的作用域外保持引用并进行访问时,才产生闭包
- 闭包是由该函数和其上层执行上下文共同构成
闭包有什么用??——可以让局部变量始终保持在内存中,
- 在外部操作某个局部变量
- 模块化(独立作用域,设置私有属性,共有属性),避免污染全局变量
- 应用,循环内部的事件函数获得循环的i
、
1.内部访问不到i,事件触发是异步,事件绑定是同步。
2.解决方法,用let声明变量,锁定区域。(ES6)
2.变量及作用域:
变量:全局变量和局部变量,函数内部可以直接读取全局变量,在函数外部无法直接读取函数内的局部变量。
通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
词法作用域:词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。(关注点在函数在何处定义)
动态作用域——(类似于this),是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。(关注点在函数在何处调用)
主要区别:词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
作用域链:本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。——(只包含作用链表)
3.垃圾回收机制
- 标记清除
当变量进入执行环境时,将这个变量标记为“进入环境”。当变量离开执行环境时,则将其标记为“离开环境”,就销毁回收内存。
- 引用计数
跟踪记录每个值被引用的次数,当引用次数变成0时,就销毁回收内存(——早期,问题:容易造成内存泄漏——内存被塞满导致死机)
注意:闭包会使得函数中的变量被保存在内存中,增加内存消耗,不能滥用闭包,否则会造成网页的性能问题,在低版本IE中还可能导致内存泄露。
4.原型及原型链
(1) 属性:prototype(原型)
每个函数对象(Function.prototype 构造函数原型除外)都有一个prototype属性(这个属性指向一个对象即原型对象),prototype原型是函数的一个默认属性,在函数的创建过程中由JS编译器自动添加(对象数组字符串都没有prototype)
验证:
Function.prototype 也是一个函数,为原码其他函数的原型是一个普通对象,实例对象共享原型对象上的属性方法(继承),函数原型方法中的this指向实例对象。
var fn1 = function (){ };
var fn2 = new Function();
function fn3(){ };
console.log(fn1.prototype);
console.log(fn2.prototype);
console.log(fn3.prototype); // Object{} 这就是我们所说的原型,它是一个对象也叫原型对象
// 为什么说 Function.prototype 除外呢?看代码:
console.log(Number.prototype);
console.log(String.prototype);
console.log(Function.prototype);
console.log(Function.prototype.prototype);// 结果看下图
(2) 属性:constructor(构造器)
每个对象都有一个隐藏属性constructor,该属性指向对象的构造函数(“类”),原型的主要作用是用于“继承”——prototype主要作用用于继承,__proto__主要作用用于维护原型链。
(3)属性:proto(原型)
每个对象都有一个隐藏属性__proto__,用于指向创建它的构造函数的原型,每一个对象(普通对象和函数对象)都有_proto_属性,指向创建该对象的构造函数的prototype。上面我们讲prototype是针对每个函数对象,这个__proto__是针对每个对象,属性__proto__非官方标准属性,但主流的浏览器基本都支持
var n = 123;
var s = 'jdk';
var b = true;
var a = [];
var f = function (){};
var o = {};
console.log(n.__proto__);
console.log(n.__proto__ === Number.prototype);
console.log(s.__proto__ === String.prototype);
console.log(a.__proto__ === Array.prototype);
console.log(f.__proto__ === Function.prototype);
console.log(o.__proto__ === Object.prototype);
console.log(b.__proto__ === Boolean.prototype);
对象通过__proto__指向原型对象,函数对象 通过prototype指向原型对象
在 JavaScript 中,没有类,只有对象,原型机制是一个内部链接,其本质是行为委托、对象间创建链接 ,这种链接在对一个对象进行属性/方法引用,而这样的属性/方法不存在时实施,在这种情况下,[[Prototype]] 链接告诉引擎在那个被链接的对象上查找这个属性/方法,接下来,如果这个对象不能满足查询,它的 [[Prototype]] 又会被查找,如此继续。。。这个在对象间的一系列链接构成了所谓的“原形链”
每个继承父函数的子函数的对象(实例对象)都包含一个内部属性__proto__,该属性包含一个指针,指向父函数的prototype,若父函数的原型对象的__proto__属性为再上一层函数的原型,在此过程中就形成了原型链。
对象间的关系图: