栈
数据结构中的栈
栈是一组数据的存放方式;特点是先进后出,后进先出
模拟栈结构特点
class Stack {
private stackArr: number[] = []
//添加新元素到栈顶
push(element: number) {
this.stackArr.push(element);
}
//移动栈顶元素,同时返回移除的元素
pop(element): number {
return this.stackArr.pop();
}
}
let stack = new Stack()
stack.push(1)
stack.push(2)
console.log(stack) //Stack { items: [ 1, 2 ] }
console.log(stack.pop()); //2
堆
- 一般来说,系统会划分出两种不同的内存空间,一种叫做stack (栈),另一种叫做heap(堆)
- stack是有结构的,每个区块按照一定次序存放,可以明确知道每个区块的大小
- heap是没有结构的,数据可以任意存放。因此,stack的寻址速度要快于heap
- 只要是局部的、占用空间确定的数据,一般都存放在stack里面,否则就放在heap里面,所有的对象都存放在heap
队列
- 队列是一种操作受限制的线性表
- 只允许在表的前端队头进行删除操作,或者在表的后端队尾进行插入操作
- 队列的特点是先进先出
模拟队列特点
class Queue {
private items: number[] = []
enqueue(element: number) {
this.items.push(element)
}
dequeue() {
return this.items.shift();
}
}
数据类型
- 基本数据类型:Number, String, Boolean, Null, undefined, Symbol
- 引用类型:Object, [], /^$/, Math, new Date()...
执行上下文
- 当函数运行时,会创建一个执行环境,这个执行环境就叫执行上下文(Execution Context)
- 执行上下文中会创建一个对象叫做变量对象(Variable Object),基础数据类型都保存在变量对象中
- 基本数据类型存储在栈中;引用数据类型存储在堆中,引用数据的地址存储在栈中
如何复制
- 当我们复制基本数据类型时,复制的是值本身
- 当我们复制的是引用数据类型时,复制的是当前指向堆中的地址
执行上下文生命周期
一个新的执行上下文的生命周期有两个阶段:
- 编译阶段:创建变量对象;初始化作用域链;初始化this 指向;初始化arguments...; 寻找里面的var 变量声明、函数声明并赋值,进行变量提升
- 执行阶段:变量赋值;函数赋值;代码执行
/* 当执行getName的时候,会创建一个执行上下文
** 编译阶段
** 1、处理参数,把参数放入VO
** 2、扫描所有代码,找出function 声明,从上往下依次执行,在编译阶段,会处理所有的函数声明,
如果有重复的声明,后面会覆盖前面的声明
** 3、扫描var 关键字, var 只声明不赋值,值是undefined
** 4、在编译阶段不会let变量的,let变量也不会放在VO里
** 编译完成
*/
var name = '小妖'
function getName() {
let name = '现世'
console.log(name);
}
getName()
作用域链
作用域链在函数创建的时候就已经确定了,跟在哪执行没有关系
闭包
简单来说是执行和被调用不在同一个上下文。
- 闭包有两部分组成,一个是当前的执行上下文,一个是在该执行上下文中创建的函数
- 当函数执行的时候引用了当前执行上下文中的变量就会产出闭包
- 在现代浏览器中当一个值失去引用的时候就会被标记,被垃圾收集回收机回收并释放空间; 闭包的本质就是在函数外部保持内部变量的引用,阻止垃圾回收
闭包的作用: 保存/保护
- 为了防止全局变量的冲突污染,我们建议每个开发者,都把自己的代码放置到一个闭包中(自执行函数执行即可,这样就是私有的上下文)
- 如果我们封装一个插件或者类库,为了防止我们定义的变量和方法和用户定义的冲突,是需要我们把所有写的代码放到一个闭包中
var/let/const
- 创建变量的六种方式中:var/function有变量提升,而let/const/class/import都不存在这个机制
- var允许重复声明,let不允许;在相同的作用域中(或执行上下文中),如果使用var/function关键词声明变量并且重复声明,是不会有影响的(声明第一次之后,之后再遇到就不在重复声明了);但是使用let/const就不行,浏览器会校验当前作用域中是否已经存在这个变量,如果已经存在了,则再次基于let等重新声明就会报错
- let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问
- 在let声明变量前不能使用该变量,这特性叫暂时性死区(temporal dead zone)
this
- 函数的this是在被调用的时候才能确定的
- 非严格模式下this就是window,严格模式下this是undefined
- 如果是为DOM元素绑定事件,那么绑定的方法中的this一般是元素本身
- 构造函数中this 是当前实例
- 箭头函数没有自己的this,无法创建箭头函数的实例
call,apply,bind
//call
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
//apply
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
//bind
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
原型链
- 函数Function自带一个prototype属性,属性值是一个对象,存储实例公用的属性和方法(也叫原型)
- 每一个原型(prototype)自带constructor属性,这个属性存储的是当前函数本身
function fn() {}
fn.prototype.constructor === fn //true
- 每一个对象数据类型的值自带一个__proto__属性,属性值指向所属类的原型protorype
继承
JS中第一种继承方案:原型继承(让子类的原型等于父类的实例即可)
function Parent () {
this.x = 100;
}
Parent.prototype.getC = function getX () {
return this.x;
}
function Child () {
this.y=200;
}
Child.prototype = new Parent; //原型继承
Child.prototype.getY = function getY () {
return this.y;
}
let c = new Child;
console.log(c.getC());
原型继承的特点:
- 父类中私有和公有的属性和方法,最后都变为子类实例公有的
- 原型继承并不会把父类的属性方法“拷贝”给子类,而是让子类实例基于__proto__原型链找到自己定义的属性和方法“指向
JS中第二种继承方式:call继承,只能继承父类中私有的,不能继承父类中公有的
function Parent () {
this.x = 100;
}
Parent.prototype.getC = function getX () {
return this.x;
}
function Child () {
Parent.call(this);// 在子类函数中,把父类当做普通方法执行(没有父类实例,父类原型上的那些东西也就和他没关系了)
//this.x=100相当于强制给c1这个实例设置了一个私有的属性x,属性值100,相当于让子类的实例继承了父类的私有的属性,并且也变为了子类私有的属性“拷贝式”
this.y=200;
}
Child.prototype.getY=function getY(){
return this.y;
}
let c1=new Child;
console.log(c1);
JS中第三中继承:寄生组合式继承(call继承+另类原型继承):
function Parent () {
this.x=100;
}
Parent.protype.getX = function getX () {
return this.x;
}
function Child () {
Parent.call(this);
this.y=200;
}
Child.prototype = Object.create(Parent.prototype); //创建一个空对象,让其原型指向该对象
Child.prototype.constructor = Child;
Child.prototype.getY = function getY () {
return this.y;
}
let c1 = new Child;
console.log(c1);
ES6中继承写法:
class Parent{
constructor(){
this.x=100;
}
getX(){
return this.x;
}
}
//继承:extends Parent(类似于寄生组合继承),继承后一定要在constructor第一行加上super();
class Child extends Parent{
constructor(){
super();//类似于之前的call继承,super(100,200):相当于把Parent中的constructor执行,传递了100和200
this.y=200;
}
getY(){
return this.y;
}
}
let c1=new Child;
console.log(c1);