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

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

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值