乐趣区

【芝士整理】JS基础图谱

JS 有哪些基本数据类型呢?
值类型:undefined, Number, Boolean, String,null
引用类型:Object
值类型存放在栈中
引用类型将地址存放在栈中,将数据实体存放在堆中
null 和 undefined,not defined 的区别?
not defined 是未声明,当使用未声明变量时浏览器会抛出这个错误
undefined 是已声明未赋值,没有定义,typeof undefined 是 undefined
null 类似于空对象,是一个已定义,定义为空的值,typeof null 是 object
如何判断数据类型?
如果是值类型,直接用 typeof 判断
如果是引用类型,使用 instanceof 判断,instanceof 基于原型链,一般用于判断自定义对象
constructor 是 prototype 上的一个属性,他容易被重写覆盖,所以不可信赖
Object.prototype.toString.call,调用 Object 原型上的 toString 方法可以得到当前调用者的具体类型
什么是原型链?
每一个函数上都有一个 prototype 属性,称为原型对象
函数实例化产生对象
每一个对象都有一个__proto__(隐匿原型)属性,指向构造它的原型对象。
原型对象本身也是对象,也有一个隐匿原型,指向它的原型对象。
沿着隐匿原型链最终会指向 Object.prototype,它的原型对象是 null
这就构成一个原型链

PS. 将原子类型赋给 prototype 的操作将会被忽略
function Foo() {}
Foo.prototype = 1; // 无效

instanceof 的原理
语法是 A instanceof B
A 的原型链是否会到达 B.prototype
继承
通过原型链实现继承,原型对象上可以定义属性和方法。
当要在一个对象上寻找某个属性,现在对象本身找,没有的话,再沿着原型链向上找原型对象里有没有,向上查找找到为之,到达顶部仍未找到,返回 undefined
PS. 判断对象上是否有某个属性,而非其原型链上有,使用 hasOwnProperty 函数
执行上下文
在函数调用时或者是全局代码开始运行时产生,处理的事情:变量声明,函数声明,函数声明形式的定义赋值,定义 this,在函数内还有定义 arguments 的操作

PS. arguments 变量不是一个数组(Array)。尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个对象(Object)。
因此,无法对 arguments 变量使用标准的数组方法,比如 push, pop 或者 slice。虽然使用 for 循环遍历也是可以的,但是为了更好的使用数组方法,最好把它转化为一个真正的数组。
Array.prototype.slice.call(arguments);

执行上下文栈
全局代码开始执行时,产生一个全局的执行上下文,压栈
代码执行到函数 A 调用时,产生一个函数 A 的执行上下文,压栈
函数 A 中调用函数 B,产生一个函数 B 的执行上下文,压栈
函数 B,执行完毕,出栈销毁执行上下文
函数 A,执行完毕,出栈并销毁执行上下文
关于 this 的指向
this 存在于执行上下文中

作为构造函数调用时,指向实例化的对象
作为对象属性调用时,指向对象
call, apply 调用时,指向参数指定上下文
全局和普通函数都指向 windows

PS. 一些误解
// 1. 严格按照规范
Foo.method = function() {
// 在这,this 是 Foo 的实例化对象
function test() {
// this 将会被设置为全局对象(译者注:浏览器环境中也就是 window 对象)
}
test();
}

// 2. 函数别名
var test = someObject.methodTest;
test(); // this 设置为全局对象

// 3.

PS. apply 和 call 的用法 function.apply(null, arguments);
function.call(null, arg1, arg2)

作用域
ES5 中只有函数作用域的概念,作用域是一个虚拟概念,没有具体的数据类型或者结构。
一个函数的作用域在函数定义时确定,创建函数的作用域成为该函数的上级作用域
在函数中寻找变量,先找到函数作用域对应的执行上下文,在执行上下文中找变量。
没有找到的话,看上级函数作用域,向上查找到,找到为止。
如果找不到,则会抛出 ReferenceError 异常。

PS. 比如,当访问函数内的 foo 变量时,JavaScript 会按照下面顺序查找:

当前作用域内是否有 var foo 的定义。
函数形式参数是否有使用 foo 名称的。
函数自身是否叫做 foo。
回溯到上一级作用域,然后从 #1 重新开始。

闭包
什么是闭包?一个函数中有依赖外部变量,函数在创建它的作用域之外被调用。将会在执行上下文栈中保留上级作用域的执行上下文。若在闭包使用完毕之后不手动解除引用,相关执行上下文将会一直保留于执行上下文栈中,占据内存空间,若持续积累,容易造成内存泄漏。
常见应用,函数作为返回值,函数作为参数。
经典问题
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
} // 5,5,5,5,5

for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
} // 0,1,2,3,4

for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(function() {
console.log(i);
}, 1000);
})(i)
} // 0, 1, 2, 3, 4

for(var i = 0; i < 5; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
} // 0, 1, 2, 3, 4
JS 中实现继承的方法

基础继承
var Bar = function () {};
Bar.prototype = {
greet: function (name) {
console.log(name);
}
}
var Foo = function () {}
Foo.prototype.__proto__ = Bar.prototype;
等价于
var Bar = function () {};
var Foo = function () {}
Foo.prototype = new Bar();
原理:实现原型链
缺点:属性不独立

组合继承
var Bar = function (name) {
this.name = name;
}
Bar.prototype = {
greet: function () {
console.log(this.name);
}
}
var Foo = function (name) {
Bar.apply(this, arguments);
}
Foo.prototype = new Bar();
原理:把 this 属性赋值在子类的作用域执行一次,方法通过原型链继承

寄生组合式继承
var Bar = function (name) {
this.name = name;
}
Bar.prototype = {
greet: function () {
console.log(this.name);
}
}
var Foo = function (name) {
Bar.apply(this, arguments);
}
Foo.prototype = Object.create(Bar.prototype);
Foo.prototype.constructor = Foo;

extends 方法
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString () {
return this.x + ‘ ‘ + this.y;
}
}

class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的 constructor(x, y)
this.color = color;
}

toString() {
return this.color + ‘ ‘ + super.toString(); // 调用父类的 toString()
}
}
子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。

PS. new 运算符做了什么
// 1. 首先创建一个空对象
var o = new Object();
// 2. 将空对象的原型赋值为构造器函数的原型
o.__proto__ = A.prototype;
// 3. 更改构造器函数内部 this,将其指向新创建的空对象

类型转换
强制转换

转为字符串::.toString(), String()

转为数值:Number 针对所有类型,parseInt 和 parseFloat 针对字符串
字符串转换为数字的常用方法:
+’010’ === 10
Number(‘010’) === 10
parseInt(‘010′, 10) === 10 // 用来转换为整数

+’010.2’ === 10.2
Number(‘010.2’) === 10.2
parseInt(‘010.2’, 10) === 10
parseFloat(‘10.1.2′) === 10.1 // 字符转换为浮点数

Number(undefined) // NaN
Number 严格转换,只要有一个字符无法转为数值输出 NaN
parseInt 原理为从左往右读字符串,读到非数值字符为止
parseFloat 原理为从左往右读字符串,读到第二个小数点或者非数值非小数点字符为止

转为布尔值:Boolean(),””、null、undefined、+0、-0 和 NaN 转为布尔型是 false,其他的都是 true

自动转换

转为字符串:包含字符串的 + 法运算,’5′ + 1 === ’51’

转为数值:不包含字符串的算术运算
转为布尔值:条件语句判断,非运算

事件轮询机制
主线程运行时产生堆和执行栈
主线程之外,还存在一个 ” 任务队列 ”。只要异步任务有了运行结果,就在 ” 任务队列 ” 之中放置一个事件。
一旦 ” 执行栈 ” 中的所有同步任务执行完毕,系统就会读取 ” 任务队列 ”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
任务队列
任务队列分为宏任务队列和微任务队列

宏任务包括:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering。
微任务包括: new Promise().then( 回调), process.nextTick, Object.observe(已废弃), MutationObserver(html5 新特性)

执行同步任务 -> 处理微任务队列 -> 处理宏任务队列里队首任务 -> 处理微任务队列
Promise.resolve().then(()=>{
console.log(‘Promise1’)
setTimeout(()=>{
console.log(‘setTimeout1’)
},0)
})
setTimeout(()=>{
console.log(‘setTimeout2’)
Promise.resolve().then(()=>{
console.log(‘Promise2’)
})
Promise.resolve().then(()=>{
console.log(‘Promise3’)
})
},0)
setTimeout(()=>{
console.log(‘setTimeout4’)
Promise.resolve().then(()=>{
console.log(‘Promise4’)
})
},0)

Output: Promise1 setTimout2 Promise2 Promise3 setTimeout4 Promise4 setTimeout1
setTimeout 和 setInterval 的区别
setTimeout:产生一个宏任务,在指定时间之后加入任务队列。
setInterval:循环产生宏任务,但存在问题,若任务执行时间长于指定时间间隔,任务会产生堆叠执行效果。

退出移动版