第1章 原始类型和引用类型

1.原始类型和引用类型

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对象想象成哈希表可以帮助你更好地理解对象结构。

Paste_Image.png

JavaScript 有好几种方法可以创建对象,或者说实例化对象。第一种是使用 new 操作符和构造函数。
构造函数就是通过 new 操作符来创建对象的函数——任何函数都可以是构造函数。根据命名规范,JavaScript中的构造函数用首字母大写来跟非构造函数进行区分。

var object = new Object();

因为引用类型不再变量中直接保存对象,所以本例中的 object 变量实际上并不包含对象的实例,而是一个指向内存中实际对象所在位置的指针(或者说引用)。这是对象和原始值之间的一个基本差别,原始值是直接保存在变量中。

当你将一个对象赋值给变量时,实际是赋值给这个变量一个指针。这意味着,将一个变量赋值给另外一个变量时,两个变量各获得了一份指针的拷贝,指向内存中的同一个对象。

var obj1 = new Object();
var obj2 = obj1;
Paste_Image.png

对象引用解除

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 new = 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;

MDN:Any object whose value is not undefined or null, including a Boolean oject whose value is false, evaluates to true when passed to a conditional statement.

1.9 总结

第一章的东西都是我们一些比较熟悉的知识。但是也有一些需要注意的地方:

  • 正确区分原始类型和引用类型
  • 对于 5 种原始类型都可以用typeof来鉴别,而空类型必须直接跟 null 进行全等比较。
  • 函数也是对象,可用 typeof 鉴别。其它引用类型,可用 instanceof 和一个构造函数来鉴别。(当然可以用 Object.prototype.toString.call() 鉴别,它会返回[object Array]之类的)。
  • 为了让原始类型看上去更像引用类型,JavaScript提供了 3 种封装类型。JavaScript会在背后创建这些对象使得你能够像使用普通对象那样使用原始值。但这些临时对象在使用它们的语句结束时就立刻被销毁。虽然可手动创建,但不建议。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容