[复习笔记]JS变量知识回顾

变量类型

原始类型

在JS中,变量分为原始类型和引用类型,其中原始类型有下面几种

  • number
  • string
  • boolean
  • null
  • undefined
  • symbol
  • BigInt(es10新增)

引用类型

引用类型也叫object类型,普通对象(Object),数组(Array), 函数(Function), 日期(Date)都是引用类型的一种

变量的存储位置

原始类型

原始类型都是直接存放在栈中的,栈内存的特点如下

  • 存储的值大小固定
  • 空间较小
  • 可以直接操作其保存的变量,运行效率高
  • 由系统自动分配存储空间

因为原始值一般不大,为了操作方便,所以会直接把数据存放在函数的执行栈里。在变量定义时,栈就为其分配好了内存空间。

引用类型

引用类型的值被存放在堆里,相对栈来说,堆有下面的特点

  • 存储的值大小不定,可动态调整
  • 空间较大,运行效率低
  • 无法直接操作其内部存储,使用引用地址读取
  • 通过代码进行分配空间

而在栈中声明的变量,只是保存了一个指向堆里某个地址的指针

file

数据类型的判断

typeof操作符

typeof在MDN文档中的描述如下

file

可以看出typeof判断大部分情况下还是没问题的,只是不太精确

// Numbers
typeof 37 === 'number';
typeof Infinity === 'number';
typeof NaN === 'number';   // Despite being "Not-A-Number"

// Strings
typeof '' === 'string';
typeof 'bla' === 'string';

// Booleans
typeof true === 'boolean';
typeof false === 'boolean';

// Symbols
typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
typeof Symbol.iterator === 'symbol'

// Undefined
typeof undefined === 'undefined';
typeof declaredButUndefinedVariable === 'undefined';
typeof undeclaredVariable === 'undefined';

// Objects
typeof {a: 1} === 'object';
typeof [1, 2, 4] === 'object';
typeof new Date() === 'object';
typeof /regex/ === 'object';

// Functions
typeof function() {} === 'function';

// bigint
typeof 42n === 'bigint';

使用时只要记住,除了typeof null是'object',typeof function是'function',其他情况下都和JS中的变量分类一样就行了

instanceof操作符

instanceof操作符主要用来检查构造函数的原型是否在对象的原型链上,也就是说只对引用类型有效, 不过当原型链被修改后,可能产生错误的判断

// defining constructors
function C() {}

let o = new C()

// true, because: Object.getPrototypeOf(o) === C.prototype
o instanceof C

o instanceof Object           // true

C.prototype = {}
let o2 = new C();

o2 instanceof C  // true

// false, because C.prototype is nowhere in
// o's prototype chain anymore
o instanceof C;

instanceof对原始类型无效

let simpleStr = 'This is a simple string'
let myString  = new String()

simpleStr instanceof String  // returns false, string literal is not an object
myString  instanceof String  // returns true

constructor属性

function Animal (name) {
    this.name = name
}

const cat = new Animal('cat');

console.log(cat.constructor === Animal)   // true

toString方法

function type (obj) {
    return Reflect.apply(Object.prototype.toString, obj, []).replace(/^\[object\s(\w+)\]$/, '$1').toLowerCase()
}

这种方法基本可以判断所有的内置类型

console.log(type(1))    // number
console.log(type("123"))    // string
console.log(type(true)) // boolean
console.log(type(undefined))    // undefined
console.log(type(null)) // null
console.log(type(Symbol("s"))); // symbol
console.log(type(11n));  // bigint
console.log(type({}));  // object
console.log(type([]));  // array
console.log(type(function () {}));  // function
console.log(type(/reg/));   // regexp
console.log(type(new Date()));  // date

但是对自定义的构造函数无效

function Animal(name) {
    this.name = name;
}

console.log(type(new Animal("sena")));  // object

toString方法 + Symbol.toStringTag

我们可以设置es6中通过Symbol暴露的Symbol.toStringTag来改变toString的输出

Animal.prototype[Symbol.toStringTag] = 'Animal';
type(rabbit) // animal

现在我们几乎可以用toString满足所有的需求了

隐式类型转化规则

转换前类型 转换前值 转换后(Boolean ) 转换后(Number) 转换后(String )
Number 123 true - "123"
Number Infinity true - "Infinity"
Number 0 false - "0"
Number NaN false - "NaN"
BigInt 123456n true 123456 "123456"
String "" false 0 -
String "123" true 123 -
String "123abc" true NaN -
String ”abc” true NaN -
Boolean ture - 1 "true"
Boolean false - 0 "false"
Undefined undefined false NaN "undefined"
Null null false 0 "null"
Object {} true NaN [object Object]
Array [] true 0 ""
Array ["abc"] true NaN "abc"
Array ["abc", null] true NaN "abc,"
Function function(){} true NaN "function(){}"
Symbol Symbol() true TypeError TypeError

类型比较

之前写过了:https://www.sakura-snow.com/archives/142

垃圾回收

引用计数

这是最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象,对象将被垃圾回收机制回收。

function test(){
    var a = {} ;         // a的引用次数为0
    var b = a ;         // a的引用次数加1,为1
    var c =a;           // a的引用次数再加1,为2
    var b ={};          // a的引用次数减1,为1
}

但是这个算法有一个严重的问题,那就是循环引用

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2  o2的引用次数+1
  o2.a = o; // o2 引用 o  o的引用次数+1

  return "abc";
}

f();

在这个例子里,o和o2不会被回收,因为他们的引用次数都不为0。

IE 6, 7使用引用计数方式对 DOM 对象进行垃圾回收。该方式常常造成对象被循环引用时内存发生泄漏

var app;
window.onload = function(){
    app = document.getElementById("#app");
    app.circularReference = app;
    app.lotsOfData = new Array(10000).join("*");
};

因为app自己使用circularReference属性引用了自己,所以它的被引用次数始终不为0,即使被从dom树中删去也不会被回收,现在这种回收算法只有很老的浏览器才会使用了

标记清除

这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

这个算法的垃圾回收分为2个阶段,分别是标记和清除。

  • 标记 :从根节点开始标记引用的对象。
  • 清除 :未被标记引用的对象就是垃圾对象,可以被清理。

从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。

GC优化

和其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。而Javascript的GC在100ms甚至以上,对一般的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。

分代回收

这个和Java回收策略思想是一致的,也是V8所主要采用的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时。

file

增量GC

这个方案的思想很简单,就是“每次处理一点,下次再处理一点,如此类推”。
file

这种方案,虽然耗时短,但中断较多,带来了上下文切换频繁的问题。
因为每种方案都其适用场景和缺点,因此在实际应用中,会根据实际情况选择方案。

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00