1.变量的类型和计算
类型
数据类型
- 基本数据类型(值类型): Number、String、Boolean、Undefined、Null、Symbol(es6新增独一无二的值) 和 BigInt(es10新增);
- 引用数据类型: Object。包含Object、Array、 function、Date、RegExp。
栈堆存储
-
值类型栈存储: 主要针对(Number、String、Boolean)三种数据。直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
-
引用类型堆栈存储: 主要针对Object、Array这两种引用数据以及null, 同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
备注:
引用类型存储把值存储在堆内存中,堆内存是从下往上存储。生成唯一内存地址。然后在栈内存中把地址赋值给变量。栈内存是从上往下存储的。之所以如此划分内存主要考虑到特别大的对象进行值传递时的效率问题。
常见的值类型:undefined、number、string、boolean、symbol
常见的引用类型:object、array
特殊的引用类型:null、function
typeof运算符
typeof a // undefined
typeof '' // string
typeof 100 // number
typeof true // boolean
typeof Symbol('s') // symbol
typeof function(){} //'function'
typeof null // object
typeof {} // object
typeof [1,2] // object
typeof 能有效检测基本类型,检测引用类型都返回object,其中null属于特殊的引用类型返回object,function属于特殊引用类型类型不用于存储数据,typeof检测返回function.
计算-类型转换
- 字符串拼接
+运算中有字符串,结果就是字符串拼接。
2.==
与===
===
三等表示全等,判断左右两边对象或值是否类型相同且值相等。
==
二等表示值相等。判断操作符两边对象或值是否相等类型可以不同,类型不同时,使用Number()转换成Number类型在进行判断。例外规则,null/undefined进行运算时不进行隐式类型转换。通常把值转为Boolean值,进行条件判断。Boolean(null)===Boolean(undefined)>false===false
结果为true
- if语句和逻辑运算
所有基本类型中Boolean值是false的只有6个,分别是 : 0 NaN ’ ’ null undefined false
引用类型Boolean值全是true.
- if语句中的判断
if条件是单个值时,如果是truly值,条件成立, 如果是falsely值,条件不成立- 逻辑判断:
逻辑与(&&)运算规则: 左侧为truly返回右侧,左侧为fasely返回左侧。
逻辑或( | | )运算规则: 左侧为truley返回左侧,左侧为fasely返回右侧。
类型判断
*四种方法
typeof、instanceof、constructor、Object.prototype.toString.call()
- typeof
基本数据类型中:Number,String,Boolean,undefined 以及引用数据类型中Function ,可以使用typeof检测数据类型,分别返回对应的数据类型小写字符。
另:用typeof检测构造函数创建的Number,String,Boolean都返回object
基本数据类型中:null 。引用数据类型中的:Array,Object,Date,RegExp。不可以用typeof检测。都会返回小写的object
console.log(
typeof 100, //"number"
typeof 'abc', //"string"
typeof false, //"boolean"
typeof undefined, //"undefined"
typeof null, //"object"
typeof [1,2,3], //"object"
typeof {a:1,b:2,c:3}, //"object"
typeof function(){console.log('aaa');}, //"function"
typeof new Date(), //"object"
typeof /^[a-zA-Z]{5,20}$/, //"object"
typeof new Error() //"object"
typeof new Number(100), //'object'
typeof new String('abc'),// 'string'
typeof new Boolean(true),//'boolean'
);
- instanceof
除了使用typeof来判断,还可以使用instanceof。instanceof运算符需要指定一个构造函数,或者说指定一个特定的类型,它用来判断这个构造函数的原型是否在给定对象的原型链上。
console.log(
100 instanceof Number, //false
'dsfsf' instanceof String, //false
false instanceof Boolean, //false
undefined instanceof Object, //false
null instanceof Object, //false
[1,2,3] instanceof Array, //true
{a:1,b:2,c:3} instanceof Object, //true
function(){console.log('aaa');} instanceof Function, //true
new Date() instanceof Date, //true
/^[a-zA-Z]{5,20}$/ instanceof RegExp, //true
new Error() instanceof Error //true
)
还需要注意null和undefined都返回了false,这是因为它们的类型就是自己本身,并不是Object创建出来它们,所以返回了false。
- constructor
constructor是prototype对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的。
如果输出一个类型的实例的constructor
console.log(new Number(123).constructor)
//ƒ Number() { [native code] }
可以看到它指向了Number的构造函数,因此,可以使用num.constructor==Number来判断一个变量是不是Number类型的。
var num = 123;
var str = 'abcdef';
var bool = true;
var arr = [1, 2, 3, 4];
var json = {name:'wenzi', age:25};
var func = function(){ console.log('this is function'); }
var und = undefined;
var nul = null;
var date = new Date();
var reg = /^[a-zA-Z]{5,20}$/;
var error= new Error();
function Person(){
}
var tom = new Person();
// undefined和null没有constructor属性
console.log(
tom.constructor==Person,
num.constructor==Number,
str.constructor==String,
bool.constructor==Boolean,
arr.constructor==Array,
json.constructor==Object,
func.constructor==Function,
date.constructor==Date,
reg.constructor==RegExp,
error.constructor==Error
);
//所有结果均为true
除了undefined和null之外,其他类型都可以通过constructor属性来判断类型。
- Object.prototype.toString.call()
可以通过toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为thisArg。
var toString = Object.prototype.toString;
toString.call(123); //"[object Number]"
toString.call('abcdef'); //"[object String]"
toString.call(true); //"[object Boolean]"
toString.call([1, 2, 3, 4]); //"[object Array]"
toString.call({name:'wenzi', age:25}); //"[object Object]"
toString.call(function(){ console.log('this is function'); }); //"[object Function]"
toString.call(undefined); //"[object Undefined]"
toString.call(null); //"[object Null]"
toString.call(new Date()); //"[object Date]"
toString.call(/^[a-zA-Z]{5,20}$/); //"[object RegExp]"
toString.call(new Error()); //"[object Error]"
这样可以看到使用Object.prototype.toString.call()的方式来判断一个变量的类型是最准确的方法。
浅拷贝与深拷贝
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
赋值和浅拷贝的区别
赋值:
把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
浅拷贝:
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
手写深拷贝(递归):
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
2.原型与原型链
原型
原型的关系
- 每个函数都有一个prototype属性,被称为显示原型
- 每个实例对象都会有_ proto _属性,其被称为隐式原型
- 每一个实例对象的隐式原型_ proto _属性指向自身构造函数的显式原型prototype
- 每个prototype原型都有一个constructor属性,指向它关联的构造函数。
原型链
获取对象属性时,如果对象本身没有这个属性,那就会去他的原型__proto__上去找,如果还查不到,就去找原型的原型,一直找到最顶层(Object.prototype)为止。Object.prototype对象也有__proto__属性值为null。
3.作用域与闭包
作用域
变量作用域:就是一个变量可以使用的范围。
上图,每一个红框代表一个作用域
作用域分为:
全局作用域
函数作用域
块级作用域(ES6 新增)
- js中首先有一个最外层的作用域,全局作用域;
- js中可以通过函数来创建一个独立作用域称为函数作用域,函数可以嵌套,所以作用域也可以嵌套;
- es6中新增了块级作用域(大括号,比如:if{},for(){},while(){}…);
// 块级作用域 let const
if(true){
let x=100
}
console.log(x) //会报错
自由变量
概念: 当前作用域没有定义的变量
1.一个变量在当前作用域没有定义,但被使用了。
2.向上级作用域,一层一层查找,直到找到为止。
3.如果到全局作用域都没有找到,就会报错。
自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方。
作用域链
自由变量的向上级作用域一层一层查找,直到找到为止,最高找到全局作用域,就形成了作用域链。
变量提升 (预解析)
var声明的变量,function声明的函数存在变量提升 (函数比var优先级高)
let const 不会变量提升
闭包
1.函数作为返回值被返回
function create() {
let a = 100
return function () {
console.log(a)
}
}
let fn = create()
let a = 200
fn() //100
2.函数作为参数被传递
function print(fn) {
let a = 200
fn()
}
let a = 100
function fn() {
console.log(a)
}
print(fn) //100
垃圾回收机制
浏览器的 Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大并且GC时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
只有函数内的变量才可能被回收
垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。通常情况下有两种实现方式:标记清除和引用计数。引用计数不太常用,标记清除较为常用。
- 标记清除
js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
- 引用计数
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为 0 的值所占用的内存。
this 指向
使用场景:
1.普通函数 ------- this指向调用者
2. call apply bind ------- 改变this的指向
3.对象方法 ------- this指向调用的对象
4.class ------- this指向调用它的构造函数
5.箭头函数 ------- this始终指向它父级的上下文
this取什么值是函数调用时确认的,不是在函数定义时确认的。
function fn1() {
console.log(this)
}
fn1() // x:100
fn1.call({ x: 100})
const fn2 = fn1.bind({ x: 200})
fn2() // x:200
4.异步和单线宏任务微任务
宏任务和微任务
宏任务包括:setTimeout setInterval Ajax DOM事件
微任务:Promise async/await
微任务比宏任务的执行时间要早
同步
console.log(100)
alert(200)
console.log(300)
// 100 200 300
异步
console.log(100)
setTimeout(()=>{
console.log(200)
},1000)
console.log(300)
// 100 300 200
两者的区别:
异步不会阻塞代码执行
同步会阻塞代码执行
使用场景:
网络请求(如:ajax请求)、定时任务(如:setTimeout)。
setTimeout:一次性定时器,只执行一次。
setInterval:间接性定时器,可执行多次。