值类型:undefined, Number, Boolean, String,nullhtml
引用类型:Objecthtml5
值类型存放在栈中windows
引用类型将地址存放在栈中,将数据实体存放在堆中数组
not defined是未声明,当使用未声明变量时浏览器会抛出这个错误浏览器
undefined是已声明未赋值,typeof undefined是undefined闭包
null相似于空对象,是一个已定义,定义为空的值,typeof null 是 objectapp
若是是值类型,直接用typeof判断异步
若是是引用类型,使用instanceof判断,instanceof基于原型链,通常用于判断自定义对象函数
constructor是prototype上的一个属性,他容易被重写覆盖,因此不可信赖this
Object.prototype.toString.call,调用Object原型上的toString方法能够获得当前调用者的具体类型
Object.prototype.toString.call().slice(8, -1); // Object|Array|Number|String|Boolean...
每个函数上都有一个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存在于执行上下文中
PS. 一些误解
// 1. 严格按照规范 Foo.method = function() { // 在这,this是Foo的实例化对象 function test() { // this 将会被设置为全局对象 } test(); } // 2. 函数别名 var test = someObject.methodTest; test(); // this设置为全局对象
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
基础继承
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属性赋值在子类的做用域执行一次,方法经过原型链继承
缺点: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;
原理: 把this属性赋值在子类的做用域执行一次,手动链接原型对象的拷贝
优势:解决组合继承的缺点
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,将其指向新建立的空对象 A.call(o);
转为数值: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原理为从左往右读字符串,读到第二个小数点或者非数值非小数点字符为止
'5' + 1 === '51'
主线程运行时产生堆和执行栈
主线程以外,还存在一个"任务队列"。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
一旦"执行栈"中的全部同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。对应的异步任务,结束等待状态,进入执行栈,开始执行
任务队列分为宏任务队列和微任务队列
执行同步任务 -> 处理微任务队列 -> 处理宏任务队列里队首任务 -> 处理微任务队列
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:循环产生宏任务,但存在问题,若任务执行时间长于指定时间间隔,会产生堆叠执行效果。