原型链
每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型”继承”属性。
可以使用Object.getPrototypeOf()
来获取某个对象的原型
继承
segmentfault.com/a/119000001…
继承方式
- 原型链继承
- 直接让子代构造函数的prototype指向父代的父代
- 无法传参,不够灵活
function Parent () {
this.name = 'kevin';
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.getName()) // kevin
复制代码
2.借用构造函数继承
- 借用了父类的构造函数,让子类被创建的时候执行父类的构造函数,使用call让该函数的this指向子类
- 方法都在构造函数中定义,每一次都会执行
Parent.call(this)
来把所有方法创建一遍
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
Parent.call(this);
}
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]
复制代码
3.组合继承
- 原型链继承和经典继承双剑合璧
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
复制代码
- 缺点:在子类实例化和修改原型链的时候会执行两次父类的构造函数
优化:把上面的Child.prototype = new Parent()换成Object.create(Person.prototype)
,这样可以少使用一次构造函数,同时可以完成继承
//......和上面一致
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
复制代码
- ES6中class的继承
懂的都懂,需要先调用super方法来调用父类的构造函数
作用域
作用域分为以下两种
- 语法(静态)作用域:函数的作用域在定义的时候就决定了
- 词法(动态)作用域:函数的作用在调用执行的时候才决定
执行上下文栈
- 初始的时候创建一个全局上下文栈,每遇到一个函数都会创建一个执行上下文压入栈中,每一个函数执行完毕之后都会被弹出当前的执行栈,注意只有函数被执行的时候才会被压入栈中,例如以下代码
代码一
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
复制代码
代码二
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
复制代码
这两段代码的执行结果虽然相同(都是在一个地方被创建的,根据静态作用域,都会拿到外层的第一个scope),但是执行栈的调用方式是不一样的
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
复制代码
ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
复制代码
变量对象
每个执行上下文都有三个重要属性
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
- this
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。全局上下文中的变量对象就是全局对象
函数上下文
当进入执行上下文时,这时候还没有执行代码,
一个变量对象包括了以下元素
- 函数的所有形参 (如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参,属性值设为 undefined
- 函数声明
- 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
- 如果变量对象已经存在相同名称的属性,则完全替换这个属性
- 变量声明
- 由名称和对应值(undefined)组成一个变量对象的属性被创建;
- 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值
总结一下上面的过程
- 全局上下文的变量对象初始化是全局对象
- 函数上下文的变量对象初始化只包括 Arguments 对象
- 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
- 在代码执行阶段,会再次修改变量对象的属性值
作用域链
函数有一个内部属性 [[scope]]
,当函数创建的时候,就会保存所有父变量对象到其中,当函数被执行的时候会复制这个作用域链,然后生成了变量对象的时候,会把当前的活动对象压入到当前被复制的作用域链的顶端
闭包
闭包是指一个可以访问另外一个函数作用域中的变量的函数(定义在函数内部的函数)
经典题目
for(var i =0; i<5; i++) {
setTimeout(function(){
console.log(i);
},1000);
}
// 为什么输出的全是5
// 同步代码for循环先执行,在异步代码执行的时候会在全局的AO对象中寻找,此时AO对象中的i为5
复制代码
解决方式
for(var i =0; i<5; i++) {
(funtion(i){
setTimeout(function(){
console.log(i);
},1000)
}
)(i);
}
// 使用闭包,立即执行函数在执行的时候会产生执行上下文,把本次循环的i给保留下来,
// 里面的定时函数执行的时候会通过作用域链去找i,在外层立即执行函数中找到,成功输出
复制代码
其他方式
- 使用let
- 传入setTimeout的第三个参数
- ……
this的指向问题
- 箭头函数的this在定义的时候决定,无法修改,并且不能用作构造函数
- 使用new的时候,在构造函数内部里面的this都指向这个构造函数创建的新对象
- 通过bind和apply,call来改变this指向,其中bind中的this不会被修改(只有第一次有效),call单个传递,apply传递数组
- 在对象中谁调用的this就指向谁
- 直接调用this指向全局
常见手写题目
后续单独写一遍
mp.weixin.qq.com/s/sDZudDS2j…
垃圾回收机制
v8回收机制 新生代老生代
juejin.cn/post/684490…
rust回收机制 所有权系统
kaisery.github.io/trpl-zh-cn/…
c手动回收机制
数据类型以及浮点数精度
基础数据类型:number string boolean null undefined object Symbol BigInt
判断数据类型的方式:
juejin.cn/post/691980…
事件循环
掌握node和浏览器环境中两种不同的事件循环
简单梳理一下node中事件循环的方式
juejin.cn/post/684490…