JS数据基本类型和引用类型
基本类型:undefined、null、string、number、boolean、symbol(ES6)
普通基本类型:undefined、null、symbol(ES6)
特殊基本包装类型:String、Number、Boolean
引用类型:Object、Array、RegExp、Date、Function
区别:引用类型值可添加属性和方法,而基本类型值则不可以。
基本类型
- 基本类型的变量是存放在栈内存(Stack)里的
- 基本数据类型的值是按值访问
- 基本类型的值是不可变的
- 基本类型的比较是它们的值的比较
引用类型
- 引用类型的值是保存在堆内存(Heap)中的对象(Object)
- 引用类型的值是按引用访问的
- 引用类型的值是可变的
- 引用类型的直接比较是引用地址的比较
(NaN是数字类型) JS数据类型
包装类
中文里面JS的String、Number、Boolean翻译成包装类,那么它们和普通的string、number、boolean具体有什么差别呢? 以string为例子我们来看下基本数据类型和其包装类的差别和关系是什么?
string vs String
我们知道基本数据类型的值是直接保存在栈内存当中的,而且在内存中是连续保存的,按值访问。像null这种基本数据类型不能进行.property
的操作,因为它就是个值。
按理说.property
的方式基础数据类型应该做不到的,但是在日常使用中,对于也是基本数据类型的string,直接.length
就可以获取到它的长度。
var a='test';
console.log(a.length);//4
var b=null;
console.log(b.length);//Uncaught TypeError: Cannot read property 'length' of null
为什么string就可以,null就不可以呢?
前面的类型总结当中我们可以看到js当中除了string
还有String
,这两者并不是一个东西。
String(val)是什么?在做什么?
以String为例子,我们可以看到在ECMA262的文档中与String相关的有这几种定义:
- String value:内存中有限长度的有序数值的原始值
- String type:所有
String value
的组合 - String Object:显然这是个
Object
,是js内置String constructor
的实例。当使用new String(str)
形式调用的时候就会创建该对象。
我们可以看到String相关的几种定义里面,涉及到了Object,那么.length
这些属性是不是通过Object来实现的呢?
var a = String('a');
var a2 = String('a');
console.log(a === a2);
//true
var b = new String('b');
var b2 = new String('b');
console.log( b === b2);
// false
console.log( b == b2);
// false
果然,我们看到new String('b')
最后是返回了一个特殊的String对象(本质还是Object),具有length属性,所以,当然直接b.length
就可以访问到它的长度。
而且,我们可以看到实例b的__proto__
指向的是String,展开我们可以看到一个特殊的String.prototype
对象,我们平常常用的一些对string的操作方法都定义在这个对象上了。
从现在来看,对于new String(val)
怎么能访问到.length
这些属性,还是比较好理解的,因为它返回的就是一个特殊的String对象的实例,所以当然可以访问到String原型上定义的各种方法啦。也就是说,String的length
访问其实归根到底,还是借助特殊的Object来实现的。
new String()和String()的区别
我们知道new关键字的过程涉及到新对象的创建,所以,new String(str)
的结果返回的一个新的String实例,所以,b和b2保存的是两个对象的引用,他们的引用地址不一样,直接比较的话,逻辑引用类型的比较是一样的,结果就是不相等。
在ecma262当中,对于String实例的属性是这样描述的:
String instances have a "length" property, and a set of enumerable properties with integer-indexed names.
字符串实例属于String exotic objects
。实例继承了String prototype object
的属性,也有[[StringData]]
内置属性。同时String instances
也有length
属性。
那为什么String(str)的值还是能按值比较呢?这个看起来就是个构造函数方法哇?像Object即使不结合new去使用,最后返回的引用实例类型也不一样。
var c = Object('a');
var d = Object('a');
console.log(c === d);
//false
然后我们看一下ecma262/#sec-string-constructor当中对于String constructor(也就是String(...))
也中有两句是这么描述的:
- creates and initializes a new String object when called as a constructor.
- 作为构造函数被调用的时候(就是被new的时候),会创建和初始化一个新的String对象
- performs a type conversion when called as a function rather than as a constructor.
- 被当做函数调用的时候(就是直接String(...)),会进行类型转换
也就是说当不用new的时候,String(...) === toString(...) 用代码来验证一下:
var a = String(1);
var b = '1';
console.log( a === b);
// true
var c = String({a:1});
var d = "[Object Object]"
console.log( c === d);
// true
console.log(d.length);
//15
从上面的结果来看,String(val)
其实跟toString
的效果没什么差别,最后也是返回一个普普通通的string
字符串,但是我们看到它还是可以调用.length
,这是为啥?
我们来看这句代码输出的结果
console.log('1'.__proto__);
我们可以看到,'1'
字符串是可以直接取到输出原型对象的,按理说'1'就是个简单的String value,它不是通过new String(val)
创建的,为什么可以输出__proto__
呢?
而且,我们对其添加属性的话并不会成功,如下代码可以正常执行,但是添加的属性最后访问到的数值是undefined
。
var a = '2';
a.haha = '123';
console.log(a);//2
console.log(a.haha);//undefined
不是说好的,String value是按值访问的吗,为什么String value还可以访问属性呢?
关键就是在这个.
操作上。看到了stackoverflow
上的回答,终于知道点操作的学名原来叫Property Accessors
。
stackoverflow/difference-between-the-javascript-string-type-and-string-object
Property Accessors(.XXX)的时候发什么了什么
在我们看ecma262中MemberExpression.IdentifierName(即类似a.b)
这种方式获取属性的时候会发生什么?
a.property
时关键的方法就是evaluate-property-access-with-identifier-key
在这个方法当中,会对调用ToObject(a)
方法。
所以关键就在于,ToObject方法到底对不同数据类型的参数做了什么操作?
在这个表格中,我们看到了String
类型那一行三个显眼的单词new String object
,结合我们最开始了解到的new String()
做了啥了,我们就清楚了,原来对于普通String value来说,当对它调用.
操作的时候,js会默默的用ToObject
操作,调用new String(val)
创建一个新的String Object。这个过程当中,会通过reference操作,查找新的String Object上是不是具有所要访问的属性值。
所以这个length
其实不是'1'
这个String value的属性,而是String Obj
实例的属性。
所以从代码上来说,我们可以简单理解为:
'1'.length的时候不是因为'1'具有`length`属性,而是创建了个String对象实例,访问到了这个实例上的length属性。
var a = new String('1');// a是个String object是个对象指向了String.prototype对象
a.length;
有一个细节要注意,因为new String(val)
返回值其实就是一个object
,ToObject(obj)
返回的是这个obj本身,所以ee.heihei = '123'
,相当于是直接对ee
添加了个属性,因此后续还是能够访问到它。
var ee = new String('a');
console.log(ee.heihei);
//undefined
ee.heihei = '123';
console.log(ee);
对于'1'.haha
访问不到,是undefined,很多文章解释是,包装类创建完后会把对象销毁,在ECMA规范中我貌似没有直接找到与销毁相关的描述,所以对于为什么访问不到我大概的理解是:
但是对于'1'.haha='123'
来说,每次.
操作的时候都是创建了新的String obj
'1'.haha = '123';
创建了String obj
console.log('1'.haha);
又创建了另一个String obj,每一个全新的String obj都没有haha属性
所以即使你做了赋值操作,也不能在后续访问到其上面的haha
属性的数值。
从ToObject
的处理逻辑,null/undefined
调用.
操作就会报错的真正原因就很清楚啦。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!