this、闭包与作用域

this、闭包与作用域

this指针详解

        函数的this关键字在JavaScript中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。在绝大多数情况下,函数的调用方式决定了this的值(运行时绑定)。

  • this关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。
  • 在函数执行过程中,this一旦被确定了,就不可以再更改。

        JavaScript中this不是固定不变的,它会随着执行环境的改变而改变。

  • 全局上下文:在全局作用域(也就是不在任何函数内部)中,this指向全局对象。在浏览器环境中,这个全局对象是window。
  • 函数被直接调用:函数被直接调用(即不是作为对象的方法或不是通过new关键字)时,this指向全局对象。
  • 对象的方法:函数作为对象的方法被调用时,this指向调用该方法的对象。
  • 构造函数:函数通过new关键字被调用时(作为一个构造函数),this指向一个新创建的对象实例。
  • 箭头函数:箭头函数不绑定自己的this,它继承自包围它的函数或全局作用域的this。箭头函数中,this指向不会改变,用apply等等都不行。
  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  • 类似 call() 和 apply() 方法可以将 this 引用到任何对象。

作用域详解

        作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合。换句话说,作用域决定了代码区块中变量和其他资源的可见性。一般将作用域分成:全局作用域、函数作用域、块级作用域。

        可以理解为是一个对象包含了当前执行环境的信息。当查找变量的时候,会先从当前作用域对象中查找,如果没有找到,就会去父级查找,一直找到全局对象window,这样有多个执行上下文的变量对象构成的链条就叫做作用域链。作用域链的变量只能向上访问,变量访问到window对象即被终止。

        在js中,包含三种作用域:全局作用域/函数作用域/块级作用域。

全局作用域

        任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问。

函数作用域

        函数作用域也叫局部作用域,如果一个变量是在函数内部声明的,它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问。

块级作用域

        ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。

默认绑定、显式绑定、隐式绑定

        根据不同使用场合,this有不同的值,主要分为下面几种情况:默认绑定、显式绑定、隐式绑定、new绑定。

默认绑定

  • 如果在全局作用域(或称为函数外部)调用函数,this指向全局对象。在浏览器中是window对象(在非严格模式下),在严格模式下是undefined。
    • 在严格模式下(代码文件顶部使用'use strict';),this会是undefined,不能将全局对象用于默认绑定,this会绑定到undefined。只有函数运行在非严格模式下,默认绑定才能绑定到全局对象。
  • 如果函数被当作普通函数调用(即不是作为一个对象的方法或不是通过new关键字),this也指向全局对象。

显式绑定

  • 使用call、apply或bind方法可以直接设置函数执行时this的值。
  • call和apply会立即执行函数,并接受一个对象作为this的值,同时还可以传递参数列表。
  • bind会创建一个新的函数,当这个新函数被调用时,this的值会被设置为bind的第一个参数,并且它也可以接受额外的参数传递给原函数。

隐式绑定

  • 当函数作为某个对象的方法被调用时,this会被隐式地绑定到那个对象上。这时this就指这个上级对象,就算一个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,this永远指向的是最后调用它的对象。
  • 如果函数不是通过对象来调用,即使它在对象内部定义,this也不会绑定到那个对象上。如果函数是在一个对象的方法内部被调用,并且这个函数是作为普通函数调用(而不是作为对象的方法调用),那么this可能并不会绑定到你所期望的对象上。为了避免这种情况,可以使用call或apply来显式地设置this的值,或者使用箭头函数来自动绑定this。

new 绑定

  • 当使用new关键字来调用函数时,会创建一个新的空对象,并将this绑定到这个新创建的对象上。
  • 如果函数没有显式地返回一个非原始值(如对象或函数),new表达式的结果就是这个新创建的对象。
  • 注意,null虽然也是对象,但是return null的时候,new仍然指向实例对象

闭包的概念

        一个函数和对其周围状态(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁。

function init() {
    var name = "Mozilla"; // name 是一个被 init 创建的局部变量
    function displayName() { // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    displayName();
}
init(); //displayName()没有自己的局部变量,由于闭包的特性,它可以访问到外部函数的变量

        闭包就是有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,并将函数返回。

存储空间、执行上下文

存储空间

        在JavaScript中,存储空间通常指内存中的区域,用于存储变量、函数、对象等。JavaScript引擎(如V8、SpiderMonkey等)管理着这部分内存,并且会自动进行垃圾回收,以释放不再使用的内存空间。

        存储空间可以大致分为以下几类:

堆内存:用于存储对象实例和闭包等。堆内存是由JavaScript引擎自动管理的,当不再需要某个对象时,垃圾回收器会将其释放。

栈内存:用于存储基本数据类型(如数字、字符串、布尔值)、函数调用的参数和局部变量等。栈内存是自动分配的,并且遵循后进先出(LIFO)的原则。

代码段:存储JavaScript代码本身。代码段是只读的,防止程序意外地修改了它的指令。

执行上下文

        执行上下文是JavaScript引擎在执行代码时创建的一个环境,它定义了变量和函数的可访问性。每当JavaScript引擎开始执行一段代码(全局代码、函数代码或eval代码)时,它都会创建一个新的执行上下文,并将其推入执行上下文栈。

执行上下文的类型分为三种:        

  • 全局执行上下文:只有一个,浏览器中的全局对象就是window对象,this指向这个全局对象。当脚本开始执行时创建,它包含了全局作用域中定义的变量和函数。全局执行上下文始终存在,并且在整个脚本执行期间都保持活动状态。
  • 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。每当一个函数被调用时,JavaScript引擎都会为其创建一个新的函数执行上下文。函数执行上下文包含了函数内部的局部变量、参数、以及函数的内部作用域链。
  • Eval函数执行上下文:指的是运行在eval函数中的代码,很少用而且不建议使用。当使用eval()函数执行代码时,也会创建一个新的执行上下文。不过,由于eval()函数的安全风险和性能问题,现代JavaScript开发中很少使用它。

        只有全局上下文(的变量)能被其他任何上下文访问。可以有任意多个函数上下文,每次调用函数创建一个新的上下文,会创建一个私有作用域,函数内部声明的任何变量都不能在当前函数作用域外部直接访问。

        执行上下文栈是JavaScript引擎用于管理执行上下文的数据结构。当一个新的执行上下文被创建时,它会被推入执行上下文栈的顶部。当当前执行上下文执行完毕后(例如,函数执行完成或遇到return语句),它会被从执行上下文栈中弹出,控制权返回到前一个执行上下文。

闭包的使用场景

        任何闭包的使用场景都离不开这两点:

  • 创建私有变量和方法
  • 延长变量的生命周期

        一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的。

        闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。

function makeSizer(size) { //例子:在页面上添加一些可以调整字号的按钮
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

柯里化函数

        目的在于避免频繁调用具有相同参数函数的同时,又能够轻松的重用。 指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只接受一个参数。将多参数的函数转换成单参数的形式。

使用闭包模拟私有方法

        在JavaScript中,没有支持声明私有变量,但我们可以使用闭包来模拟私有方法。

其他

        例如计数器、延迟调用、回调等闭包的应用,其核心思想还是创建私有变量和延长变量的生命周期。

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值