本文深入解析JavaScript中定义字符串的三种方式:字面量、String()函数与new String()构造函数。通过对比它们的类型、内存存储位置(栈与堆)以及属性访问机制,阐明基本类型与包装对象的区别。文章解释了为何基本类型能调用方法,以及为何无法为基本类型自定义属性,帮助开发者理解JS底层的设计逻辑。
JavaScript字符串定义的三种方式
在JavaScript开发中,字符串是最基础的数据类型之一。
在实际工作场景中,开发者通常采用以下三种方式来创建字符串变量:
1. 使用字面量语法:var str = 'hello'; 2. 使用String构造函数作为普通函数:var str1 = String('hello'); 3. 使用new关键字实例化String对象:var str2 = new String('hello');
下文将直接引用上述定义的三个变量进行后续分析。
通过这三种方式创建的字符串值'hello',均具备length属性以及indexOf()等常用方法。在日常编码实践中,这三种方式的表现看似并无显著差异。
三种定义方式的类型与相等性差异
然而,若深入探究其内部机制,我们会发现几个关键问题:
1. 这三种方式创建的'hello'在本质上是完全相同的吗?
2. 为什么基本类型的数据可以直接调用其对应的方法?
针对第一个问题:这三种方式定义出的'hello'是否完全一致?
console.log(str === str1) //true
console.log(str === str2) //false
console.log(str1 === str2) //false
通过比较可以发现,第三种方式(new String)创建的值与前两种方式创建的值并不相等。
???
要理解这一现象,首先需要明确一个核心概念:
使用new关键字创建的一定是对象。
因此,我们可以分别打印这三个变量的类型来验证:
console.log(typeof str) //string
console.log(typeof str1) // string
console.log(typeof str2) //object
这也正是它们之间无法通过严格相等(===)判断为相同的原因。
基本类型与引用类型的内存存储机制
接下来,我们将探讨数据类型与堆栈内存之间的关系。
为了深入理解原因,我们需要回顾JS的数据类型分类:
我们知道,String、Number和Boolean在JavaScript中属于基本类型(Primitive Types)。基本类型的数据直接存储在栈(stack)内存中,其数据大小是确定的,内存空间可以精确分配。
而引用类型(如对象)则存储在堆(heap)内存中。栈内存中仅保存指向堆内存的指针。这就是为什么当我们执行 a = {num:1}, b=a, b.num1 = 2 时,a.num1也会变为2的原因,因为变量a和b指向了堆内存中的同一个地址。
前两种方式定义的字符串直接存储在栈中且值相等,而第三种方法定义出的字符串在栈中仅保存一个指向堆内存的指针。
因此,这也是导致三种定义方式结果不一致的根本原因。
原始数据类型与包装对象的方法借用机制
那么,为什么基本类型可以直接调用其对应的方法呢?
让我们通过代码尝试一下:
console.log(str.length) // 5
str.say = 'world'
console.log(str.say) //undefined
console.log(str1.lengh) // 5
str1.say = 'world'
console.log(str1.say) //undefined
console.log(str2.lengh) // 5
str2.say = 'world'
console.log(str.say) //world
这里涉及到包装对象(Wrapper Objects)和原始数据类型(Primitives)的概念。
我们发现,第一种和第二种方式定义的字符串都可以访问length属性,
但是,为什么我们无法为它们自定义属性并进行访问呢?
在JavaScript中,数字、字符串和布尔值被称为原始(primitives)数据类型,而 new String() 或 new Number() 创建的是包装对象。
包装对象本质上仍然是对象。
这就是为什么我们打印三种类型的结果分别为:string(原始数据类型)、string(原始数据类型)和object(包装对象)。
我们可以理解,使用new创建的str2对象拥有String类的一系列方法。
console.log(str2.indexOf === String.prototype.indexOf) // true
那么,尝试验证第一种和第二种方法是否也具有同样的特性?
console.log(str.indexOf === String.prototype.indexOf) //true
console.log(str1.indexOf === String.prototype.indexOf) //true
但是:
str instanceof String // false
str1 instanceof String // false
str变量并不属于String对象,却拥有String的方法,这是为什么?
原因在于:
这是JavaScript语言的设计机制。
这是JavaScript语言的设计机制。
这是JavaScript语言的设计机制。
原始数据类型的属性与方法实际上是“借”来的。
一个原始数据类型值本身并不具备属性或方法,
当原始数据类型在运算中需要调用属性或方法时,JavaScript引擎会临时创建一个对应的包装对象,从中“借来”属性和方法供其使用。
因此,原始数据类型可以借用 new String() 或 new Number() 的所有方法,但其自身并不存储这些属性和方法。
这就是为什么前两种定义方式无法自定义属性,却可以使用对应类型方法的原因。
总结:
1. 第一种和第二种方法定义的是原始数据类型,
它们存储在栈内存中,
并在运算时向包装对象(new ..())借用方法和属性。
2. 第三种方法是包装对象,
栈内存中存储的是指向堆内存的指针,
而实际内容存储在堆内存中。
这也解释了为什么会出现一系列看似反常却又符合逻辑的现象。
哈哈,理解这一点后,很多疑惑便迎刃而解。
当然,既然涉及到了堆栈内存的概念,
我们就有必要进一步了解堆栈内存的具体定义,
以及它们之间的区别等更多内容。
以上就是本文的全部内容,希望这些内容对大家的学习或工作具有一定的参考价值。如有任何疑问,欢迎留言交流,感谢大家对本站的支持。