Javascript基础-变量和类型

变量和类型

JavaScript规定了几种语言类型

Undefined Null Boolean String Number Symbol Object

为什么有的编程规范要求用 void 0 代替 undefined?

undefined是全局对象的一个属性,也就是说,它是全局作用域中的一个变量,undefined的最初值就是原始数据类型undefined。ES5之后的标准中,规定了全局变量下的undefined值为只读,不可改写的,但是局部变量中依然可以对之进行改写。而void 0无论什么时候都是返回undefined,这样来看,使用void 0来代替undefined就比较稳妥,不会出错

1
2
3
4
5
6
console.log(window.undefined == void 0); // true
function changeUndefined () {
var undefined = 1;
console.log(undefined);
}
changeUndefined(); // 1

JavaScript对象的底层数据结构是什么

js基本类型数据都是直接按值存储在栈中的(Undefined、Null、不是new出来的布尔、数字和字符串),每种类型的数据占用的内存空间的大小是确定的,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,相对于堆来说,更加容易管理内存空间。

js引用类型数据被存储于堆中 (如对象、数组、函数等,它们是通过拷贝和new出来的)。其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据

参考资料:再谈js对象数据结构底层实现原理

Symbol类型在实际开发中的应用、可手动实现一个简单的Symbol

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。

symbol类型的 key 不能被 Object.keysfor..of 循环枚举。因此可当作私有变量使用。

1
2
3
4
5
6
7
8
let mySymbol = Symbol('key');
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};

完整实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
(function() {
var root = this;

var generateName = (function(){
var postfix = 0;
return function(descString){
postfix++;
return '@@' + descString + '_' + postfix
}
})()

var SymbolPolyfill = function Symbol(description) {

if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

var descString = description === undefined ? undefined : String(description)

var symbol = Object.create({
toString: function() {
return this.__Name__;
},
valueOf: function() {
return this;
}
})

Object.defineProperties(symbol, {
'__Description__': {
value: descString,
writable: false,
enumerable: false,
configurable: false
},
'__Name__': {
value: generateName(descString),
writable: false,
enumerable: false,
configurable: false
}
});

return symbol;
}

var forMap = {};

Object.defineProperties(SymbolPolyfill, {
'for': {
value: function(description) {
var descString = description === undefined ? undefined : String(description)
return forMap[descString] ? forMap[descString] : forMap[descString] = SymbolPolyfill(descString);
},
writable: true,
enumerable: false,
configurable: true
},
'keyFor': {
value: function(symbol) {
for (var key in forMap) {
if (forMap[key] === symbol) return key;
}
},
writable: true,
enumerable: false,
configurable: true
}
});

root.SymbolPolyfill = SymbolPolyfill;

})()

参考资料: ES6 系列之模拟实现 Symbol 类型

JavaScript中的变量在内存中的具体存储形式

JavaScript 中的变量分为基本类型和引用类型:

基本类型: 保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问

引用类型: 保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript 不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用

参考资料:JavaScript中的变量在内存中的具体存储形式

基本类型对应的内置对象,以及他们之间的装箱拆箱操作

String(), Number(), Boolean()

装箱:就是把基本类型转变为对应的对象。装箱分为隐式和显示

1
2
3
4
5
6
7
8
9
10
// 隐式装箱: 每当读取一个基本类型的值时,后台会创建一个该基本类型所对应的对象。
// 在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法。
// 这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁。
let num=123;
num.toFixed(2); // '123.00'//上方代码在后台的真正步骤为
var c = new Number(123);
c.toFixed(2);
c = null;
// 显式装箱: 通过内置对象 Boolean、Object、String 等可以对基本类型进行显示装箱。
var obj = new String('123');

拆箱: 拆箱与装箱相反,把对象转变为基本类型的值。

1
2
3
4
 Number([1]); //1
// 转换演变:
[1].valueOf(); // [1];
[1].toString(); // '1';Number('1'); //1

参考资料:JavaScript 基本类型的装箱与拆箱

nullundefined的区别

Number 转换的值不同,Number(null) 输出为 0, Number(undefined) 输出为 NaN
null 表示一个值被定义了,但是这个值是空值
undefined 表示缺少值,即此处应该有值,但是还没有定义

typeof 结果不同

1
2
3
typeof undefined // "undefined"
typeof null // "object",尽管返回 `object`,但他依旧是一个原始值,这是 JavaScript 在实现上的一个 bug。
typeof NaN // "number"

至少可以说出三种判断JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型

1.typeof —— 返回给定变量的数据类型,可能返回如下字符串:

1
2
3
4
5
6
7
8
'undefined'——Undefined
'boolean'——Boolean
'string'——String
'number'——Number
'symbol'——Symbol
'object'——Object / Null (Null 为空对象的引用)
'function'——Function
// 对于一些如 error() date() array()无法判断,都是显示object类型

2.instanceof 检测 constructor.prototype 是否存在于参数 object 的原型链上,是则返回 true,不是则返回 false

1
2
3
4
5
alert([1,2,3] instanceof Array) // true
alert(new Date() instanceof Date) // true
alert(function(){this.name="22";} instanceof Function) //true
alert(function(){this.name="22";} instanceof function) //false
// instanceof 只能用来判断两个对象是否属于实例关系,而不能判断一个对象实例具体属于哪种类型。

3.constructor —— 返回对象对应的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
alert({}.constructor === Object);  =>  true
alert([].constructor === Array); => true
alert('abcde'.constructor === String); => true
alert((1).constructor === Number); => true
alert(true.constructor === Boolean); => true
alert(false.constructor === Boolean); => true
alert(function s(){}.constructor === Function); => true
alert(new Date().constructor === Date); => true
alert(new Array().constructor === Array); => true
alert(new Error().constructor === Error); => true
alert(document.constructor === HTMLDocument); => true
alert(window.constructor === Window); => true
alert(Symbol().constructor); => undefined
// null 和 undefined 是无效的对象,没有 constructor,因此无法通过这种方式来判断。

4.Object.prototype.toString() 默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,是一个字符串,其中 Xxx 就是对象的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object.prototype.toString.call(new Date);//[object Date]
Object.prototype.toString.call(new String);//[object String]
Object.prototype.toString.call(Math);//[object Math]
Object.prototype.toString.call(undefined);//[object Undefined]
Object.prototype.toString.call(null);//[object Null]
Object.prototype.toString.call('') ; // [object String]
Object.prototype.toString.call(123) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用
// 比较全面
优缺点 typeof instanceof constructor Object.prototype.toString.call
优点 使用简单 能检测出引用类型 基本能检测所有的类型(除了null和undefined) 检测出所有的类型
缺点 只能检测出基本类型(除了null) 不能检测出基本类型,且不能跨iframe constructor易被修改,也不能跨iframe IE6下,undefined和null均为Object

可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用

一、数字运算符
+操作时,数字被隐式转换成字符串,实际上做的是字符串连接操作。

做除了加法以外的运算操作时,字符串被隐式转换成数字,实际上做的是数值计算。

二、.点号操作符
数字、字符串等直接量在做.操作调用方法时,隐式地将类型转换成对象。

三、if语句
if()括号里的表达式部分会被隐式转化为布尔类型进行判别。
null""undefinded, 0, false 都会被转化为 false

四、==等号-object array map set

参数 结果
undefined “undefined”
null “null”
boolean “true” 或者 “false”
number 数字作为字符串。比如,”1.765”
string 无需转换
[] “”
[5,2] “5,2”
{} “[object Object]”
Symbol() “Symbol()”

参考资料:JavaScript进阶系列-类型转换、隐式类型转换

出现小数精度丢失的原因,JavaScript可以存储的最大数字、最大安全数字,JavaScript处理大数字的方法、避免精度丢失的方法

1.精度丢失原因,说是 JavaScript 使用了 IEEE 754 规范,二进制储存十进制的小数时不能完整的表示小数

2.能够表示的最大数字 Number.MAX_VALUE 等于 1.7976931348623157e+308 ,最大安全数字 Number.MAX_SAFE_INTEGER 等于 9007199254740991

3.处理大数字: BigNumber

4.避免精度丢失: 把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)

参考资料:js数字位数太大导致参数精度丢失问题