1.JavaScript
规定了几种数据类型
规定了7种数据类型
- Undefined
- Null
- Boolean
- String
- Number
- Symbol
- Object
2.JavaScript
对象的底层数据结构是什么
需要阅读一些书籍解决,待解决
3.Symbol
类型在实际开发中的应用、可手动实现一个简单的Symbol
3.1 引入
Symbol
数据类型的原因
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin
模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6
引入Symbol
的原因。
3.2 如何使用
Symbol
- Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。
let s = "Hello world!";
let sl = Symbol();
typeof s; // string
typeof sl; // symbol
上面代码表明, 变量sl
是独一无二的值,typeof
运算符的结果,表明变量sl
是 Symbol
数据类型,而不是字符串之类的其他类型。
- Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
- 如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。
const obj = {
toString() {
return 'abc';
}
};
const sym = Symbol(obj);
sym // Symbol(abc)
- 注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
- Symbol 值不能与其他类型的值进行运算,会报错。
let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string
- Symbol 值可以显式转为字符串,也可以转为布尔值,但是不能转为数值。
let sym = Symbol();
Boolean(sym) // true
!sym // false
if (sym) {
// ...
}
Number(sym) // TypeError
sym + 2 // TypeError
3.3 作为属性名的
Symbol
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
- 将对象的属性名指定为一个 Symbol 值。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
- 注意,Symbol 值作为对象属性名时,不能用点运算符。
const mySymbol = Symbol();
const a = {};
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
- Symbol 类型还可以用于定义一组常量,保证这组常量的值都是不相等的。
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
};
log(log.levels.DEBUG, 'debug message');
log(log.levels.INFO, 'info message');
3.4
Symbol
实例,也就是作用 消除魔术字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。
function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串
上面代码中,字符串Triangle就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。
常用的消除魔术字符串的方法,就是把它写成一个变量。
const shapeType = {
triangle: 'Triangle'
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 });
上面代码中,我们把Triangle写成shapeType对象的triangle属性,这样就消除了强耦合。
如果仔细分析,可以发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可。因此,这里就很适合改用 Symbol 值。
const shapeType = {
triangle: Symbol()
};
3.5
Symbol
作为属性名有哪些特性
-
Symbol 作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名
-
Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols
// [Symbol(a), Symbol(b)]
- 下面是另一个例子,Object.getOwnPropertySymbols方法与for…in循环、Object.getOwnPropertyNames方法进行对比的例子。
const obj = {};
let foo = Symbol("foo");
Object.defineProperty(obj, foo, {
value: "foobar", // 定义一个对象,属性值为foobar 这种定义对象的方式不常用,要知道
});
for (let i in obj) {
console.log(i); // 无输出
}
Object.getOwnPropertyNames(obj)
// []
Object.getOwnPropertySymbols(obj)
// [Symbol(foo)]
- Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]
3.6 汇总一些
Symbol
方法和属性
Symbol.for(); // 我们希望重新使用同一个 Symbol 值,Symbol.for方法可以做到这一点
Symbol.keyFor(); // Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key。
Symbol.hasInstance;
Symbol.isConcatSpreadable;
Symbol.species;
Symbol.match;
Symbol.replace;
Symbol.search;
Symbol.split;
Symbol.iterator;
Symbol.toPrimitive;
Symbol.toStringTag;
Symbol.unscopables;
详情参考: 阮一峰-ECMAScript 6 入门
4.JavaScript
中的数据在内存中的具体存储形式
Js的数据类型包括两种:
基本数据类型:String、Boolean、Number、undefined、null、Symbol
引用数据类型(复杂数据类型):Object
在内存中分为栈区(stack)和堆区(heap),基本数据类型存放在栈区,引用数据类型存放在堆区,
基本数据类型:
- 声明一个变量a的时候,会在栈里面开辟出一块新的内存空间,用来存放这个变量a的值
- 当变量 a 储存的数值发生改变时,栈区里对应的那块内存里存的数据也会发生改变
- 再声明一个变量b,并把变量a赋值给变量b,此时会在栈内开辟一个新空间用来储存变量b。
- 这时变量a和变量b对应栈内存中两个空间,修改其中一个不会影响到另一个。
复杂数据类型
- 声明一个对象var obj1 = {name: ‘sheng’}, 此时会在堆中开辟一块空间存放obj1值{name: ‘sheng’}
- 在栈中开辟一个空间存放指向obj1值的指针,obj1通过这个指针可以拿到堆中的值
- 如果将obj1这个对象赋值给obj2时,此时其实赋值给obj2是栈中的指针,
- 那么obj1和obj2通过相同的指针指向是同一个值,修改其中一个对象的值,会影响到另一个对象。
- 如果对obj1重新赋值的话,那么这个对象会堆中的另一块区域,不会在与obj2共享同一块区域。
5、null
和undefined
的区别
1、首先看一个判断题:null和undefined 是否相等
console.log(null==undefined)//true
console.log(null===undefined)//false
观察可以发现:null
和undefined
两者相等,但是当两者做全等比较时,两者又不等。
-
null
:Null
类型,代表“空值”,代表一个空对象指针,使用typeof运算得到 “object”,所以你可以认为它是一个特殊的对象值。 -
undefined
:Undefined
类型,当一个声明了一个变量未初始化时,得到的就是undefined
。 -
实际上,undefined值是派生自null值的,ECMAScript标准规定对二者进行相等性测试要返回true,
2、那到底什么时候是null,什么时候是undefined呢?
null表示”没有对象”,即该处不应该有值。典型用法是:
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。