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
)。
变量对象来记录函数中的变量。基本数据类型保存在变量对象,引用数据类型保存引用堆地址。在执行上下文中定义的 变量,参数,函数都是变量对象的一个属性
执行上下文栈
-
栈是一种数据结构,里面放着很多执行上下文
-
每次函数执行,都会产生一个执行上下文。
-
在
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]
}
}
}
*/
var
和let
js
的作用域有:全局作用域,函数作用域,块级作用域。块级作用域是由{}
包括,if
语句和for语句中
{}`也属于块作用域
-
let
相关问题- 允许块级作用域任意嵌套
- 外层作用域无法读取内层作用域的变量
- 内层作用域可以定义外层作用域的同名变量
- 函数本身的作用域在其所在的块级作用域之内
-
var
&let
&const
var
不存在块级概念,let
只能在块级作用域内访问,不能跨块使用,不可重复声明let
不能在声明变量前使用该变量,这个特性就叫暂时性死区- 如果有重复变量
let
会在编译时报错
-
全局变量
ES5
声明变量只有两种方式var
和function
ES6
有let、const、import、class
再加上ES5
的var、function
共六种声明变量的方式- 浏览器环境中顶层对象是
window
,Node
中是global
对象 ES6
中var、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
,都是对象,所以这里就会产生一个混乱的关系
Object
是一个函数,所以是Function的实例Object.__proto__
会指向Function.prototype
Object
是一个函数即存在Object.Prototype
,它是一个对象,存在__proto__
Function
既然是对象但是也存在Function.__proto__
所以这里就会存在冲突,Object
是Function
的实例,Function
也是对象,也存在__proto__
。所以js
规定
Function.__proto__
===Function.prototype
Object.Prototype.__proto__
===null
- 函数的祖宗是
Function
Object
的祖宗是null
搬运了一张图,请勿见怪
instanceof
instanceof
运算符的第一个参数是一个对象,第二个参数一般是一个函数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}]
}