JavaScript 是一种弱类型或者说动态语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据:
//例如可以声明一个变量:
var a = 1;
a = 'leilei'
a = true
这种形式在JavaScript语法中不会报错,但是会有潜在的一些问题,因此微软推出的JavaScript的超集TypeScript就解决了这个问题;在声明一个变量之前首先声明他的类型;(示例为ts代码)
//声明变量的类型及初始值:
var [变量名] : [类型] = 值;
var uname:string = "leilei";
数据类型
ECMAScript标准定义了8中数据类型:
- 7种原始类型
- Boolean
- Null
- Undefined
- Number
- BigInt
- String
- Symbol
- Object
原始值( primitive values )
除 Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。
布尔类型
布尔表示一个逻辑实体,可以有两个值:true 和 false。
Null 类型
Null 类型只有一个值:null。
Undefined 类型
一个没有被赋值的变量会有个默认值 undefined。
Null 和 Undefined 区别(面试问到过)
console.log(null==undefined)//true
console.log(null===undefined)//false
null: object类型,代表“空值”,代表一个空对象指针,
undefined: undefined类型,
null和 undefined都表示“值的空缺”,可以认为undefined是表示系统级的、出乎意料的或类似错误的值的空缺,
null是表示程序级的、正常的或在意料之中的值的空缺。
undefined是访问一个未初始化的变量时返回的值,而null是访问一个尚未存在的对象时所返回的值。
因此,可以把undefined看作是空的变量,而null看作是空的对象。
场景:
null
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
undefined
(1)变量被声明了,但没有赋值时,就等于undefined。
(2)调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时或者return后面什么也没有,返回undefined。
数字类型
JavaScript 只有一种数字类型。它并没有为整数给出一种特定的类型,只要在双精度浮点数的取值范围内在JavaScript中数字就是安全的。超出范围那么JavaScript 数字就不再安全了。具体检测数值是否安全可以参考 MDN
还有一些带符号的值:+Infinity,-Infinity 和 NaN (非数值,Not-a-Number)。
BigInt 类型
BigInt
类型是 JavaScript 中的一个基础的数值类型,可以用任意精度表示整数。使用 BigInt,您可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。BigInt是通过在整数末尾附加 n 或调用构造函数来创建的。
字符串类型
JavaScript 字符串是不可更改的。这意味着字符串一旦被创建,就不能被修改。
符号类型
符号(Symbols)是ECMAScript 第6版新定义的。符号类型是唯一的并且是不可修改的, 并且也可以用来作为Object的key的值(如下). 在某些语言当中也有类似的原子类型(Atoms). 你也可以认为为它们是C里面的枚举类型
symbol类型是一种基本数据类型 ([primitive data type]。
Symbol()
函数会返回symbol类型的值,该类型具有静态属性和静态方法。
它的静态属性会暴露几个内建的成员对象;
它的静态方法会暴露全局的symbol注册,且类似于内建对象类,
但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()
"。
每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。
Symbol类型详解
创建symbol实例
//可以通过调用Symbol()函数来创建一个Symbol实例:
let symbol1 = Symbol();
//可以在调用Symbol()函数时传入一个可选的字符串参数,相当与给该实例一个描述信息
let symbol2 = Symbol('this is symbol2');
检查类型(使用typeof)
typeof symbol1 // 'symbol'
对比示例
let symbol1 = Symbol();
let symbol2 = Symbol('this is symbol2');
let symbol3 = Symbol();
symbol1 == symbol3 //false
symbol1 === symbol3 //false
symbol1 == symbol2 //false
使用场景1:常量
//常量
const type1 = 'type1';
const type2 = 'type2';
const type3 = 'type3';
function testType(type){
switch(type){
case type1:
console.log(type)
break;
case type2:
console.log(type)
break;
case type3:
console.log(type)
break;
default:
throw new Error('type error')
}
}
//可以使用
const type1 = Symbol();
const type2 = Symbol();
const type3 = Symbol();
//来替换以上常量声明,避免命名困难强迫症陷阱,并且完美保证每个常量的值都是唯一。
使用场景2:对象属性名
//我们知道对象的key是保持唯一的,因此有时候可以用对象key的特性来进行数组去重。symbol的特性可以用来做对象的key值。
//特殊的一点是在使用Symbol 类型作为对象的key值时需要使用中括号 [ ] 来讲Symbol类型括起来。
let obj = {
name : 'ray',
age:23
}
obj.name // 'ray'
obj[age] // 23
const name = Symbol();
const age = Symbol();
let obj = {
[name]:'ray'
}
obj[age] = 23;
obj[name] //'ray';
obj[age] // 23
//此时我们输出obj
console.log(obj); //{Symbol(): "ray", Symbol(): 23}
//很显然对象key值为Symbol();
//如果我们使用for in 循环去遍历obj的key会得出什么呢?
for(let key in obj){
console.log(key); //
}
//此时的输出竟然是空的 ......
//对了,输出确实是空的。因为Symbol类型的key是不能通过Object.keys()或者for...in来枚举的,
//它未被包含在对象自身的属性名集合(property names)之中。
//接着看
var symbol = Symbol();
var obj1 = {
name:'ray',
age:24,
[symbol] : 'symbol'
}
Object.keys(obj1) // ['name','age'];
//那它的[symbol] key值去哪了,怎么访问到呢???
obj1[symbol] // 'symbol'
//放心 可以访问到我们定义好的值,只是不能通过常规的手段去获取而已。
//例如,使用 JSON.stringify() 转换成字符串时 Symbol属性也会被排除;
JSON.stringify(obj1) // "{"name":"ray","age":24}",此时也是没有Symbol属性的。
//但是有专门的API就是针对Symbol的 例如:
// 使用Object的API
Object.getOwnPropertySymbols(obj1) // [Symbol()]
// 使用新增的反射API
Reflect.ownKeys(obj1) // ['name', 'age',Symbol(symbol)]
总结:在使用Symbol此类型的数据作为对象的key时,无法通过Object.keys()和for in 循环去处理获取对象的Symbol类型key值,使用JSON.stringify()去转换对象为字符串时,也会隐藏掉以Symbol为key的属性名和属性值。但是可以使用Object.getOwnPropertySymbols(obj)与Reflect.ownKeys(obj)来获取以Symbol类型作为对象的key的键的,实际上这也为对象创建了其私有属性。
关于Symbol的使用还有很多,本文Symbol内容部分知识参考大神
一斤代码的《理解和使用ES6中的Symbol》
JavaScript 重量级人物来了,他就是对象
对象(Object)
相信对象对每个程序员来说都不陌生,毕竟办公室里除了敲代码就是谈论怎么找对象,然后年级大点的老程序员(其实他还不到30岁)就说“哎呀 对象嘛,哪里有那么难搞,New一个出来就完了嘛”。
咳咳,言归正传,相信每个JavaScript程序员都听说过,在JavaScript中,万物皆对象。我认为对于这句话理解的透彻程度,决定了一个JavaScript程序员的自我修养深度。
先来点MDN的概念,虽然有的看不太懂,但是就是觉得人家说得好,哈哈。
在计算机科学中, 对象是指内存中的可以被标识符引用的一块区域.
在 Javascript 里,对象可以被看作是一组属性的集合。用对象字面量语法来定义一个对象时,会自动初始化一组属性。(也就是说,你定义一个var a = "Hello",那么a本身就会有a.substring这个方法,以及a.length这个属性,以及其它;如果你定义了一个对象,var a = {},那么a就会自动有a.hasOwnProperty及a.constructor等属性和方法。)而后,这些属性还可以被增减。属性的值可以是任意类型,包括具有复杂数据结构的对象。属性使用键来标识,它的键值可以是一个字符串或者符号值(Symbol)。
一个 Javascript 对象就是键和值之间的映射.。键是一个字符串(或者 Symbol
)Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。 值可以是任意类型的值。 这使得对象非常符合哈希表
函数是一个附带可被调用功能的常规对象。
数组是一种使用整数作为键(integer-key-ed)属性和长度(length)属性之间关联的常规对象。此外,数组对象还继承了 Array.prototype 的一些操作数组的便捷方法。例如, indexOf (搜索数组中的一个值) or push
(向数组中添加一个元素),等等。 这使得数组是表示列表或集合的最优选择。
JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,来源于 JavaScript 同时也被多种语言所使用。 JSON 用于构建通用的数据结构。
总之,这段话说明了一件事,那就是数字,字符串,函数,数组,JSON等都是对象。
万物皆对象就完事了。
创建对象
1.字面量:
var obj = {
name:'leilei',
age:23
}
2.使用new操作符:
var obj = new Object();
obj.name = 'leilei';
obj.age = 23;
3.Object.create():
var obj = {a:1}
var obj1 = Object.create(obj)
console.log(obj1) // 输出{} ????? 下面有原因
console.log(obj1.a) // 输出1
4.构造函数模式
function Person(name,age){
this.name = name;
this.age = age;
}
var person1 = new Person('leilei',23);
console.log(person1) // Person {name: "leilei", age: 23}
5.工厂模式
function person(name,age){
var o = new Object();
o.name = name;
o.age = age;
return o;
}
var person1 = person('leilei',23);
console.log(person1); //{name: "leilei", age: 23}
6.原型模式
function Person(){}
Person.prototype.name = 'leilei';
Person.prototype.age = 23;
var person1 = new Person();
console.log(person1); //Person {}
console.log(person1.name); //'leilei'
person1.sex = 'male';
console.log(person1);// Person {sex: "male"}
person1.name = 'zhaohui';
console.log(person1);// Person {sex: "male", name: "zhaohui"}
7.混合模式(构造函数模式+原型模式)
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.sex = 'male';
var person1 = new Person('leilei',23);
console.log(person1); // Person {name: "leilei", age: 23}
console.log(person1.sex) // 'male'
以上是对象的几种创建方法,里面有很多细节都没有详细的说明,主要是因为本文的重点不在这块,而且在平时工作中我自己个人使用的较少,也是害怕写错了误人,以上写过的代码都是测试过没有问题的。
以下是对于上面的一些补充,以及面试经历过关于这块的问题:
1.对象创建的几种方式
见上几种;
2.对象创建new 和Object.creact()的区别
首先,看看MDN上关于Object.create()方法的说明:
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。 (不理解的话继续往下看)
语法:
Object.create(proto[, propertiesObject])
参数:
proto:新创建对象的原型对象。
propertiesObject:可选。如果没有指定为undefined
,则是要添加到新创建对象的不可枚举(默认)属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()
的第二个参数。
返回值:
一个新对象,带着指定的原型对象和属性。
看代码:
var obj = {a:1}
var o = Object.create(obj);
console.log(o); // {} 空对象
console.log(o.a); // 1
console.log(o.__proto__) // {a: 1}
/*由定义可知,Object.create()使用现有的对象来提供新创建的对象的proto,所以可以看出属性a不是对象o自身的属性,
而是通过其原型链访问到的对象obj的属性,相当于将对象obj放到了对象o的原型上,也可以说o继承了obj,将obj的属性
添加到了其原型下。*/
//因此Object.keys(o) // [];
//使用for in 循环也获取不到key值。
然后再看看他的第二个参数 propertiesObject
Object.create() 用第二个参数来创建非空对象的属性描述符默认是为false的;
什么意思呢?先看代码:
var obj = {a:1}
var o = Object.create(obj,{des:{value:123}})
console.log(o) // {des: 123}
Object.keys(o) // [];
o.des = 456;
console.log(o.des); //123 ,没有变为456
//现在使用Object.getOwnPropertyDescriptors()来看一下
Object.getOwnPropertyDescriptors(o);
输出为:
{
des:{
configurable:false, //不可配置
enumerable:false, //不可枚举
value:123,
writable:false //不可写
}
}
以上可以看出,为什么使用Object.keys(),和for in 循环不能获取Object.create()的第二个参数,以及为什么修改des为456之后再次去获取还是123;
那么如何让它可以枚举,可以写入,可以配置呢?
o2 = Object.create({}, {
p: {
value: 2019,
writable: true, //可写
enumerable: true, //可枚举
configurable: true //可配置
}
});
o2.p = 2020;
console.log(o2) // {p:2020};
总结:Object.create()方法可以创建对象,创建一个新对象,使用现有的对象来提供新创建的对象的proto;(现在理解这句话有点清晰了吧)
由于是在原型上,所以使用Object.keys(),for in 循环获取key值自然是拿不到的。
Object.create()的第二个参数创建非空对象的属性描述符默认是为false的;也就是不可写,不可配置,不可枚举,但是可以通过自己修改来使其可以配置,可以写入,可以枚举。
使用Object.getOwnPropertyDescriptors();方法可以获取Object.create()第二个参数具体属性。
这样大概也就对Object.create()有了一个大概了解,接着再看new Object()与Object.create()的区别:
1.创建对象的方式不同
new Object() 通过构造函数来创建对象, 添加的属性是在自身实例下。
Object.create() es6创建对象的另一种方式,可以理解为继承一个对象, 添加的属性是在原型下。
2.创建对象属性的性质不同
Object.create() 用第二个参数来创建非空对象的属性描述符默认是为false的,
而构造函数或字面量方法创建的对象属性的描述符默认为true。
3.创建空对象时不同
当用构造函数或对象字面量方法创建空对象时,对象时有原型属性的,即有proto;
当用Object.create()方法创建空对象时,对象是没有原型属性的。
本文Object.create()内容部分知识参考大神liwuwuzhi的文章《Object.create()》
Set
Set
对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
const set2 = new Set([{a:1}, '2', 3, [4,5], 6,false]);
语法:
new Set([iterable]);
参数:
iterable
如果传递一个可迭代对象,它的所有元素将不重复地被添加到新的 Set中。如果不指定此参数或其值为null
,则新的 Set为空。
返回值:
一个新的Set对象。
简述:
Set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。
属性:
Set.prototype.size:返回Set对象的值的个数。
var set = new Set([1,2,3,4,5]);
set.size // 5
方法:
Set.prototype.add(value);在Set对象尾部添加一个元素。返回该Set对象。
var set = new Set([1,2,3,4,5]);
set.add(6);
set // Set [1,2,3,4,5,6];
Set.prototype.clear();移除Set对象内的所有元素。
var set = new Set([1,2,3,4,5]);
set.clear();
set // Set []
Set.prototype.delete(value);移除Set的中与这个值相等的元素,返回Set.prototype.has(value)在这个操作前会返回的值(即如果该元素存在,返回true,否则返回false)。Set.prototype.has(value)在此后会返回false。
var set = new Set([1,2,3,4,5]);
set.delete(1); //true
set // Set [2,3,4,5]
Set.prototype.has(value);返回一个布尔值,表示该值在Set中存在与否。
var set = new Set([1,2,3,4,5]);
set.has(1) // true;
set.has('abc') // false
Set.prototype.values();返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值.
var set = new Set([1,2,3,4,5]);
set.values() // SetIterator[1,2,3,4,5]
Set.prototype.keys();与values()方法相同,返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。
var set = new Set([1,2,3,4,5]);
set.keys() // SetIterator[1,2,3,4,5]
操作:
//交集:
let intersection = new Set([...set1].filter(x => set2.has(x)));
//差集:
let difference = new Set([...set1].filter(x => !set2.has(x)));
与数组在相互转换
//Set 转 Array
var set = new Set([1,2,3])
var arr = Array.from(set);
arr // [1,2,3]
//或者 使用扩展运算符
var arr = [...set]
//Array转 Set
var arr = [1,2,3]
var set = new Set(arr);
set // Set [1,2,3]
数组去重
var arr = [1,2,2,4,3,4,5,6,7,5];
var uniqArr = [...new Set(arr)];
uniqArr // [1,2,3,4,5,6,7]
//或者使用Array.from()
var uniqArr = Array.from(new Set(arr))
更多Set对象资料请参考MDN......
Map
Map对象保存键值对,并且能够记住的原始插入顺序。任何值(对象或者原始值)都可以作为一个键或一个值。
语法:
new Map([iterable])
参数:
Iterable 可以是一个数组
或者其他 iterable 对象,其元素为键值对(两个元素的数组,例如: [[ 1, 'one' ],[ 2, 'two' ]])。 每个键值对都会添加到新的 Map。null
会被当做 undefined。
描述:
一个Map对象在迭代时会根据对象中元素的插入顺序来进行 — 一个 for...of
循环在每次迭代后会返回一个形式为[key,value]的数组。
Objects和maps
- 一个Object的键只能是字符串或者Symbols,但一个Map的键可以是任意值,包括函数,对象,基本类型。
- Map中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map对象是按插入的顺序返回键值。
- 你可以通过
size
属性直接获取一个Map的键值对个数,而Object的键值对个数只能手动计算。 - Map可直接进行迭代,而Object的迭代需要先获取它的键数组,然后再进行迭代。
- Object都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
- Map 在涉及频繁增删键值对的场景下会有些性能优势。
属性:
Map.length:属性的length的值为0;
想要计算一个Map中的条目数量,使用Map.prototype.size.
Map 实例
所有的Map对象实例都会继承Map.prototype。
实例属性:
Map.prototype.size:返回Map对象的键/值对的数量。
var map = new Map([[1,2],[3,4]])
map.size // 2
方法:
Map.prototype.clear();移除Map对象的所有键/值对。
var map = new Map([[1,2],[3,4]])
map.clear();
Map.prototype.delete(key);如果Map对象中存在该元素,则移除它并返回true,否则如果钙元素不存在则返回false,
var map = new Map([[1,1],[2,2]])
map.delete(1); // true
map // Map(1){2=>2}
Map.prototype.entries() 返回一个新的Iterator对象,它按插入顺序包含了Map对象中每个元素的[key,value]数组。
var map = new Map([[1,1],[2,2]]);
var map2 = map.entries();
map2 // MapIterator{1=>1, 2=>2}
Map.prototype.get(key)返回键对应的值,如果不存在,则返回undefined;
var map = new Map([[1,1],[2,2]]);
map.get(1) // 1
Map.prototype.has(key);返回一个布尔值,表示Map实例是否包含键对应的值。
var map = new Map([[1,1],[2,2]]);
map.has(1) // true
map.has(4) // false
Map.prototype.keys() 返回一个新的Iterator对象,它按插入顺序包含了Map对象中每个元素的键。
var map = new Map([[1,1],[2,2]]);
map.keys() // MapIterator {1, 2 }
Map.prototype.values();返回一个新得Iterator对象,它按插入顺序包含了Map对象中每个元素的值。
var map = new Map([['键1','值1'],['键2','值2']]);
map.values() // MapIterator {"值1", "值2"}
Map.prototype.set(key,value) ;设置Map对象中键的值。返回该Map对象。
let myMap = new Map();
let keyObj = {};
let ketFunc = function(){};
let keyString = 'string';
myMap.set(keyObj ,'Obj值');
myMap.set(ketFunc ,'Func值');
myMap.set(keyString,'Str值');
myMap.size // 3
myMap // Map(3) {{…} => "Obj值", ƒ => "Func值", "string" => "Str值"}
Map与数组的关系
var kvArray = [['key1','value1'],['key2','value2']]
//数组转换为Map对象
let myMap = new Map(kvArray);
myMap.get('key1'); // 'value1'
//Map对象转换为数组
var myArr = Array.from(myMap);
myArr // [['key1','value1'],['key2','value2']]
//或者使用展开运算符
var myArr = [...myMap]
//也可以得到键,或者值的数组
console.log(Array.from(myMap.keys())); // 输出 ["key1", "key2"]
复制或合并Maps
//复制
let original = new Map([
[1, 'one']
]);
let clone = new Map(original);
console.log(clone.get(1)); // one
console.log(original === clone); // false. 浅比较 不为同一个对象的引用
//合并 会保持键的唯一性
let first = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let second = new Map([
[1, 'uno'],
[2, 'dos']
]);
// 合并两个Map对象时,如果有重复的键值,则后面的会覆盖前面的。
// 展开运算符本质上是将Map对象转换成数组。
let merged = new Map([...first, ...second]);
console.log(merged.get(1)); // uno
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three
更多Map对象资料请参考MDN