关键词: JS
- 类型及检测方式
- This
- apply、bind call原理及实现
- 变量提升
- 执行上下文
- 作用域
- 闭包
JS基础
类型及检测方式
分为基础类型与引用类型
两种类型在引用的时候有些许区别。总的来说是地址引用
引用类型因为是地址存在栈内存中,实体在堆内存中
JavaScript 中的数据是如何存储在内存中的?
在 JavaScript 的执行过程中,主要有三种类型内存空间,分别是代码空间、栈空间、堆空间
其中的代码空间主要是存储可执行代码的,JavaScript 引擎需要用栈来维护程序执行期间上下文的状态。所以需要这一部分较为精简。故而引出堆空间
数据检测类型
- typeof
typeof 对于原始类型来说,除了 null 都可以显示正确的类型
同时,typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型,所以想判断一个对象的正确类型,这时候可以考虑使用 instanceof
- instanceof
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype
- instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型;
- 而 typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了 function 类型以外,其他的也无法判断
- constructor
- Object.prototype.toString.call()
toString() 是 Object 的原型方法,调用该方法,可以统一返回格式为 “[object Xxx]” 的字符串,其中 Xxx 就是对象的类型。对于 Object 对象,直接调用 toString() 就能返回 [object Object]
实现一个全局通用的数据类型判断方法,来加深你的理解,代码如下 太长了不美观放到Github 上了
小结
-
typeof
-
- 直接在计算机底层基于数据类型的值(二进制)进行检测
-
instanceof
-
- 底层机制:只要当前类出现在实例的原型上,结果都是true
-
Object.prototype.toString.call([val])
-
- 返回当前实例所属类信息
数据类型转换
知识点插播 valueOf() 方法可返回 String 对象的原始值
- 注意: valueOf() 方法通常由 JavaScript 在后台自动进行调用,而不是显式地处于代码中。
toString() 将一种数据类型转化为字符串
'+' 运算符
- 运算中其中一方为字符串,那么就会把另一方也转换为字符串
- 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"
强制类型转换
强制类型转换方式包括 Number()、parseInt()、parseFloat()、toString()、String()、Boolean(),
null 与undefined 的区别
- 首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
- undefined 代表的含义是未定义, null 代表的含义是空对象(其实不是真的对象,请看下面的注意!)。一般变量声明了但还没有定义的时候会返回 undefined,null 主要用于赋值给一些可能会返回对象的变量,作为初始化。
其实 null 不是对象,虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
== 与 ===
用白话文来讲就是 == 代表相同 === 代表严格相同 == 会进行隐式的类型转换
This
不同情况的调用,this指向分别如何。顺带可以提一下 es6 中箭头函数没有 this, arguments, super等,这些只依赖包含箭头函数最接近的函数
箭头函数并不是没有this,没有自身的this,简而言之就是this在其定义的时候被赋予.也就是this是静态的,是由声明的时候确定的
this之前有一篇非常典型的写法就是 this指向左边的那个作用域
super
this关键词指向函数所在的当前对象 super指向的是当前对象的原型对象
apply/call/bind 原理
apply/call/bind的手写见Github
变量提升
当执行 JS 代码时,会生成执行环境,只要代码不是写在函数中的,就是在全局执行环境中,函数中的代码会产生函数执行环境,只此两种执行环境。
对于变量提升准确来讲。应该是
b() // call b
console.log(a) // undefined
var a = 'Hello world'
function b() {
console.log('call b')
}
在生成执行环境时,会有两个阶段。第一个阶段是创建的阶段,JS 解释器会找出需要提升的变量和函数,并且给他们提前在内存中开辟好空间,函数的话会将整个函数存入内存中,变量只声明并且赋值为 undefined,所以在第二个阶段,也就是代码执行阶段,我们可以直接提前使用
执行上下文
当执行 JS 代码时,会产生三种执行上下文
- 全局执行上下文
- 函数执行上下文
- eval执行上下文
每个执行上下文中都有三个重要的属性
- 变量对象(VO),包含变量、函数声明和函数的形参,该属性只能在全局上下文中访问
- 作用域链(JS 采用词法作用域,也就是说变量的作用域是在定义时就决定了)
- this
他又跟我之前学的那个东西有关系 执行栈中包含变量作用域以及词法作用域
唉!学完就忘
作用域
- 作用域
-
- 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找
- 作用域链
-
- 作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和 函数。
作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前 端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。
- 全局作用域
- 函数作用域
- 块级作用域,ES6 中的 let、const 就可以产生该作用域
作用域链
- 首先作用域链是在定义时就被确定下来的,和箭头函数里的 this 一样,后续不会改变,JS会一层层往上寻找需要的内容。
- 其实作用域链这个东西我们在闭包小结中已经看到过它的实体了:[[Scopes]]
闭包
闭包其实就是一个可以访问其他函数内部的函数。闭包最常见的一种写法就是在一个函数内容创建另一个函数。从而使得创建的函数可以访问到当前函数的局部变量
闭包产生的本质是: 当前环境中存在指向父级作用域的引用
function fun1() {
var a = 1;
return function(){
console.log(a);
};
}
fun1();
var result = fun1();
result(); // 1
闭包的两个常见用途
- 创建私有变量
- 变量对象不会被回收
如何解决循环输出的问题
for(var i = 1; i < 5; i ++){
setTimeout(function() {
console.log(i)
}, 0)
}
持续输出5的原因:
- setTimeout 为宏任务,由于JS为单线程的。所以宏任务执行完毕之后,setTimeout中的回调函数才依次执行
- setTimeout函数实际上也是闭包往上找它的父级作用域链就是 window,变量 i 为 window 上的全局变量,开始执行 setTimeout 之前变量 i 已经就是6了,因此最后输出的连续就都是 6。
解决办法:
实际上解决办法网上都能找的到。但更重要的是我们思考的过程
- 利用IIFE (立即执行函数)
原理就是每次循环完后立即执行
for(var i = 1;i <= 5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
}, 0)
})(i)
}
- ES6 let
for(let i = 1; i <= 5; i++){
setTimeout(function() {
console.log(i);
},0)
}
- 传入第三个参数
for(var i=1;i<=5;i++){
setTimeout(function(j) {
console.log(j)
}, 0, i)
}