关于js闭包的学习(草稿)

219 阅读6分钟

闭包: javascript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。

var foo = function(){  
    var name = "exe";  
    return function inner(){  
        console.log( name );  
    }  
}
var bar = foo();
bar(); // exe

出现这个状况的原因:

函数内部的[[scope]]是虚拟出来的一个属性,我们实际访问时是访问不到这个属性的,这个属性是为了让我们更好的理解函数,虚拟出来的一个内部属性。(我们不能使用,供js引擎使用) 我们在创建函数的时候会生成[[scope]]这个属性,这个属性就会保存函数被创建的作用域中对象的集合(这个集合呈链式链接,被称作函数的作用域链)。在函数执行时,函数会生成一个scope属性,这个属性保存着函数在执行上下文时创建的活动对象,活动对象包含函数内部的局部变量和函数参数,和函数内部的[[scope]]属性。
例子:

var x = 10;
function foo () {
    console.log(x);
}
foo(); // 10
function fun () {
    var x = 20;
    var foo1 = foo;
    foo1();   // 10还是20?
}
fun();

最后的结果是10,原因:在复制函数的时候,复制后的函数与复制前的函数引用的是同一个[[scope]]属性,所以foo1执行的时候,foo1的作用域链就直接查找到了全局对象中的x属性,所以最后返回10

scope是域的意思

[[scope]]是在ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域对象的集合,这个集合被称为函数的作用域链,它决定哪些数据能被函数访问
1.在函数创建的时候,它的作用域链中会填入一个全局对象,该全局对象包含了所有的全局变量;
2.函数执行的时会创建一个成为“运行期上下文”的内部对象,运行期上下文定义了函数执行时的环境。每个运行期上下文都有自己的作用域链,用于标识符解析,当运行期上下文被创建时,它的作用域链初始化为当前运行函数的[[scope]]所包含的对象;
3.这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中,它们共同组成了一个新的对象,叫“活动对象”,该对象包含了函数的所有局部变量、参数命名、参数集合以及this,然后次对象会推入作用域链的前端
4.当运行期上下文被销毁,活动对象也随之销毁。

函数不能操作传入的参数: js里处理object是用引用传递,当调用函数时,如果修改object的值,是修改的引用的值(原值会被修改)

[[scope]]和执行上下文(执行环境)虽然保存的都是作用域链,但是不是同一个东西。 [[scope]]属性是函数创建时产生的,会一直存在 而执行上下文是在函数执行时产生的,函数执行结束便会销毁。

执行环境=执行上下文

套娃思维:blog.csdn.net/ChenXvYuan_…

js引擎查找作用域链是为了解析标志符,变量在执行环境作用域链的位置越深,读写速度就越慢。

在函数中读写局部变量总是最快的,读写全局变量通常最慢。
这些额外的性能开销对于优化js引擎来说可能微不足道,甚至可以说咩有性能损耗,但是还是要养成这样的编码习惯:尽量使用局部变量(变量) 例如:

function demo(){
    var a = document.getElementById('a');
    var b = document.getElementById('b');
    var c = document.getElementById('c');
    a.onclick = function(){
        ...
    }
    a.style.left = ...;
    a.style.top = ...;
    b.style.backgroundColor = ...;
    c.className = ...;
    document.body.appendChild(...);
    document.body.appendChild(...);
    document.body.appendChild(...);
}

这段代码看起来缓存了,优化了代码,但是其实这段代码执行的过程中,js引擎解析标志符,要查找6次document,而且document位于window对象,也就是作用域链的最末尾(因为作用域链一层一层的网上找)
重构函数:

function demo(){
    var doc = document,
        bd = doc.body,
        a = doc.getElementById('a'),
        b = doc.getElementById('b'),
        styleA = a.style;
    a.onclick = function(){
        ...
    }
    styleA.left = ...;
    styleA.top = ...;
    styleA.backgroundColor = ...;
    b.className = ...;
    bd.appendChild(...);
    bd.appendChild(...);
    bd.appendChild(...);
}

执行环境: 在函数执行时,会创建一个叫做执行环境/执行上下文的内部对象
它定义了一个函数执行时的环境
函数每次执行时的执行环境独一无二
多次调用函数就会多次创建执行环境
并且函数执行完毕后,执行环境就会被销毁
执行环境有自己的作用域链,用于解析标志符

C++/java面向对象,都是写一个class,然后new出相应的实例对象,如果class要继承另一个,直接用相应的语法继承就行了。 而在javascript中,采用的是原型继承的思路来实现面向对象的,通过new一个构造函数来生成一个对象

在javascript中,没有class的概念(ES6之后的语法糖暂且不提),而是使用一个函数,运行new的时候便会生成一个对象,有一个特别属性__proto__,它的值就是要new的对象的prototype的引用,新对象的__proto__ === 原对象的prototyoe。

每个函数都有一个prototype属性,

console.log(Person.prototype);
{constructor: ƒ}
  constructor: ƒ Person(s)
__proto__: Object

是一个包含constructor的对象,如果给这个prototype对象加点东西,那么由这个Person函数构造出来的对象就都能访问到。

Person.prototype = {
  country: 'China'
};

这时候

console.log(Person.prototype);
{country: "China"}
  country: "China"
__proto__: Object

每个构造函数都有一个prototype属性,该属性指向了一个原型对象
原型对象默认会有一个constructor属性指向的是prototype所在的函数(所以展开,永远都展不完)
函数.prototype === 对象点(.)proto 就都可以访问到构造函数(构造函数中的prototype属性包含构造函数和__proto__,)

prototype可以使用__proto__访问

Object.prototype有原型吗? Object.prototype.proto 的值为null

Note:通过Function.prototype.bind方法构造出来的函数是一个例外,它没有prototype属性

constructor这个属性主要用于将原型对象指向关联的构造函数

prototype指向一块内存,这个内存里有公用的属性 __proto__指向同一块内存 prototype和__proto__的不同点在于prototype是构造函数的属性,而__proto__是对象的属性,用于自身的原型链访问

__proto__帮忙节省了很多代码,prototype的意义就是把共有属性预先定义好,给之后的对象用

__proto__是js内置的,prototype是你自己写的。__proto__里已经内置了一些方法,这就是封装好的js函数。怕你不够用,多给你个prototype让你自己写,写多了会有全局污染

===js内存泄漏问题 内存泄漏会导致出一系列问题,比如:运行缓慢,崩溃,高延迟

闭包使用不好就会有内存泄漏问题

执行上下文,简言之,就是评估和执行javascript代码的环境的抽象概念。

写csv文件时,return的语句块问题

在js函数中,执行上下文是可变的,其中this的指向可通过bind和call、apply改变。

bind和call、apply改变作用域 相同点:三者都是用来改变函数内部this的指向 不同点: 1、bind是返回对应函数,便于稍后调用;apply、call是立即调用; 2、apply、call接受参数的方式不一样(使用call需要把参数按照顺序传递进去,而使用apply是把参数放在数组里); 3、bind()是永久的绑定,还会覆盖后面的apply/call命令。使用bind()绑定后,再使用call()或apply()不能重新绑定this;

执行上下文: juejin.cn/post/684490…