龙岗建设网站公司,微营销的优势,网站内的地图导航怎么做,seo属于什么从数据类型到变量、作用域、执行上下文
JS数据类型
分类
1》基本类型#xff1a;字符串String、数字Number、布尔值Boolean、undefined、null、symbol、bigint
2》引用类型#xff1a;Object (Object、Array、Function、Date、RegExp、Error、Arguments)
Symbol是ES6新出…从数据类型到变量、作用域、执行上下文
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); // falselet 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); // numberconsole.log(typeof nb); // stringconsole.log(typeof true); // booleanconsole.log(typeof undefined); // undefinedconsole.log(typeof null); // objectconsole.log(typeof [1, 2]); // objectconsole.log(typeof { content: hi }); // objectfunction func(){console.log(月亮不睡我不睡);};console.log(typeof func); // function补充任何实现内部使用了call方法的对象使用typeof都返回function,比如正则表达式在Chrome7和Safari5及之前版本上述浏览器的正则表达式内部实现使用了call但是后面都是返回object了
instanceof
适用于检测引用类型的具体类型
返回值布尔值
自己定义的构造函数new 出来的实例也可以检测 console.log([1, 2] instanceof Array); //trueconsole.log({ content: hi } instanceof Object); //trueconst fn () {console.log(不是秃头小宝贝);};console.log(fn instanceof Function); //trueconsole.log([1, 2] instanceof Object); //trueconsole.log({ content: hi } instanceof Array); //falsefunction Person(name) {this.name name;}const p new Person(luka);console.log(p instanceof Person); //true// instanceof不能检测基本数据类型console.log(66 instanceof Number); //falseconsole.log(null instanceof Object); //falseObject.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指向创建该实例对象的构造函数
注意 null 和 undefined 没有 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); // undefinedvar num 1;console.log(num); // ReferenceError: Cannot access num before initializationlet num 1;let 在声明前使用变量会报错也就是我们说的暂时性死区
2》作用域不同var 声明的变量作用域在最近的上下文中全局或函数的上下文而let是块级作用域
var定义的age实在全局作用域中 if (true) {var age 20;}console.log(age); // 20if形成了块级作用域全局作用中域访问不到就报错了 if (true) {let age 20;}console.log(age); // ncaught ReferenceError: age is not defined3》声明同名变量不同var 可以在同一作用域下声明同名变量而let不行会报错 var str hihi;var str 嗨嗨;console.log(str); //嗨嗨let str hihi;let str 嗨嗨; // Uncaught SyntaxError: Identifier str has already been declaredconsole.log(str); // 嗨嗨4》在全局上下文中声明变量var声明的变量会添加到window对象中如果没有使用var来声明直接赋值也会添加到window对象上不会报错而let声明的变量不会被添加到window对象中 var num1 20;let num2 22;console.log(window.num1); // 20console.log(window.num2); // undefinedconst和let差不多唯一不同的时它声明的是个常量声明时必须赋值声明之后就不能重新赋值引用类型只要不改变引用地址就行
变量提升
一个上下文创建的时候会创建变量对象创建变量对象的过程
1》建立argument对象他是一个伪数组属性名是01……属性值就是传入的值
2》函数提升在变量对象中创建一个变量名为函数名值为指向函数内存地址的引用
3》变量提升在变量对象中以变量名建立一个属性属性值为undefined但是let/const声明的变量没有赋值undefined,所以不能提前使用 console.log(num);var num 0;相当于
var num
console.log(num)
num0函数的提升先于变量的提升首先函数的声明提升然后变量提升
如果 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》检查当前上下文的变量声明就在变量对象中以变量名建立一个属性属性值为undefinedconst/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函数变量对象)----- strresponse函数变量对象)
response变量对象中没有找到它就往greet变量对象中找如果greet函数的变量对象中也没有就从全局变量对象中找 console.log(content); // hellofunction greet() {var content hi;console.log(content); // hifunction 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];2Object.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的值不一样3Array.prototype.concat() const arr [1, { name: hello }, 3];copyArr [].concat(arr);4Array.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); // 马冬梅 马冬梅实现方式
1JSON 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 JSON2递归
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;
}