1.1 什么是类型
原始类型 保存为简单数据值. 引用类型 保存为对象, 其本质是指向内存位置的引用.
为了让开发者能够把原始类型和引用类型按相同的方式处理, JavaScript 花费了很大的努力来保证语言的一致性.
其他编程语言用栈存原始类型, 用对存储引用类型.
而 JavaScript 则完全不同: 它使用一个变量对象追踪变量的生存期.
原始值被直接保存在变量对象内, 而引用值则作为一个指针保存在变量对象内, 该指针指向实际对象在内存中的存储位置.
1.2 原始类型
原始类型代表照原样保存的一些简单数据.
JavaScript 共有 5 种原始类型:
boolean 布尔, 值为 true or false
number 数字, 值为任何整型或浮点数值
string 字符串, 值为由单引号或双引号括住的单个字符或连续字符
null 空类型, 仅有一个值: null
undefined 未定义, 只有一个值: undefined(undefined 会被赋给一个还没有初始化的变量)
JavaScript 和许多其他语言一样, 原始类型的变量直接保存原始值(而不是一个指向对象的指针).
- var color1 = "red";
- var color2 = color1;
- console.log(color1); // "red"
- console.log(color2); // "red"
- color1 = "blue";
- console.log(color1); // "blue"
- console.log(color2); // "red"
鉴别原始类型
鉴别原始类型的最佳方式是使用 typeof 操作符.
- console.log(typeof "Nicholas"); // "string"
- console.log(typeof 10); // "number"
- console.log(typeof true); // "boolean"
- console.log(typeof undefined); // "undefined"
至于空类型 (null) 则有些棘手.
console.log(typeof null); // "object"
对于 typeof null, 结果是 "object".(其实这已被设计和维护 JavaScript 的委员会 TC39 认定是一个错误. 在逻辑上, 你可以认为 null 是一个空的对象指针, 所以结果为 "object", 但这还是很令人困惑.)
判断一个值是否为空类型 (null) 的最佳方式是直接和 null 比较:
console.log(value === null); // true or false
注意: 以上这段代码使用了三等号 (全等 ===), 因为三等号(全等) 不会将变量强制转换为另一种类型.
- console.log("5" == 5); // true
- console.log("5" === 5); // false
- console.log(undefined == null); // true
- console.log(undefined === null); // false
原始方法
虽然字符串, 数字和布尔值是原始类型, 但是它们也拥有方法(null 和 undefined 没有方法).
- var name = "Nicholas";
- var lowercaseName = name.toLowerCase(); // 转为小写
- var count = 10;
- var fixedCount = count.toFixed(2); // 转为 10.00
- var flag = true;
- var stringFlag = flag.toString(); // 转为 "true"
- console.log("YIBU".charAt(0)); // 输出 "Y"
尽管原始类型拥有方法, 但它们不是对象. JavaScript 使它们看上去像对象一样, 以此来提高语言上的一致性体验.
1.3 引用类型
引用类型是指 JavaScript 中的对象, 同时也是你在该语言中能找到最接近类的东西.
引用值是引用类型的实例, 也是对象的同义词(后面将用对象指代引用值).
对象是属性的无序列表. 属性包含键 (始终是字符串) 和值. 如果一个属性的值是函数, 它就被称为方法.
除了函数可以运行以外, 一个包含数组的属性和一个包含函数的属性没有什么区别.
创建对象
有时候, 把 JavaScript 对象想象成哈希表可以帮助你更好地理解对象结构.
JavaScript 有好几种方法可以创建对象, 或者说实例化对象. 第一种是使用 new 操作符和构造函数.
构造函数就是通过 new 操作符来创建对象的函数 -- 任何函数都可以是构造函数. 根据命名规范, JavaScript 中的构造函数用首字母大写来跟非构造函数进行区分.
var object = new Object();
因为引用类型不再变量中直接保存对象, 所以本例中的 object 变量实际上并不包含对象的实例, 而是一个指向内存中实际对象所在位置的指针(或者说引用). 这是对象和原始值之间的一个基本差别, 原始值是直接保存在变量中.
当你将一个对象赋值给变量时, 实际是赋值给这个变量一个指针. 这意味着, 将一个变量赋值给另外一个变量时, 两个变量各获得了一份指针的拷贝, 指向内存中的同一个对象.
- var obj1 = new Object();
- var obj2 = obj1;
对象引用解除
JavaScript 语言有垃圾收集的功能, 因此当你使用引用类型时无需担心内存分配. 但最好在不使用对象时将其引用解除, 让垃圾收集器对那块内存进行释放. 解除引用的最佳手段是将对象变量设置为 null.
- var obj1 = new Object();
- // dosomething
- obj1 = null; // dereference
添加删除属性
在 JavaScript 中, 你可以随时添加和删除其属性.
- var obj1 = new Object();
- var obj2 = obj1;
- obj1.myCustomProperty = "Awsome!";
- console.log(obj2.myCustomProperty); // "Awsome!" 因为 obj1 和 obj2 指向同一个对象.
1.4 内建类型实例化
内建类型如下:
Array 数组类型, 以数字为索引的一组值的有序列表
Date 日期和时间类型
Error 运行期错误类型
Function 函数类型
Object 通用对象类型
RegExp 正则表达式类型
可使用 new 来实例化每一个内建引用类型:
- var items = new Array();
- var now = new Date();
- var error = new Error("Something bad happened.");
- var func = new Function("console.log('HI');");
- var object = new Object();
- var re = new RegExp();
字面形式
内建引用类型有字面形式. 字面形式允许你在不需要使用 new 操作符和构造函数显示创建对象的情况下生成引用值. 属性的键可以是标识符或字符串(若含有空格或其他特殊字符)
- var book = {
- name: "Book_name",
- year: 2016
- }
上面代码与下面这段代码等价:
- var book = new Object();
- book.name = "Book_name";
- book.year = 2016;
虽然使用字面形式并没有调用 new Object(), 但是 JavaScript 引擎背后做的工作和 new Object() 一样, 除了没有调用构造函数. 其他引用类型的字面形式也是如此.
1.5 访问属性
可通过 . 和 中括号 访问对象的属性. 中括号 [] 在需要动态决定访问哪个属性时, 特别有用. 因为你可以用变量而不是字符串字面形式来指定访问的属性.
1.6 鉴别引用类型
函数是最容易鉴别的引用类型, 因为对函数使用 typeof 操作符时, 返回 "function".
- function reflect(value){
- return value;
- }
- console.log(typeof reflect); // "function"
对其他引用类型的鉴别则较为棘手, 因为对于所有非函数的引用类型, typeof 返回 object. 为了更方便地鉴别引用类型, 可以使用 JavaScript 的 instanceof 操作符.
- var items = [];
- var obj = {};
- function reflect(value){
- return value;
- }
- console.log(items instanceof Array); // true;
- console.log(obj instanceof Object); // true;
- console.log(reflect instanceof Function); // true;
instanceof 操作符可鉴别继承类型. 这意味着所有对象都是 Oject 的实例, 因为所有引用类型都继承自 Object.
虽然 instanceof 可以鉴别对象类型 (如数组), 但是有一个列外. JavaScript 的值可以在同一个网页的不用框架之间传来传去. 由于每个网页拥有它自己的全局上下文 -- Object,Array 以及其他内建类型的版本. 所以当你把一个对象(如数组) 从一个框架传到另外一个框架时, instanceof 就无法识别它.
1.8 原始封装类型
原始封装类型有 3 种: String,Number 和 Boolean. 当读取字符串, 数字或布尔值时, 原始封装类型将被自动创建.
- var name = "Nicholas";
- var firstChar = name.charAt(0); // "N"
这在背后发生的事情如下:
- var name = "Nichola";
- var temp = new String(name);
- var firstChar = temp.charAt(0);
- temp = null;
由于第二行把字符串当成对象使用, JavaScript 引擎创建了一个字符串的实体让 charAt(0) 可以工作. 字符串对象的存在仅用于该语句并在随后销毁(一种被称为自动打包的过程). 为了测试这一点, 试着给字符串添加一个属性看看它是不是对象.
- var name = "Nicholas";
- name.last = "Zakas";
- console.log(name.last); // undefined;
下面是在 JavaScript 引擎中实际发生的事情:
- var name = "Nicholas";
- var temp = new String(name);
- temp.last = "Zakas";
- temp = null; // temporary object destroyed
- var temp = new String(name);
- console.log(temp.last);
- temp = null;
新属性 last 实际上是在一个立刻就被销毁的临时对象上而不是字符串上添加. 之后当你试图访问该属性时, 另一个不同的临时对象被创建, 而新属性并不存在.
虽然原始封装类型会被自动创建, 在这些值上进行 instanceof 检查对应类型的返回值却是 false. 这是因为临时对象仅在值被读取时创建. instanceof 操作符并没有真的读取任何东西, 也就没有临时对象的创建.
当然你也可以手动创建原始封装类型.
- var str = new String("me");
- str.age = 18;
- console.log(typeof str); // object
- console.log(str.age); // 18
如你所见, 手动创建原始封装类型实际会创建出一个 object. 这意味着 typeof 无法鉴别出你实际保存的数据的类型.
另外, 手动创建原始封装类型和使用原始值是有一定区别的. 所以尽量避免使用.
- var found = new Boolean(false);
- if(found){
- console.log("Found"); // 执行到了, 尽管对象的值为 false
- }
这是因为一个对象 (如 {} ) 在条件判断语句中总被认为是 true;
1.9 总结
第一章的东西都是我们一些比较熟悉的知识. 但是也有一些需要注意的地方:
正确区分原始类型和引用类型
对于
5
种原始类型都可以用 typeof 来鉴别, 而空类型必须直接跟 null 进行全等比较.
函数也是对象, 可用 typeof 鉴别. 其它引用类型, 可用 instanceof 和一个构造函数来鉴别.(当然可以用
Object.prototype.toString.call()
鉴别, 它会返回 [object Array]之类的).
为了让原始类型看上去更像引用类型, JavaScript 提供了
3
种封装类型. JavaScript 会在背后创建这些对象使得你能够像使用普通对象那样使用原始值. 但这些临时对象在使用它们的语句结束时就立刻被销毁. 虽然可手动创建, 但不建议.
来源: https://juejin.im/post/5c151758f265da61461e0ec8