JavaScript 基础(作用域/作用域链,变量对象,变量提升)

作用域

作用域是指程序源代码中定义变量的区域。

作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。

  • 词法作用域(静态作用域)
    • 函数的作用域在函数定义的时候就决定了。
  • 动态作用域
    • 作用域在函数被调用的时候才决定。
var value = 1;
function foo() {
  console.log(value);
}
function bar() {
  var value = 2;
  foo();
}
bar(); // 1

作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。

这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

函数的作用域在函数定义的时候就决定了。

  • 函数创建
    • 函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
    function foo() {
      function bar() {
          //...
      }
    }
    // 函数创建时,各自的[[scope]]为:
    foo.[[scope]] = [
      globalContext.VO
    ];
    bar.[[scope]] = [
      fooContext.AO,
      globalContext.VO
    ];
    
  • 函数激活
    • 当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。
    • 这时候执行上下文的作用域链,我们命名为 Scope:
      Scope = [AO].concat([[Scope]]);

执行上下文

当变量提升时:

var foo = function () {
  console.log('foo1');
}
foo();  // foo1
var foo = function () {
  console.log('foo2');
}
foo(); // foo2

当函数提升时:

function foo() {
  console.log('foo1');
}
foo();  // foo2
function foo() {
  console.log('foo2');
}
foo(); // foo2

JavaScript 的可执行代码(executable code)的类型分三种,全局代码、函数代码、eval代码。

当执行到一个函数的时候,就会进行准备工作,这里的"准备工作"就叫做"执行上下文(execution context)"

当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。

执行上下文栈(Execution context stack,ECS)来管理执行上下文

变量对象(Variable object,VO)

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。

全局对象

  1. 可以通过 this 引用,在客户端 JavaScript 中,全局对象就是 Window 对象。
  2. 全局对象是由 Object 构造函数实例化的一个对象。
  3. 预定义了一堆,一大堆函数和属性。
  4. 作为全局变量的宿主。
  5. 客户端 JavaScript 中,全局对象有 window 属性指向自身。

PS:见W3School

全局函数和属性

  • Infinity 代表正的无穷大的数值。
  • java 代表 java.* 包层级的一个 JavaPackage。
  • NaN 指示某个值是不是数字值。
  • Packages 根 JavaPackage 对象。
  • undefined 指示未定义的值。
  • decodeURI() 解码某个编码的 URI。
  • decodeURIComponent() 解码一个编码的 URI 组件。
  • encodeURI() 把字符串编码为 URI。
  • encodeURIComponent() 把字符串编码为 URI 组件。
  • escape() 对字符串进行编码。
  • eval() 计算 JavaScript 字符串,并把它作为脚本代码来执行。
  • getClass() 返回一个 JavaObject 的 JavaClass。
  • isFinite() 检查某个值是否为有穷大的数。
  • isNaN() 检查某个值是否是数字。
  • Number() 把对象的值转换为数字。
  • parseFloat() 解析一个字符串并返回一个浮点数。
  • parseInt() 解析一个字符串并返回一个整数。
  • String() 把对象的值转换为字符串。
  • unescape() 对由 escape() 编码的字符串进行解码。

活动对象

正如全局对象,当全局环境一产生,全局对象就由此诞生。

在函数上下文中,就用活动对象(activation object, AO)来表示变量对象。

活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。

执行上下文的代码会分成两个阶段进行处理:

  • 分析,当进入执行上下文(这时候还没有执行代码),变量对象会包括
    1. 函数的所有形参 (如果是函数上下文)
    • 由名称和对应值组成的一个变量对象的属性被创建
    • 没有实参,属性值设为 undefined
    1. 函数声明
    • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
    • 如果变量对象已经存在相同名称的属性,则完全替换这个属性
    1. 变量声明
    • 由名称和对应值(undefined)组成一个变量对象的属性被创建;
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
  • 执行,代码执行
    • 在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值
function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};
  b = 3;
}
foo(1);

// 在进入上下文后,AO为
AO = {
  arguments: {
    0: 1,
    length: 1
  },
  a: 1,
  b: undefined,
  c: reference to function c(){},
  d: undefined
}
// 执行完成后,AO为
AO = {
  arguments: {
    0: 1,
    length: 1
  },
  a: 1,
  b: 3,
  c: reference to function c(){},
  d: reference to FunctionExpression "d"
}

undefined & null

undefined和null在if语句中,都会被自动转为false

  • null 表示"没有对象",即该处不应该有值
    1. 作为函数的参数,表示该函数的参数不是对象。
    2. 作为对象原型链的终点。
  • undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义
    1. 变量被声明了,但没有赋值时,就等于undefined。
    2. 调用函数时,应该提供的参数没有提供,该参数等于undefined。
    3. 对象没有赋值的属性,该属性的值为undefined。
    4. 函数没有返回值时,默认返回undefined。

变量提升

  • 所有的声明都会提升到作用域的最顶上去。
  • 同一个变量只会声明一次,其他的会被忽略掉或者覆盖掉。
  • 函数声明的优先级高于变量申明的优先级,并且函数声明和函数定义的部分一起被提升。

事件驱动–addEventListener

EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Element,Document 和 Window 或者任何其他支持事件的对象 (比如 XMLHttpRequest)

  • 允许给一个事件注册多个监听器。 特别是在使用AJAX库,JavaScript模块,或其他需要第三方库/插件的代码。
  • 提供了一种更精细的手段控制 listener 的触发阶段。(即可以选择捕获或者冒泡)。
  • 对任何 DOM 元素都是有效的,而不仅仅只对 HTML 元素有效。
target.addEventListener(String type, [Function | Object] listener[, options]);
target.addEventListener(String type, [Function | Object] listener[, useCapture]);
target.addEventListener(String type, [Function | Object] listener[, useCapture, wantsUntrusted  ]);  // Gecko/Mozilla only
  • type 事件类型的字符串。
  • listener 一个回调函数或者是一个实现了 Event 接口的对象
    回调时箭头函数时,原函数中可用的变量和常量在事件处理器中同样可用
  • options 一个指定有关 listener 属性的可选参数对象。可用的选项如下:
    • capture: Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
    • once: Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
    • passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
    • mozSystemGroup: 只能在 XBL 或者是 Firefox’ chrome 使用,这是个 Boolean,表示 listener 被添加到 system group。
  • useCapture

事件监听器可以被指定为回调函数或实现EventListener的对象,其handleEvent()方法用作回调函数

回调要么是一个函数要么就是一个实现了 EventListener 的对象里面的 handleEvent() 方法

回调函数本身具有与 handleEvent() 方法相同的参数和返回值;也就是说,回调接受一个参数:一个基于Event 的对象

事件原型

(function() {
  if (!Event.prototype.preventDefault) {
    Event.prototype.preventDefault=function() {
      this.returnValue=false;
    };
  }
  if (!Event.prototype.stopPropagation) {
    Event.prototype.stopPropagation=function() {
      this.cancelBubble=true;
    };
  }
  if (!Element.prototype.addEventListener) {
    var eventListeners=[];
    
    var addEventListener=function(type,listener /*, useCapture (will be ignored) */) {
      var self=this;
      var wrapper=function(e) {
        e.target=e.srcElement;
        e.currentTarget=self;
        if (typeof listener.handleEvent != 'undefined') {
          listener.handleEvent(e);
        } else {
          listener.call(self,e);
        }
      };
      if (type=="DOMContentLoaded") {
        var wrapper2=function(e) {
          if (document.readyState=="complete") {
            wrapper(e);
          }
        };
        document.attachEvent("onreadystatechange",wrapper2);
        eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper2});
        
        if (document.readyState=="complete") {
          var e=new Event();
          e.srcElement=window;
          wrapper2(e);
        }
      } else {
        this.attachEvent("on"+type,wrapper);
        eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper});
      }
    };
    var removeEventListener=function(type,listener /*, useCapture (will be ignored) */) {
      var counter=0;
      while (counter<eventListeners.length) {
        var eventListener=eventListeners[counter];
        if (eventListener.object==this && eventListener.type==type && eventListener.listener==listener) {
          if (type=="DOMContentLoaded") {
            this.detachEvent("onreadystatechange",eventListener.wrapper);
          } else {
            this.detachEvent("on"+type,eventListener.wrapper);
          }
          eventListeners.splice(counter, 1);
          break;
        }
        ++counter;
      }
    };
    Element.prototype.addEventListener=addEventListener;
    Element.prototype.removeEventListener=removeEventListener;
    if (HTMLDocument) {
      HTMLDocument.prototype.addEventListener=addEventListener;
      HTMLDocument.prototype.removeEventListener=removeEventListener;
    }
    if (Window) {
      Window.prototype.addEventListener=addEventListener;
      Window.prototype.removeEventListener=removeEventListener;
    }
  }
})();

以上来自 MDN

jQuery 事件

不得不说 jQuery 中换了一种思维,保留了事件原型中的大部分特性

  • $.Event 事件对象
    • $.Event 与原生 event 相全部得到的一个全新事件对象
  • 事件注册
    • on() 入口,与传统事件绑定不同,可以绑定多个事件并独立执行
    • 用 $.Callbacks 回调存放,并没每个回调设置标识,可以直接用 off() 直接移除
    • 并且回调也可以是对象
  • 事件执行
    • 事件触发执行 $.Event
    • 事件队列里的回调全部执行
  • 抛弃原生捕获和冒泡
    • 模拟捕获和冒泡
  • 支持自定义事件
  • 手动触发

参考链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值