原型与作用域详解

作用域与全局对象

  1. 当在页面中引入多个js文件的时候,需要把单个js文件中的代码写入一个立即执行函数中,然后将需要使用到的东西返回出来即可,这样可以避免全局变量污染

原型与原型链

  1. prototype
    函数,本质上是一个特殊的对象,他的特殊之处之一就是,函数可以直接访问该函数的prototype属性,即原型,原型是一个普通对象,函数即有原型,又有隐式原型,原型prototype默认存储的为constructor即构造函数本身的函数体
  2. proto
    此为隐式原型,所有通过构造函数new出来的对象都具有此属性,此属性也是一个对象,这个对象中存储了构造函数的prototype
  3. 原型链
    所谓原型链,即在对象诞生过程中的,构造函数具有自己的prototype,通过new创造出的实例具有__proto__属性,该属性存储了构造函数的prototype,这样就形成了一条链,当在构造函数的原型上添加一些数据或者方法时,通过该构造函数创建的对象也可以通过自己的__proto__(隐式原型)找到构造函数的原型从而使用构造函数原型上的方法和数据
    当访问实例成员时(对象的某个值先在自身上找,如果自身找不到就会通过__proto__找到构造函数的原型,然后在构造函数的prototype上找,由于构造函数也是对象他也具有隐式原型,当在构造函数的原型上也找不到时就会继续向上查找,这个时候一般就找到Object(或者Array..),这就是为什么可以声明一个对象,他身上没有toString方法却可以直接使用的原因

属性描述符

Objec.defineProperty

执行期上下文

此为一个栈结构
在js代码执行之前需要有一个供代码使用的环境此为全局执行上下文,存在于栈底,建立好全局上下文后才可以开始执行代码
在函数运行(调用或者运行时)之前会创建一片空间,空间中包含该函数执行所需要的数据,此为函数的执行期上下文,函数调用或者运行完毕之后会将该函数的执行期上下文移除,继续新的函数运行,注意,定义函数是不会创建上下文的,只有调用时才会去创建上下文

js引擎始终执行的是栈顶上下文中的环境(处于该上下文的函数)

  1. 执行上下文中的内容
    1. this(this指向)
      如在全局全局上下文中的this在浏览器环境中就指向window,一个函数在全局上下文中运行那么他的上下文中的this指向全局上下文中的this即window,如果通过new调用此函数,那么这个函数的上下文中的this就指向新对象
    2. VO变量对象
      Variable Object:VO 中记录了该环境中所有声明的参数变量函数
      全局环境的上下文中的vo对象,可以认为全局环境的vo也指向window,全局环境的vo对象由于其特殊性也可以叫做GO对象(global object)
    3. vo对象中数据的获取流程(变量提升原理)
      1. 确定所有**形参值(会直接赋值)**以及特殊变量arguments
      2. 确定函数中通过var声明的变量,将它们的值设置为undefined,如果VO中已有该名称,则直接忽略。
      3. 确定函数中通过字面量声明的函数,将它们的值设置为指向函数对象,如果VO中已存在该名称,则覆盖。完成这一步后就开始一行一行的执行代码了
      4. 函数是一等公民其原因就在于如果发现var声明的变量已存在在vo中会直接忽略此次的undefined赋值,而如果是一个字面量函数声明发现vo中已有同名变量则会直接覆盖
      function A(a, b) {
          console.log(a, b);
          var b = 123;
          console.log(a, b);
      
          function b() {
              var d = 123;
          }
      }
      
      A(1, 2);
      
      根据vo对象数据挂载规则,在全局vo中会首先将函数字面量对象A挂载,然后执行A,A执行前创建A函数的上下文,由于A函数直接执行this指向全局上下文即window,vo对象中首先将形参a和b直接赋值为1和2,同时将argument赋值,然后发现通过var声明的b由于vo中已存在b则不用将b赋值为undefined,然后发现字面量函数声明b则将vo中的b覆盖指向函数对象,然后开始执行代码第一个console将输出1和function,然后执行到赋值语句,将b赋值为123,接下来输出为1和123,函数执行完毕

作用域链

  1. VO中包含一个额外的属性,该属性指向创建该VO的函数本身(上下文是由函数调用产生的,全局vo特殊没有此属性)
  2. 每个函数对象在创建时,会有一个隐藏属性[[scope]],它指向创建该函数时所处的vo(上下文)
  3. 当访问一个变量时,会先查找自身VO中是否存在,如果不存在,则依次查找[[scope]]属性。
  4. 某些浏览器会优化作用域链,函数的[[scope]]中仅保留需要用到的数据。
  5. 闭包原理
        function A() {
            var one = 1
            return function() {
                one++
                console.log(one)
            }
        }
        var x = A()
        x()
        x()
    
    这段代码将会输出2和3,原因是当运行A时首先创建函数A的vo对象,这个vo对象有一个特殊属性他指向创建此对象的函数本身也就是A函数,函数对象在创建时会有一个隐藏属性scope,他指向创建该对象时随处的上下文对象即vo,此时函数A的vo中挂载变量one为undefined,开始执行代码,将one赋值为1,然后遇到一个返回一个函数的表达式,这里相当于new了一个匿名函数,这个匿名函数在new的时候创建了函数对象那么他身上就有了scope,这个scope就持有了他被创建时所处的vo即函数A的vo,这时这个函数被返回了出去,由于A的vo依然被返回的函数持有所有垃圾回收器不会将A的vo回收,这时开始调用被返回并保存的函数,开始创建上下文调用此函数是在全局环境中调用的,所以其上下文中的this指向window,vo也指向全局的vo即window,vo中的额外属性指向被返回的匿名函数,这个匿名函数的scope持有了A函数执行时的vo对象,所以他依然可以访问A函数的vo,所以就实现了one的累加,这就是闭包

事件循环

  1. 浏览器的线程
    1. JS执行引擎:负责执行JS代码
    2. 渲染线程:负责渲染页面
    3. 计时器线程:负责计时
    4. 事件监听线程:负责监听事件
    5. http网络线程:负责网络通信
  2. 事件队列(分宏队列和微队列)
    一块内存空间,用于存放执行时机到达的异步函数。当JS引擎空闲(执行栈没有可执行的上下文),它会从事件队列中拿出第一个函数执行。
    1. 宏队列
      setTimeout、setInterval、I/O、UI 渲染、DOM 事件等回调
    2. 微队列
      Promise.then、MutationObserver、queueMicrotask 等回调,优先级高于宏任务
  3. 事件循环
    事件循环是指函数在执行栈、宿主线程、事件队列中的循环移动,需要理解的是只有js执行引擎才可以执行函数,其他的线程都是做监听之用,当它们监听到了函数的执行时机就会将这个函数移交到对应的队列中,而队列里的函数不会立即执行,他会等js执行线程中没有要执行的函数时,js执行线程会先去微队列查看是否有要执行的函数如果有则执行,当微队列中的函数执行完再去看宏队列中是否有函数需要执行,如果有则执行,注意:当执行一个宏任务(如 setTimeout 回调、事件回调)时,如果在执行过程中产生了新的微任务(如 Promise.then),这些微任务会被推入微任务队列,但不会立即执行,当前宏任务会继续执行到完成(包括其所有同步代码),每个宏任务执行完毕后执行栈会查看微队列里是否存在待执行任务,如果有则优先清空微队列,然后再转回执行宏队列中的任务,每个宏任务执行完执行栈都会去查看微队列是否有待执行任务,而微队列则会优先清空,清空后再查看宏队列是否存在任务

原型与作用域详解
http://localhost:8090/archives/yuan-xing-yu-zuo-yong-yu-xiang-jie
作者
Administrator
发布于
2026年01月30日
许可协议