JS基础知识

87 阅读7分钟

JS基础知识

基础知识

栈是一组数据存放的方式特点,先进后出,后进先出。例如函数调用栈等

class Stack {
    private items:number[]:[]
    push(val:number){
        this.items.push(val)
    }
    pop(){
        return this.items.pop()
    }
}

队列

  • 队列是一种操作受到限制的线性表
  • 特殊之处在于他只允许在表的前端进行删除操作,在表的后端进行插入操作
  • 因为队列只在一段插入,另一端删除。只有最早进入队列的元素,才能优先删除,所以先进先出,后进后出
class Queue{
    private items:number[]:[]
    enqueue(val:number){
        this.items.push(val)
    }
    dnqueue(){
        return this.items.shift()
    }
}

执行上下文

  • 当函数运行的时候,会创建一个执行环境,这个执行环境就叫执行上下文(Execution Context)
  • 执行上下文中会创建一个变量对象(Variable Object),基础数据类型都保存在变量对象中
  • 引用数据类型都保存在堆里,我们通过操作对象的引用地址来操作独享

解释:每个函数执行都会产生执行上文对象(EC),每个执行上下文对象都有变量对象 (VO)。

变量对象来记录函数中的变量。基本数据类型保存在变量对象,引用数据类型保存引用堆地址。在执行上下文中定义的 变量,参数,函数都是变量对象的一个属性

执行上下文栈

  1. 栈是一种数据结构,里面放着很多执行上下文

  2. 每次函数执行,都会产生一个执行上下文。

  3. JS运行环境中主要分为全局执行上下文,和函数执行上下文

    • 全局执行只有一个,一般由浏览器或者nod创建。
    • JS执行的过程中就会产生多个执行上下文,JS引擎会用栈来管理这些执行上下文。执行上下文栈也叫调用栈。调用栈用来存储函数执行期间所有创建的执行上下文,具有先进后出,后进先出原则
    • 栈底永远是全局执行上下文,栈顶是当前正在执行的上下文。
    • 当开启一个函数执行,创建一个执行上下文并放入调用栈,执行结束自动出栈
    • 只有全局上下文变量对象允许通过VO属性名词来间接访问,函数上下文中是不能直接访问VO对象

全局上下文的变量对象VO也被称为GO,存放着setTimeout、Math....等变量,可以在任何地方被访问。

函数执行上下文,不能直接通过VO对象访问。

什么叫作用域链

创建函数时候确定作用域链的,js采用的是静态作用域。换句话来说,定义函数的时候,是父级函数执行时,会给定义的函数,添加scope属性并且确定作用域链接。

// global 
var a = 1
function one(){
    var b = 2;
    function two(){
      var c = 3
       function three(){
          var D = 3
        }
    }
    two()
}
one()
/*
当执行one函数时,会创建one的执行上下文,然后进行变量提升, b会提升为undefined.
函数two 会被创建并且添加作用域链。在此刻已经确定了作用域链
oneExecutionContext = {
    VO:{
        b:undefined,
        two:{
            twoFn:twoFn,
            '[[scopes]]':[globalExecutionContextVo]
        }
    }
}
​
后续two执行,oneExecutionContext会变成。而新创建two执行上下文
​
oneExecutionContext = {
    VO:{
        b:2,
        two:{
            twoFn:twoFn,
            '[[scopes]]':[globalExecutionContextVo]
        }
    }
}
然后进行变量提升, b会提升为undefined。three函数被定义且确定了作用域链scope
twoExecutionContext = {
    VO:{
        c:undefined,
        three:{
            threeFn:threeFn,
            '[[scopes]]':[oneExecutionContextVo,globalExecutionContextVo]
        }
    }
}
*/

varlet

js的作用域有:全局作用域,函数作用域,块级作用域。块级作用域是由{}包括,if语句和for语句中{}`也属于块作用域

  1. let相关问题

    • 允许块级作用域任意嵌套
    • 外层作用域无法读取内层作用域的变量
    • 内层作用域可以定义外层作用域的同名变量
    • 函数本身的作用域在其所在的块级作用域之内
  2. var & let & const

    • var 不存在块级概念,let只能在块级作用域内访问,不能跨块使用,不可重复声明
    • let不能在声明变量前使用该变量,这个特性就叫暂时性死区
    • 如果有重复变量let会在编译时报错
  3. 全局变量

    • ES5声明变量只有两种方式 varfunction
    • ES6let、const、import、class再加上ES5var、function共六种声明变量的方式
    • 浏览器环境中顶层对象是windowNode中是global对象
    • ES6var、function依然是顶层对象属性,let、const、class生命的全局变量不属于顶层对象属性

this

为什么会出现this,我们需要this 来做什么

this表示调用者,或者说 当前执行这个逻辑的主体是谁。函数只是一个处理逻辑。在确定this只需要判断点前面是谁即可。即找出来当前函数的主体是谁

其实this的确定只有一条,谁调用方法this就是谁

let person = {
    name:'zhangsan',
    getName(){
        console.log(this.name)
    }
}
person.getName()   // person 是this
let getName =  person.getName   
getName()   // 严格模式 this是 undefined  非严格模式 this是window
dom.addEventListener('click',function(){  
    console.log(this) // 事件绑定 this就是dom元素 
})

call、apply、bind

三个方法都改变this指向,call bind参数传递方式不同,会立马执行。bind不会立马执行。

call 实现

Function.prototype.call = (context,...args)=>{
    const baseType = ['number','string','boolean']
    let type = typeof context
    if(baseType.includes(type)){
        context = new context.constructor(context)
    }
    context = context || window
    let symbol = Symbol('fn')
    context[symbol] = this
    let res
    try{
          res = context[symbol](...args)
    }catch(error){
        res = error
    }
    delete context[symbol]
    return res
}

apply 实现

Function.prototype.apply = (context,args)=>{
    const baseType = ['number','string','boolean']
    let type = typeof context
    if(baseType.includes(type)){
        context = new context.constructor(context)
    }
    context = context || window
    let symbol = Symbol('fn')
    context[symbol] = this
    let res
    if(!Array.isArray(args)){
       throw Error ('arguments error')
    }
    try{
          res = context[symbol](...args)
    }catch(error){
        res = error
    }
    delete context[symbol]
    return res
}

bind实现

Function.prototype.bind = (context,...outerArgs)=>{
  return (...args)=> this.call(context,...outerArgs,...args)
}

new 关键字

// 基础版
function _new(clazz,...args){
    let obj = {}
    obj.__proto__ = clazz.prototype
    clazz.call(obj,...args);
    return obj
}

原型链

原型链设计是为了解决共有属性的问题,节约空间,节约内存。__proto__属性组成的链条就叫原型链,实现属性和方法的共享

__proto__称为隐式原型,prototype 原型。当我们使用点运算符获取某个属性,如果找到直接返回。如果不存在继续在__proto__上面查找,一层一层往上查找。直至最终。

在实例中对象是存在__proto__而不存在prototype,而构造函数是存在prototype,也存在__proto__

首先__proto__是一个属性,存在于实例上面,指向原型prototype,只要是prototype,都是对象,所以这里就会产生一个混乱的关系

  1. Object是一个函数,所以是Function的实例
  2. Object.__proto__会指向 Function.prototype
  3. Object是一个函数即存在Object.Prototype ,它是一个对象,存在__proto__
  4. Function既然是对象但是也存在 Function.__proto__

所以这里就会存在冲突,ObjectFunction的实例,Function也是对象,也存在__proto__。所以js规定

  1. Function.__proto__ === Function.prototype
  2. Object.Prototype.__proto__ === null
  3. 函数的祖宗Function
  4. Object祖宗是null

搬运了一张图,请勿见怪

instanceof

  1. instanceof 运算符的第一个参数是一个对象,第二个参数一般是一个函数
  2. instanceof的判断规则是,沿着对象的__proto__这条链向上查找,如果能找到函数的prototype则返回true,否则返回false

变量环境VE和词法环境LE

ES5中执行上下文中主要包含 this 、VO、scopeChain,而在ES6中包含this、variableEnviroment(变量环境)、lexicalEnviroment(词法环境)、outer(外部执行下文)

function fn(){
    var a = 1
    let b = 2
    {
        // 第一个代码块
        let b = 3
        var c = 4
        let d = 5
        console.log(a,b,c,d)
    }
    {
        // 第二个代码块
        let b = 6
        let d = 7
        console.log(a,b,c,d)
    }
}
fn()
/**
全局编译
es6 创建VE 和LE
*/

globalEc = {
	this:window, // this 指针
    outer:null, // 来实现作用域链接
    variableEnviroment:{fn,}
    lexicalEnviroment:{}
}
// fn编译阶段
fnEc = {
    this:window
    outer:globalEc.variableEnviroment,
    variableEnviroment:{a:undefined,c:undefined}
	lexicalEnviroment:[{b:undefined}]
}
// lexicalEnviroment 在编译阶段会忽略块级作用域

// 在fn 开始执行 每遇到一个代码块就形成一个新的LE,当编译第一个代码块
fnEc = {
    this:window
    outer:globalEc,
    variableEnviroment:{a:1,c:undefined}
	lexicalEnviroment:[{b:2},[b:undefined,d:undefined]]
}
// 第一个代码块执行
fnEc = {
    this:window
    outer:globalEc.variableEnviroment,
    variableEnviroment:{a:1,c:4}
	lexicalEnviroment:[{b:2},[b:3,d:5]]
}
// 第一个代码块执行完毕,他的LE会出栈,第二个代码块编译入栈
fnEc = {
    this:window
    outer:globalEc.variableEnviroment,
    variableEnviroment:{a:1,c:4}
	lexicalEnviroment:[{b:2},{b:undefined,d:undefined}]
}
// 第二个代码块执行
fnEc = {
    this:window
    outer:globalEc.variableEnviroment,
    variableEnviroment:{a:1,c:4}
	lexicalEnviroment:[{b:2},{b:6,d:7}]
}
// 第二个代码块执行完毕 会出栈
fnEc = {
    this:window
    outer:globalEc.variableEnviroment,
    variableEnviroment:{a:1,c:4}
	lexicalEnviroment:[{b:2}]
}