从数据类型到变量、作用域、执行上下文

news2024/9/24 9:28:15

从数据类型到变量、作用域、执行上下文

JS数据类型

分类

1》基本类型:字符串String、数字Number、布尔值Boolean、undefined、null、symbol、bigint

2》引用类型:Object (Object、Array、Function、Date、RegExp、Error、Arguments)

Symbol是ES6新出的一种j基本数据类型,特点就是没有重复的数据,所以它可以作为object的key。
数据的创建方法Symbol(),因为它的构造函数不够完整,所以不能使用new Symbol()创建数据。由于Symbol()创建数据具有唯一性,所以 Symbol() !== Symbol(), 同时使用Symbol数据作为key不能使用for获取到这个key,需要使用Object.getOwnPropertySymbols(obj)获得这个obj对象中key类型是Symbol的key值

  const s1 = Symbol("hi");
  console.log(s1); // Symbol(hi)
  const s2 = Symbol("hi");
  console.log(s1 == s2); // false
  let obj = {};
  obj[s1] = "hello";
  obj[s2] = "world";
  const keys = Object.getOwnPropertySymbols(obj);
  console.log(keys); // [Symbol(hi), Symbol(hi)];

bigint大整数,不能用于Math 对象中的方法;不能和任何Number实例混合运算,两者必须转换成同一种类型

      const bigNum1 = BigInt(1);
      const bigNum2 = BigInt(2);
      const num = Number(bigNum2 - bigNum1); // 1

数据类型判断

typeof

typeof检测原始类型(基本数据类型)的具体类型

返回值:数据的类型;

不能判断null的类型,如果是null,返回的是object

能判断函数的类型,函数返回的是function

      console.log(typeof 666); // number
      console.log(typeof "nb"); // string
      console.log(typeof true); // boolean
      console.log(typeof undefined); // undefined
      console.log(typeof null); // object
      console.log(typeof [1, 2]); // object
      console.log(typeof { content: "hi" }); // object
      function func(){
        console.log("月亮不睡我不睡");
      };
      console.log(typeof func); // function

补充:任何实现内部使用了call方法的对象,使用typeof都返回function,比如正则表达式;在Chrome7和Safari5及之前版本,上述浏览器的正则表达式内部实现使用了call,但是后面都是返回object了

instanceof

适用于检测引用类型的具体类型

返回值:布尔值

自己定义的构造函数,new 出来的实例,也可以检测

  console.log([1, 2] instanceof Array); //true
  console.log({ content: "hi" } instanceof Object); //true
  const fn = () => {
    console.log("不是秃头小宝贝");
  };
  console.log(fn instanceof Function); //true
  console.log([1, 2] instanceof Object); //true
  console.log({ content: "hi" } instanceof Array); //false
  function Person(name) {
    this.name = name;
  }
  const p = new Person("luka");
  console.log(p instanceof Person); //true
  // instanceof不能检测基本数据类型
  console.log(66 instanceof Number); //false
  console.log(null instanceof Object); //false
Object.prototype.toString
      const toString = Object.prototype.toString;
      console.log(toString.call(undefined)); // [object Undefined]
      console.log(toString.call(null)); // [object Null]
      console.log(toString.call(true)); // [object Boolean]
      console.log(toString.call(1)); // [object Number]
      console.log(toString.call("")); // [object String]
      console.log(toString.call({})); // [object Object]
      console.log(toString.call([])); // [object Array]
      console.log(toString.call(() => {})); // [object Function]
      console.log(toString.call(new Date())); // [object Date]
构造函数

对象的constructor指向创建该实例对象的构造函数

注意 nullundefined 没有 constructor,以及 constructor 可以被改写,不太可靠

      const arr = [1, 2, 3];
      console.log(arr.constructor === Array); // true
全等===
      console.log(null === null);
      console.log(undefined === undefined);

变量

变量的存储方式

基本数据类型存在栈中,引用类型存在堆中

栈中存数据想罐子中放东西,先放的放在底下,取出的时候,最后才拿出来;栈中先进后出

堆数据结构是树状结构,从堆中拿数据就像从书架中找书

垃圾回收:基本思路就是确定变量不会再使用,就释放它的内存,这个过程是周期性的,隔一段时间回收一次

一般是函数的上下文执行完毕,函数上下文退出函数调用栈,解除变量引用,内存释放,等待垃圾回收;全局的上下文是再退出的时候才被销毁,所以我们尽量减少全局变量,也要尽量减少闭包

变量声明

var let const关键字都可以声明变量

比较三者

1》变量提升不同;var 存在变量提升,let、const没有

      console.log(num); // undefined
      var num = 1;
      console.log(num); // ReferenceError: Cannot access 'num' before initialization
      let num = 1;

let 在声明前使用变量会报错,也就是我们说的暂时性死区

2》作用域不同;var 声明的变量作用域在最近的上下文中(全局或函数的上下文),而let是块级作用域

var定义的age实在全局作用域中

      if (true) {
        var age = 20;
      }
      console.log(age);  // 20

if形成了块级作用域,全局作用中域访问不到就报错了

      if (true) {
        let age = 20;
      }
      console.log(age);  // ncaught ReferenceError: age is not defined

3》声明同名变量不同;var 可以在同一作用域下声明同名变量,而let不行,会报错

      var str = "hihi";
      var str = "嗨嗨";
      console.log(str); //嗨嗨
      let str = "hihi";
      let str = "嗨嗨"; // Uncaught SyntaxError: Identifier 'str' has already been declared
      console.log(str); // 嗨嗨

4》在全局上下文中声明变量,var声明的变量会添加到window对象中(如果没有使用var来声明,直接赋值也会添加到window对象上,不会报错),而let声明的变量不会被添加到window对象中

      var num1 = 20;
      let num2 = 22;
      console.log(window.num1);  // 20
      console.log(window.num2);  // undefined

const和let差不多,唯一不同的时它声明的是个常量,声明时必须赋值,声明之后,就不能重新赋值(引用类型只要不改变引用地址就行)

变量提升

一个上下文创建的时候会创建变量对象,创建变量对象的过程:

1》建立argument对象,他是一个伪数组,属性名是:0,1,……,属性值就是传入的值

2》函数提升;在变量对象中创建一个变量名为函数名,值为指向函数内存地址的引用

3》变量提升;在变量对象中以变量名建立一个属性,属性值为undefined;但是let/const声明的变量没有赋值undefined,所以不能提前使用

      console.log(num);
      var num = 0;

相当于:

var num
console.log(num)
num=0

函数的提升先于变量的提升,首先函数的声明提升,然后变量提升

如果 var 变量与函数同名,则在这个阶段,以函数值为准,在下一个阶段,函数值会被变量值覆盖

      console.log(cal); // cal函数
      var cal = 10;
      function cal(num1, num2) {
        return num1 - num2;
      }
      console.log(cal); // 10

相当于:

 function cal() {
    return num1 - num2;
  };
  var cal;
  console.log(cal);
  cal = 10;
  console.log(cal);

预编译

在上下文创建以后, 并不会立即执行JS代码, 而是会先进行一个预编译的过程, 根据上下文的不同, 预编译又可以分为:

  • 全局预编译
  • 函数预编译每个执行上下文都有一个与之相关联的变量对象 (Variable Object, 简称 VO, 初其实就是一个对象:{key : value}形式) , 当前执行上下文中所有的变量函数都添加在其中

预编译大致过程:

1》function关键字声明的函数进行提升,声明的函数就是函数本身,但是不会执行

2》var关键字声明变量会进行提升, 属性值置为 undefined

3》如果函数名与变量名冲突(相同), 函数声明会将变量声明覆盖, 属性值就是函数本身

4》预编译结束以后, 再逐行执行代码

  console.log(logA); // function logA(a){log(a)}
  function logA(a) {
    console.log(a);
  }
  var logA = 2;
  logA(logA); // 报错:logA is not a function

执行上下文与作用域链

执行上下文

执行上下文就是当前代码的执行环境;

上下文有全局上下文和函数上下文,函数上下文是在函数被调用的时候产生的;

  var globalValue = "shake you body";
  function greet() {
    alert("hello");
  }
  greet();

JavaScript引擎会以栈的方式来处理上下文,这个栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文

首先全局上下文进栈,greet被调用时,产生一个函数上下文,进入栈中

上下文放在栈中,栈中的先进后出,就像往一个罐子中放东西,拿出来的时候,先拿出的是罐子顶部的东西,也就是后放进去的东西,那么首先进栈的全局上下文是在栈底,最先进入,但是最后出来;函数上下文在函数被调用时产生,放入栈中,执行完后退出来;而全局上下文在程序退出前出栈

函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈

同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待

全局上下文只有唯一的一个,它在浏览器关闭时出栈

函数的执行上下文的个数没有限制

每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此

上下文的生命周期:

1》创建阶段

这个阶段会创建变量对象、确定this指向,作用域链等

2》执行阶段

完成变量的赋值,执行其他js代码等

上下文在执行阶段,该上下文的变量对象VO就变为了活动对象AO

3》销毁阶段

执行上下文出栈,对应的引用失去内存空间,等待回收

变量对象

变量对象创建过程:

1》创建arguments 对象,检查当前上下文的参数,建立对应属性与属性值

2》检查当前上下文的函数声明,在变量对象中以函数名建立一个属性

3》检查当前上下文的变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined,const/let 声明的变量没有赋值,不能提前使用

如果 var 变量与函数同名,则在这个阶段,以函数值为准,在下一个阶段,函数值会被变量值覆盖

这就解释了变量提升,以及变量提升中function声明比var声明优先级高一些

作用域

作用域就是一套规则,它规定了一个变量可以使用的范围

可分为:

1》全局作用域

2》函数作用域

3》块级作用域

作用域链

作用域链,是由当前环境与上层环境的一系列变量对象组成,保证对执行环境有权访问的所有变量和函数的有序访

上下文创建的时候会创建变量对象arguments,并确定变量对象的作用域链

在一个执行上下文中,首先会在当前执行上下文的变量对象中查找,没有找到会往一层上下文中的变量对象上找;这种(单向)链式查找的机制被称为作用域链

var a = 20;
function test() {
  var b = a + 10;
  function innerTest() {
    var c = 10;
    return b + c;
  }
  return innerTest();
}
test();

在这里插入图片描述

下面代码的作用域:

content greet(全局变量对象) ----- content response (greet函数变量对象))----- str(response函数变量对象)

response变量对象中没有找到,它就往greet变量对象中找,如果greet函数的变量对象中也没有,就从全局变量对象中找

      console.log(content); // hello
      function greet() {
        var content = "hi";
        console.log(content); // hi
        function response() {
          var str = "你好";
          console.log(content); //hi
        }
        response();
      }
      greet();

首先全局上下文进栈,greet被调用时,产生一个函数上下文,进入栈中

上下文放在栈中,栈中的先进后出,就像往一个罐子中放东西,拿出来的时候,先拿出的是罐子顶部的东西,也就是后放进去的东西,那么首先进栈的全局上下文是在栈底,最先进入,但是最后出来;函数上下文在函数被调用时产生,放入栈中,执行完后退出来;而全局上下文在程序退出前出栈

上下文在其所有代码都执行完毕后被销毁

es6增加了块级作用域的概念:由最近的一堆{}花括号界定;if块、while块、function块

深拷贝与浅拷贝

浅拷贝

浅拷贝和深拷贝的主要区别在于是否完全独立于原始对象

浅拷贝:对于对象上的每一个属性,如果是简单类型,直接赋值值,如果是引用类型,赋值的是引用地址

      const obj1 = {
        name: "马冬梅",
        more: {
          boyfriend: "unknow",
        },
      };
      const obj2 = { ...obj1 };
      obj1.name = "马什么梅";
      obj1.more.boyfriend = "none";

修改后两个对象的name不一致,boyfriend一致

实现方式

1>扩展运算符…

      const arr = [1, 2, 3];
      const copyArr = [...arr];

2>Object.assign()

      const obj1 = {
        name: "马冬梅",
        more: {
          boyfriend: "unknow",
        },
      };
      const obj2 = Object.assign({}, obj1);
      const arr = [1, { name: "hello" }, 3];
      const copyArr = Object.assign([], arr);
      arr[1] = 6; // 两者索引为1的值不一样

3>Array.prototype.concat()

      const arr = [1, { name: "hello" }, 3];
      copyArr = [].concat(arr);

4>Array.prototype.slice()

      const arr = [1, { name: "hello" }, 3];
      copyArr = arr.slice();

拷贝

创建一个全新的对象,新对象与原始对象具有不同的内存地址

两者相互独立,互不影响

      let obj1 = { name: "马什么梅" };
      let obj2 = obj1;
      console.log(obj2); // { name: "马什么梅" }
      obj2.name = "马冬梅";
      console.log(obj1.name, obj2.name); // 马冬梅 马冬梅

实现方式

1>JSON

   JSON.parse(JSON.stringify(obj))

JSON使用方便,但是它也有一些坑

当对象中有undefined类型或function类型的数据时 — undefined和function会直接丢失

      const object1 = {
        name: undefined,
        fn: (v) => v,
      };
      const object2 = JSON.parse(JSON.stringify(object1));

当对象中有时间类型的元素时候 -----时间类型会被变成字符串类型数据

      const object1 = {
        date: new Date(),
      };
      const object2 = JSON.parse(JSON.stringify(object1));
      console.log(typeof object1.date === typeof object2.date); // false,'object'!=='string'

当对象中有NaN、Infinity和-Infinity这三种值的时候 — 会变成null

      const object1 = {
        isNum: NaN,
      };
      const object2 = JSON.parse(JSON.stringify(object1));
      console.log(object2); // { isNum: null }

当对象循环引用的时候 --会报错

      const obj = {
        objChild: null,
      };
      obj.objChild = obj;
      const objCopy = JSON.parse(JSON.stringify(obj));
      console.log(objCopy, "objCopy"); // 报错 Converting circular structure to JSON

2>递归

const cloneDeep1 = (target, hash = new WeakMap()) => {
  // 对于传入参数处理
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  // 哈希表中存在直接返回
  if (hash.has(target)) return hash.get(target);

  const cloneTarget = Array.isArray(target) ? [] : {};
  hash.set(target, cloneTarget);

  // 针对Symbol属性
  const symKeys = Object.getOwnPropertySymbols(target);
  if (symKeys.length) {
    symKeys.forEach(symKey => {
      if (typeof target[symKey] === 'object' && target[symKey] !== null) {
        cloneTarget[symKey] = cloneDeep1(target[symKey]);
      } else {
        cloneTarget[symKey] = target[symKey];
      }
    })
  }

  for (const i in target) {
    if (Object.prototype.hasOwnProperty.call(target, i)) {
      cloneTarget[i] =
        typeof target[i] === 'object' && target[i] !== null
        ? cloneDeep1(target[i], hash)
        : target[i];
    }
  }
  return cloneTarget;
}

简单思路

function deepCopy(obj){
    //判断是否是简单数据类型,
    if(typeof obj == "object"){
        //复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for(let i in obj){
            result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
        }
    }else {
        //简单数据类型 直接 == 赋值
        var result = obj;
    }
    return result;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2053620.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

S7协议转HTTP协议

如下来源成都纵横智控-https://www.iotrouter.com/ 需求概述 本章要实现一个流程:EG8200采集西门子S7-200Smart的数据,并组装成JSON格式通过HTTP上报应用平台。 要采集的PLC点位表如下: PLC S7-200 Smart IP 192.168.0.34/102 点表(DB1…

C++第十一弹 -- STL之List的剖析与使用

文章索引 前言1. list的介绍2 list的使用2.1 list的构造函数2.2 iterator的使用2.3 list capacity2.4 list element access2.5 list modifiers 3. list的迭代器失效4. list与vector的对比总结 前言 本篇我们旨在探讨对于STL中list的使用, 下一篇我们将会对list进行底层剖析以及…

目录操作(2)(21)

1.getpwuid struct passwd *getpwuid(uid_t uid); 功能: 根据用户id到ks文件下解析获得 结构体信息 参数: uid:用户id 返回值: 成功返回id对应用户的信息 失败返回NULL eg:接受返回值struct passwd * pw getpwuid(uid); struct passwd {char *pw_name; …

Servlet---axios框架 ▎路由守卫

前言 在现代Web应用中,前端和后端通常分离,前端使用框架(如Vue.js、React)与后端服务交互。Servlet是Java EE中处理HTTP请求的重要组成部分,能够生成动态Web内容。 Axios是一个基于Promise的HTTP客户端,简…

【layUI】只能选某个特定区间的日历

要实现的功能如下&#xff1a;业务要求让日历只有近3天可选&#xff0c;其它部分变灰且不可选。 代码实现 在html中加入如下代码&#xff1a; <label class"layui-form-label" style"">日期&#xff1a; </label> <div class"layu…

二、前后端分离通用权限系统(2)

&#x1f33b;&#x1f33b; 目录 一、 Mybatis-Plus 复习1.1、简介1.2、特点1.3、支持数据库1.4、在工程中引入依赖 二、Mybatis-Plus 入门2.1、导入配置文件2.2、导入启动类2.3、实体类2.4、创建 Mapper 类2.5、创建测试 Mapper接口2.6、CRUD 测试2.6.1、insert 添加2.6.2、主…

flink环境搭建

Flink会话模式 1.集群规划&#xff1a; 2. 将flink拖到/opt/so下 3. 将安装包解压到/opt/module下&#xff1a; tar -zxvf /opt/so/flink-1.15.4-bin-scala_2.12.tgz -C /opt/module 4. 改个名&#xff1a;mv flink-1.15.4 flink 5. 修改配置文件&#xff1a;cd /opt/mo…

CPU 绑核

随笔记录 目录 1. 背景介绍 2. 查询设备CPU 中断核 2.1 查询设备名 2.2 查询设备CPU 中断核 2.2.1 查询本服务上所有设备 CPU 中断核Number 2.2.2 查询 每个设备cpu 中断核的 3. 确定可绑定CPU 核 3.1 查询cpu 信息 3.2 绑核 3.3 更新group 3.4 重启后查看 4. 绑核…

9 算术、关系、逻辑、赋值、位操作、三元运算符及其优先级

目录​​​​​​​ 1 运算符基础 1.1 什么是运算符 1.2 什么是表达式 1.3 左操作数和右操作数 1.4 运算符分类 1.4.1 按照操作数个数分类 1.4.2 按照功能分类 1.5 如何掌握运算符 2 算术运算符 2.1 正号和负号 2.2 加、减、乘、除 2.3 取模&#xff08;取余&#…

Java八股整合(MySQL+Redis+Maven)

MySQL 数据库设计三范式 不可再分&#xff0c;部分依赖&#xff0c;传递依赖 主键和外键区别 主键非空约束&#xff0c;唯一性约束&#xff0c;唯一标识一个字段 外键用于和其他表建立连接&#xff0c;是另一张表的主键&#xff0c;可重复可为空可以有多个 为什么不推荐使…

记录一次生产jvm问题的排查

记录一次生产问题的排查 第一天晚上 现象 1、前援反馈页面有接口陆续出现请求超时 2、登录后台服务器top命令查看发现java进程发生高cpu占用情况 3、查看对应业务日志&#xff0c;报数据库连接等待超时-数据库连接池连接无空闲 对应处理 1、临时调大数据库连接池最大连接数限…

如何发布自己的NPM包详细步骤

前言 在前端开发中&#xff0c;将自己编写的 Vue 组件或插件打包并发布到 NPM 上&#xff0c;不仅可以方便自己在其他项目中复用&#xff0c;还能分享给更多的开发者使用。本文将从 NPM 注册、登录与发布流程&#xff0c;及如何通过 Vue CLI 打包插件的角度详细介绍如何发布 V…

C#线程的使用

每个正在操作系统上运行的应用程序都是一个进程&#xff0c;一个进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元&#xff0c;在进程中可以有多个线程同时执行代码。 1、单线程 单线程就是只有一个线程。默认情况下&#xff0c;系统为应用程序分配一个主…

论团体标准的有效期

在当今快速发展的社会中&#xff0c;标准对于规范行业秩序、保障产品和服务质量起着至关重要的作用。其中&#xff0c;团体标准作为标准体系的重要组成部分&#xff0c;以其灵活性和专业性受到了广泛的关注。而团体标准的有效期&#xff0c;则是一个值得深入探讨的重要议题。 团…

2024年最新上榜的文件加密管理软件

文件加密市场风起云涌&#xff0c;后辈迭出&#xff0c;2024年安企神软件在文件加密管理软件市场中备受瞩目&#xff0c;凭借其强大的功能和全面的保护策略&#xff0c;成功上榜并受到广泛认可。以下是对它的详细介绍&#xff1a; 一、产品概述 安企神软件不仅是一款电脑监控…

“软件定义汽车”下的软件虚拟化技术

01.虚拟化技术概述 近年来&#xff0c;随着嵌入式软硬件的高速发展&#xff0c;嵌入式系统产品已融入日常生活的方方面面&#xff0c;在航空航天、车载电子、工业控制等要求更为严苛等领域的应用也更加广泛。特别对汽车领域&#xff0c;每辆车内ECU的使用数量已从21世纪初的30…

定时任务调度`crond` 和 `at` 命令使用

&#x1f600;前言 本篇博文是关于 linux实操篇-定时任务调度crond 和 at 命令&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满…

【心酸报错】ImportError: failed to find libmagic. Check your installation

目录 报错信息&#xff1a;ImportError: failed to find libmagic. Check your installation按照网络上找的办法修改还是报错&#xff1a;LookupError:Resource punkt not found.下载nltk_data又报错&#xff1a;AttributeError: tuple object has no attribute page_content怀…

软件工程概述(下)

4、软件工程原理 &#xff08;1&#xff09;什么是软件工程&#xff1f; 软件工程是指导计算机软件开发和维护的一门学科。 采用工程的概念、原理、技术和方法来开发与维护软件&#xff0c;把经过时间考验而证明正确的管理技术和当前能够得到的最好的技术方法结合起来&#xf…

【Qt】常用控件QCheckBox

常用控件QCheckBox QCheckBox表示复选按钮&#xff0c;可以允许选中多个。 QCheckBox继承自QAbstractButton 例子&#xff1a;获取复选按钮的取值 使用Qt Designer先大体进行设计 代码实现&#xff1a; #include "widget.h" #include "ui_widget.h"Widge…