1. 犀牛前端部落首页
  2. JS教程

js常见错误总结

js常见错误总结

全局对象和全局变量对象

全局对象GO

  • 是浏览器天生自带的存储属性和方法的堆,是一个对象

全局变量对象VO

  • 是我们代码创建的变量要存储的地方,是栈内存

全局执行上下文

带var

  • 带var是创建一个全局变量,存放在全局变量对象VO中
  • 基于var创建变量,会给VO和GO中各自存储一份

不带var

  • 不带var创建的不是变量,而是全局对象GO的一个属性

输出顺序

  • 先看是否为全局变量对象VO
  • 再看是否为全局对象GO

私有执行上下文

带var

  • 在私有上下文的AO变量对象中声明一个私有变量(是当前上下文的私有变量,和上下文以外没有必然联系)

不带var

  • 浏览器发现不是私有变量,则向其上级上下文中查找(scope-chain),如果上级也没有则继续查找,一直到EC全局上下文为止
  • 如果全局也没有,则给GO设置一个属性

作用域和作用域链

作用域

  • 当前函数’[[scope]]’ = 当前函数创建时候所在的上下文

作用域链

  • scopeChain : <当前EC, 函数[[scope]] >
  • 先找自己上下文的,自己没有,按照作用域链向上级作用域
  • 作用域链是在函数执行的时候形成的

函数执行步骤

  1. 创建私有上下文(有存放私有变量的变量对象AO)
  2. 进栈执行(会把全局上下文压缩到底部)
  3. 初始化作用域链scopeChain
  4. 初始化this指向
  5. 形参赋值(包含初始化arguments)
  6. 变量提升
  7. 代码执行
  8. 执行完可能会出栈(也可能不出栈)

内存的形式

堆内存

  • 释放:如果对内存用完后,手动释放赋值为null(null是空对象指针,也就是不指向任何的堆内存)
  • 不释放:如果有变量或者其他东西存储了堆内存的地址,则当前堆内存被视为占用,也就不能释放销毁

栈内存

  • 释放:全局栈内存,关掉页面的时候才会销毁。一般情况下,函数只要执行完,形成的私有栈内存就会被销毁释放掉(排除出现无限极递归,出现死循环的模式)
  • 不释放:如果当前上下文的某些内容(一般也是当前上下文中创建的堆)被上下文以外的变量或者其他事务所占用,那么当前上下文就不能出栈释放

内存释放机制

  • 查找引用方式(webkit内核):浏览器有自动回收垃圾的机制,定期间隔某段时间,把所有没有被占用的内存回收释放(这种垃圾回收机制比其他语言要完善一些)
  • 内存计数器方式(Trident内核):当前内存被其他东西引用了,则给堆计数1(累加计数),取消占用后,则减1,当减到零之后,浏览器就可以把它释放了

var与let

重复声明

  • 在当前上下文中,不管用什么方式,只要声明了这个变量,都不能基于let重复声明,会报错
  • 是否重复声明,并不是在代码执行阶段检测的,而是在词法解析的阶段检测的
  • 词法错误SyntaxError在词法解析阶段报错,当前代码不会执行
  • 语法错误ReferenceError在代码执行阶段报错,报错前的代码会执行

window属性

let声明的变量仅仅是全局变量,和GO没关系

var声明的变量即是全局变量,也相当于给GO(window)设置了一个属性,而且两者建立映射机制

暂时性死区

  • 基于typeof 检测一个没有声明过的变量,并不会报错,结果是’undefined’
  • 如果这个变量在后面会用到let声明,则前面在基于typeof检测就会报错,不能在声明之前使用

构造函数执行步骤

  1. 初始化作用域链
  2. 形参赋值
  3. 变量提升
  4. 首先会在当前上下文中,创建一个对象(这个对象就是当前类的实例) – 浏览器默认多做的事情
  5. 让当前上下文中的this指向新创建的对象 – 浏览器默认多做的事情
  6. 代码执行
  7. 代码执行完,如果没有设置return浏览器默认会把新创建的实例对象返回 – 浏览器默认多做的事情

原型和原型链

  • 每一个函数都天生具备一个属性:prototype(原型),prototype的属性值是一个对象(浏览器默认会给其开辟一个堆内存)
  • 在类的prototype原型对象中,默认存在一个内置的属性:constructor(构造函数),属性值就是当前类(函数)本身,所以类也称为构造函数
  • 每一个对象都天生具备一个属性:_proto_(原型链),属性值是当前实例(对象)所属类的prototype原型

原型链查找机制

  • 首先找自己的私有属性,私有属性有,调取的就是私有属性
  • 如果没有,默认基于__proto__原型链属性,找所属类prototype原型上的公共属性和方法
  • 如果还没有,则基于原型prototype中的__proto__继续向上找,一直找到Object.prototype为止

寄生组合式继承

  • call继承 + 另类原型继承
function Parent() {
  this.x = 100
}
Parent.prototype.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

检测数据类型区别

typeof

  • typeof null的结果是’object’(这是浏览器的BUG,所有的值在计算中都以二进制编码存储,浏览器中把前三位000的当作对象),而null的二进制前三位是000,所以被识别为对象,但是它不是对象,它是空对象指针,是基本类型值)

instanceof

  • 当前类的原型只要出现在了实例的原型链上就返回true
  • 本身不能完成数据类型检测,只是利用它的特征(检测某个实例是否属于这个类)来完成数据检测

constructor

  • 本身不能完成数据类型检测,利用它的实例类型检测(不能重定向)

Object.prototype.toString.call()

  • 每一种数据类型的构造函数的原型上都有toString方法
  • 除了Object.prototype上的toString是返回当前实例所属类的信息(检测数据类型的),其余的都是转换字符串的
  • 对象.toString,toString方法中的this是对象实例,也就是检测他的数据类型,也就是this是谁,就是检测谁的数据类型
  • Object.prototype.toString.call(value)所以是把toString方法执行,基于call让方法中的this指向检测的数据值,这样就可以实现数据类型检测了

二叉树

先序遍历

function preOrder(node){
  if(!(node==null)){
    divList.push(node);
    preOrder(node.firstElementChild);
    preOrder(node.lastElementChild);
  }
}

中序遍历

function inOrder(node) {
  if (!(node == null)) {
    inOrder(node.firstElementChild);
    divList.push(node);
    inOrder(node.lastElementChild);
  }
}

后序遍历

function postOrder(node) {
  if (!(node == null)) {
    postOrder(node.firstElementChild);
    postOrder(node.lastElementChild);
    divList.push(node);
  }
}

检查对称二叉树

function isTreeSymmetric(t) {
    if (!t){
        return true
    }
    return isTreeEqual(t.left, t.right)
}

isTreeEqual = function(x, y) {
    if (!x && !y){
        return true
    }
    if (!x || !y){
        return false
    }
    if (x.value === y.value){
        return isTreeEqual(x.left, y.right) && isTreeEqual(x.right, y.left)
    } else {
        return false
    }
} 

原创文章,作者:犀牛前端部落,如若转载,请注明出处:https://www.pipipi.net/4339.html

发表评论

登录后才能评论