JavaScript 的数据类型可分为
原始数据类型
和引用数据类型
。
原始数据类型:String
(字符串)、Number
(数字)、Boolean
(布尔)、Null
(空)、Undefined
(未定义)、Symbol
(es6新增,表示独一无二的值)、BigInt
(es10新增,比Number数据类型支持更大范围的整数值)
原始数据类型存储在栈(stack)
内存中,占据空间小、大小固定,属于被频繁使用的数据,所以放在栈中存储。
引用数据类型:Object
(Object本质上是由一组无序的名值对组成的),里面包含 function、Array、Date等等。
引用数据类型同时存储在栈(stack)
和堆(heap)
中,占据空间大、大小不固定。只是在栈
中存储了指针
,该指针指向堆
中该实体
的起始地址
,当解释器寻找引用值时,会先检索其在栈中的指针,取得后从堆中获取到实体
逻辑短路是对于逻辑运算而言的,是指,仅计算逻辑表达式中的一部分便能确定结果,而不对整个表达式进行运算的现象。
对于&&
运算符,当第一个操作数为false
时,将不会判断第二个操作数,因为此时无论第二个操作数为何,最后的运算结果一定是false
对于||
运算符,当地一个操作数为true
时,将不会判断第二个操作数,因为此时无论第二个操作数为何,最后的运算结果一定是true
Null
:是JS的关键字,用于描述空值
,对其执行 typeof 操作,返回object
,即为一个特殊的对象值,可以表示数字、字符串、对象是无值
的
Undefined
:是预定义的全局变量,其值为未定义
,它是变量的一种取值,表示变量没有初始化,当查询对象属性、数组元素的值时,如果返回 Undefined
则表示属性或者元素不存在,如果函数没有任何返回值,也返回undefined
注意:
虽然null 和 Undefined 是不同的,但是因为都表示值的空缺
,两者可以互换,因此,使用==
认为二者是相等的,需要用===
来区分
在js中类型转换只有三种情况:
Boolean()
方法)Number() 、parseInt()和parseFloat()方法
).toString()或String()方法
)注:null
和undefined
没有 .toString()
方法
原始值 | 转换值 | 结果 |
number string undefined、null 引用类型 |
布尔值 | number里除了+0 -0 NaN都是 false string里除了空字符串都为true null和undefined为false 引用类型为true |
number Boolean、function、Symbol 数组 对象 |
字符串 | number就是'number' true或false就是'true'或'false' 。 function就是'function(){}' 。Symbol就是'Symbol()' 数组 [1,2]就是'1,2' ;空数组转化就是空字符串 对象 就是 '[object Object]' |
string 数组 null 除了数组之外其他的引用类型 Symbol |
数字 | '1'就是1,'a'就是NaN 空数组[]为0,数组中有一个元素并且是数字 转为 该数字,其他情况都为NaN null为0 除数组之外的引用类型都为NaN Symbol报错 (Uncaught TypeError: Cannot convert a Symbol value to a number) |
此外还有一些操作符会存在隐式转换
封装一个判断数据类型的函数
,之前详细介绍过这里就不举例子多说了
全局的对象( global objects )或称标准内置对象
,不要和 "全局对象(global object)"
混淆。这里说的全局的对象是说在全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。
标准内置对象的分类
Infinity、NaN、undefined、null 字面量
eval()、parseFloat()、parseInt() 等
Object、Function、Boolean、Symbol、Error 等
Number、Math、Date
String、RegExp
Array
Map、Set、WeakMap、WeakSet
SIMD 等
JSON 等
Promise、Generator 等
Reflect、Proxy
Intl、Intl.Collator 等
js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般我们经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象
已在作用域中声明但还没有赋值
的变量,是 undefined
的。相反,还没有在作用域中声明
过的变量,是 undeclared
的。
对于 undeclared
变量的引用,浏览器会报引用错误,如 ReferenceError: b is not defined 。
但是我们可以使用 typeof
的安全防范机制来避免报错,因为对于 undeclared
(或者 not defined
)变量,typeof
会返回 "undefined"
。
var obj = {}
obj.valueOf() // {}
obj.toString() // "[object Object]"
var arr = []
arr.valueOf() // []
arr.toString() // ""
作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。
变量的作用域无非就是两种:
全局变量
和局部变量
。
全局作用域
:
最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的
局部作用域
:
和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部
作用域链: 作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和
函数。
作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。
当我们查找一个变量时,如果当前执行环境中没有找到,我们可以沿着作用域链向后查找。
执行环境
想要理解js怎么链式查找,就得先了解js的执行环境
每个函数运行时都会产生一个执行环境,而这个执行环境怎么表示呢?
js为每一个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的。
js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。
Object构造函数创建
var Person = new Object();
Person.name = "xiaobai";
Person.age = 26;
创建了Object引用类型的一个新实例,然后把实例保存在变量Person中。
使用对象字面量表示法
var Person = {};//相当于 var Person = new Object();
var Person = {
name:'xiaobai',
age:26
}
对象字面量是对象定义的一种简写形式。和Object构造函数创建对象其实都是一样的。
这两种写法的缺陷及优化:
缺陷:它们都是用了一个接口创建很多对象,会产生大量的重复代码。
优化:所以为了避免过多的重复代码,把创建对象的过程封装在函数体内,通过函数的调用直接生成对象
使用工厂模式创建对象
function Person(name,age,job){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayHello = function(){
console.log("hello!!!")
};
return obj;
}
var person1 = Person('xiaobai',26,'Web');
var person2 = Person('laowang',35,'QA');
在使用工厂模式创建对象的时候,在Person函数中返回的是一个对象。我们无法判断返回的对象是一个什么样的类型。于是又出现了构造函数创建对象的模式。
使用构造函数创建对象
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayHello = function(){
console.log('hello!!!');
};
}
var person1 = new Person('xiaobai',26,'Web');
var person2 = new Person('laowang',35,'QA');
对比工厂模式,可以发现一下区别:
这里用instanceof来检测对象类型
console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true
构造函数确实很好用,但是它也有它的缺点:
就是每个方法都要在每个实例上重新创建一遍,方法指的就是我们在对象里面定义的函数。如果方法数量很多就会占用很多不必要的内存。
于是就出现了原型创建对象模式。
原型创建对象模式
function Person(){}
Person.prototype.name = 'xiaobai';
Person.prototype.age = 26;
Person.prototype.job = 'Web';
Person.prototype.sayHello = function(){
console.log('hello!!!')
};
var person1 = new Person();
var person2 = new Person();
person1.name = "laowang";
console.log(person1.name); // 'laowang' ---来自实例
console.log(person2.name); // 'xiaobai' ---来自原型
使用原型创建对象的方式,可以让所有对象实例共享它所包含的属性和方法。
当为对象实例添加一个属性时,这个属性就会屏蔽
原型对象中保存的同名属性。
这时候我们就可以使用构造函数模式与原型模式结合的方式,构造函数
模式用于定义实例属性
,而原型
模式定义方法和共享的属性
。
组合使用构造函数模式和原型模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor:Person,
sayName:function(){
console.log(this.name)
}
}
var person1 = new Person('xiaobai',26,'Web')