《红宝书》 |原始数据类型
相关文章:什么是引用类型
typeof操作符
判断变量的数据类型并返回字符串(主要用于判断原始数据类型):
let message="str";
console.log(typeof message);//"string"
console.log(typeof 95);//"number"
Undefined
undefined只有一个值,即undefined。当使用var或let声明变量时,只要没有初始化,该变量的值就默认为undefined:
let a
console.log(a)//"undefined"
console.log(a==undefined)//true
未初始化的变量与未定义的变量是有区别的,对于未定义的变量,会提示报错:
console.log(b)//报错
如果使用typeof
,则不管是未初始化还是未定义,都会返回undefined。所以建议在声明变量的同时就要初始化,这样,返回undefined是我们就知道这个变量是没有声明,而不是声明了未初始化。
let a
console.log(typeof a)//"undefined"
console.log(typeof b)//"undefined"
Null
null只有一个值,即null。null表示一个空对象的指针,所以在定义将来要保存对象值的变量时,建议使用null来初始化。
let car=null;
console.log(typeof car)//"object"
这样,可以判断该变量是不是null来确定该变量是否后来被赋予了一个对象的引用:
if(cae!=null){
//如果car被赋予了对象引用,执行
}
Boolean
它有两个字面量:true和false。其他类型的值也有相应布尔值的等价形式。可以通过Boolean()
转型函数将其他类型的值转为布尔值:
//字符串类型
Boolean('')//false
Boolean('111')//true
//数值类型
Boolean(0)//false
Boolean(NaN)//false
Boolean(1)//true
//对象
Boolean(null)//false
Boolean({a:1})//true
Number
基本概念
Number
使用IEEE 754 格式表示整数和浮点数。
-
十进制整数
let initNum=55
-
八进制整数
前缀为
0
,然后是八进制数字0~7,如果字面量中包含的数值超出了有效范围,会忽略前缀0,后面的数字会被当成十进制数。(在严格模式下无效)let octalnum=070 //八进制56 let octalnum=079 //无效八进制值,当成79
-
十六进制整数
前缀
0x
,然后是十六进制数字0~9
以及A~F
let hexnum=0xA //十六进制10
-
浮点数
数值中必须包含小数点,而且小数点后面必须至少有一个数字。
let floatnum=1.1 let floatnum=0.1 let floatnum=.1 //有效 但不推荐
小数点后没有数字、数值本身就是整数的情况下,会被转化为整数:
let floatnum=1. //转为整数1 let floatnum=10.0 //转为整数10
对非常大或者非常小的数,浮点值可用科学计算法表示,格式是在数值后加上e + 几次幂,如下:
let floatnum=3.125e7 //等于31250000
由于舍入错误,很难测试特定的浮点值。如0.1+0.2实际上会等于0.30000000000000004,所以不建议检测特定的浮点值。
值的范围
ECMAScript可以表示的最小值保存在Number.MIN_VALUE
中,最大值保存在Number.MAX_VALUE
中,如果某个值超过js可以表示的范围,那么该数字会自动转化为Infinity
或-Infinity
。
NaN
Number有一个特殊的数值叫NaN
,意思是Not a Number,即不是数值。用于表示要返回数值的操作失败了,比如0除以任何数都会导致错误:
console.log(0/1) //NaN
console.log(0/0) //NaN
console.log(-0/+0) //NaN
如果分子非0,分母是0,则返回Infinity
或-Infinity
:
console.log(5/0) //Infinity
console.log(-5/0) //-Infinity
任何涉及NaN
的操作始终返回NaN
:
console.log(NaN/10) //NaN
NaN
不等于包含NaN
在内的任何值:
console.log(NaN==NaN); //false
isNaN()
可以判断参数是否“不是数值”:
isNaN(NaN) //true 不是数值
isNaN(10) //false 是数值
//它会尝试把参数转为数值
isNaN("10") //false (被转化为了10,所以是数值)
isNaN(true) //false (被转化为了1,所以是)
//无法转换的则判定为“不是数值”
isNaN("blue") //true
数值转换
Number()
:将任何数据转为数值(转整数)Number(true) //1 Number(10) //10 Number(null) //0 Number(undefined) //NaN //为字符串时: Number("") //0 Number("1") //1 Number("1.1") //1.1 Number("011") //11 为八进制时,则转为十进制值 Number("0xf") //15 为十六进制时,转为与该十六进制值对应的十进制整数 Number("hello") //NaN 若为上述外的其他字符,返回NaN
parseInt()
:将字符串转为数值//1.为数值时: parseInt(1) //1 parseInt(1.1) //1 //2.为字符串时: //如果第一个字符不是数值、加号或减号,则返回NaN parseInt("") //NaN parseInt("a") //NaN //如果第一个字符是数值、加号或减号,则依次往后检测,直到字符串末尾或碰到非数值字符 parseInt("1234b") //1234 parseInt("22.5") //22 //可以识别八进制、十六进制 parseInt("0xA") //0 parseInt("0xAF",16) //175 后面的数表示进制数,防止混淆 parseInt("AF",16) //175 parseInt("AF") //NaN parseInt("10",2) //2 按二进制 parseInt("10",8) //8 按八进制 parseInt("10",10) //10 按十进制 parseInt("10",16) //16 按十六进制
parseFloat()
:将字符串转为数值(转小数)//忽略字符串开头的0 parseFloar("09") //9 //解析到字符串末尾或者碰到非数值字符为止,其中第一个小数点有效 parseFloat("0.01") //0.01 parseFloat("0.01a") //0.01 parseFloat("0.01.11") //0.01 parseFloat("1234b") //1234 //检测不了十六进制 始终返回0 parseFloat("0xA") //0
String
字符串的表示
String(字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号、单引号或反引号标识:
let str="Jack";
let str='Jack';
let str=`Jack`;
字符串拼接
用+
连接,拼接两个字符串。
console.log('abc'+'d') //"abcd" 如果另一个是数字,不会计算结果;
console.log('2'+1) //"21"
console.log('2'+'1') //"21"
JS转义字符
转义字符主要用于表示非打印字符或其他用途字符。字符串内部如果存在特殊字符(如'
,"
,\
等),需要采用\
来进行转义,否则会报错:
JS定义反斜杠加上字符可以表示字符自身:
let text='i\'am'
console.log(text) //i'am
注意,一些字符加上反斜杠后会表示特殊字符而不是原字符本身,这些特殊转义字符被称为转义序列,具体说明如表所示:
下面是使用换行转义字符的例子:
let text="first line\nsecond line"
console.log(text)
//first line
//second line
//它们可以放在任意位置,且作为单个字符被解释:
console.log(text.length) //3
html转义字符分成三部分:第一部分是一个
&
符号;第二部分是实体(Entity)名字或者是#
加上实体(Entity)编号;第三部分是一个分号。比如,要显示小于号(<),就可以写<
; 或者<
;。它与JS转义字符的区别在于书写方式不一样。文章参考:https://blog.csdn.net/xinzhu1990/article/details/7032301
字符串不可变
字符串是不可变的(immutable),即一旦创建,值就不能变了。要修改某个变量的字符串值,必须先销毁原始的字符串,然后重新赋值一个新的字符串:
let lang="english"
lang="chinese" //之前的english被销毁
转化为字符串
转化为字符串的方法有两种:一种是toString()
,一种是String()
。
toString()
:返回自身的一个副本,可以转换数值、布尔值、对象和字符串值
一般情况下不接受参数。当然也可以接收一个底数作为参数,即以什么底数来输出数值的字符串表示:let age=11; let ageString=age.toString() //"11" let found=true; let foundString=found.toString() //"true"
let num=10 num.toString() //"10" (默认情况下返回十进制字符串表示) num.toString(2) //"1010" 二进制的字符串表示 num.toString(8) //"12" 八进制的字符串表示 num.toString(10) //"10" 十进制的字符串表示 num.toString(16) //"a" 十六进制的字符串表示
String()
:这个方法不仅可以转换数值、布尔值、对象和字符串值, 还可以转换null
和undefined
,它直接返回这两个值的字面量文本:let value=null String(value)//"null" let value2=undefined String(value2)//"undefined"
模板字面量
使用模板字面量可以保留换行字符实现跨行:
//1.一般情况下通过\n换行
let str=fiist line\nsecond line;
//2.模板字符串直接换行
let str=`first
line`
console.log(str)
//first line
//second line
顾名思义,模板字面量在定义模板时特别有用:
let pageHtml=`
<div>
<a href="#">
jack
</a>
</div>`
字符串插值
模板字面量最常用的特性就是支持字符串插值。
实际上模板字面量不是字符串,而是一种特殊的JavaScript表达式。模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量会从离它们最近的作用域中取值。字符串插值通过在${}
中使用一个JavaScript表达式实现:
let age=5;
let name='Jack';
//1.以前的字符串插值
let str=name+'is'+age+'years old.'
//2.模板字面量插值
let str=`${name} is ${age} years old.`
console.log(str)//"Jack is 5 years old."
在插值表达式中可以调用函数和方法:
function capitalize(word){
//调用方法:toUpperCase()和slice()
return `${word[0].toUpperCase()}${word.slice(1)}`
}
//调用函数:capitalize()
console.log(`${capitalize('hello')},${capitalize('world')}!`) //Hello,World
toUpperCase():该方法将字符串转为大写形式并返回;
slice(开始索引,结束索引):提取某个字符串的一部分,并返回一个新的字符串。没有结束索引表示取到最后
模板字面量标签函数
标签函数可以自定义插值行为。标签函数会接收原始字符串数组和对每个表达式求值的结果。下面定义一个标签函数:
//标签函数
function sumpleTag(stringArr,Expression1,Expression2,Expession3){
console.log(stringArr)
console.log(Expression1)
console.log(Expression2)
console.log(Expession3)
return 'result'
}
只要将标签函数的函数名称放在模板字面量前面就可以使用:
let a=6;
let b=9;
sumpleTag`The result is: ${a}+${b}=${a+b}!`
//["The result is: ", "+", "=", "!"] //原始字符串数组
//6 //${a}的求值的结果
//9 //${b}的求值的结果
//15 //${a+b}的求值的结果
//"result" //返回的结果
需要注意的是,如果模板字面量的开头和结尾没有字符串,那么会当成空字符串""
处理:
sumpleTag`${a}+${b}=${a+b}`
//["", "+", "=", ""]
//6
//9
//15
//"result"
应用:很多时候无法知道传过来的表达式参数有多少,这时可以用到arguments
,arguments
可以获取每个传入的参数值,它是一个伪数组:
function sumpleTag(stringArr){
console.log(stringArr)
for(let i=0;i<arguments.length;i++){
console.log(arguments[i])
}
return result
}
或者使用rest
参数(形式为“…变量名”),用于获取函数的多余参数:
function sumpleTag(stringArr,...expressions){
console.log(stringArr)
for(const v of expressions){
console.log(v)
}
return 'result'
}
标签函数函数可以对传进来的原始字符串和表达式结果进行重整,最后返回一个新的字符串。它的一个重要应用是过滤HTML字符串,防止用户输入恶意内容:
var message = SaferHTML`<p>${sender} has sent you a message.</p>`;
function SaferHTML(templateData) {
var s = templateData[0];
for (var i = 1; i < arguments.length; i++) {
var arg = String(arguments[i]); //将表达式结果转为字符串
// 替换掉传过来的特殊字符如"&"、"<"、">"
s += arg.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
s += templateData[i];
}
return s;
}
上面代码中,sender
变量往往是用户提供的,经过SaferHTML
函数处理,里面的特殊字符都会被转义
var sender = '<script>alert("abc")</script>'; //恶意代码
var message = SaferHTML`<p>${sender} has sent you a message.</p>`;
console.log(message);
// <p><script>alert("abc")</script> has sent you a message.</p>
原始字符串
使用模板字面量可以获取转义后的内容
//Unicode示例
//\u00A9 是版权符号
console.log(`\u00A9`) // ©
如果不想获取转义后的的字符,可以使用String.raw
标签函数:
console.log(String.raw`\u00A9`) //\u00A9
另外,可以通过标签函数的第一个参数(字符串数组)的.raw属性取得每个字符串的原始内容:
function printRaw(stringArr){
console.log('实际上的')
for(const string of stringArr){
console.log(string)
}
console.log('转换后的')
for(const rawString of stringArr.raw){
console.log(rawString)
}
}
printRaw`\u00A9${'split'}\n`
Symbol
symbol也称符号,是原始值,其实例是唯一、不可变的。用途是保证对象属性使用唯一标识符,以免属性冲突。
符号实例化
let sym=Symbol()
console.log(typeof sym) //symbol
可以给Symbol()
传入参数,表示对该符号的描述,它与符号定义或标识完全无关。
let sym1=Symbol('foo')
let sym2=Symbol('foo')
console.log(sym1==sym2) //false
只要创建symbol()
实例并将其作对象的新属性,就可以保证它不会覆盖已有的对象属性
全局符号注册
Symbol.for()
:全局注册符号Symbol.keyFor()
:检查全局注册表
当许多地方需要共享和重用符号实例时,可以用Symbol.for()
来注册全局符号,接收字符串作为描述,此时该描述同时是一个标识。
//全局创建新符号
let sym1=Symbol.for('foo')
//先检查全局注册表,判断是否存在对应的标识,没有则创建新实例,有则返回旧实例
let sym2=Symbol.for('foo')//这里是返回旧实例
console.log(sym1===sym2) //true
即使描述相同,全局注册的符号与Symbol()
定义的符号是不相同的。
let sym1=Symbol('foo')
let sym2=Symnol.for('foo')
console.log(sym1===sym2)//false
可用Symbol.keyFor()
检查全局注册表。接收符号,返回该全局符号对应的字符串键。
//1.全局符号
let sym1=Symbol.for('foo')
console.log(Symbol.keyFor(sym1)) //foo
//2.普通符号:若查询的不是全局符号,返回undefined
let sym2=Symbol('bar')
console.log(Symbol.keyFor(sym2)) //undefined
【应用】使用符号作为属性
let s1=Symbol('foo')
let s2=Symbol('bar')
let s3=Symbol('baz')
let s4=Symbol('qux')
//方式1:
let obj={
[s1]:'fooValue'
}
//{Symbol(foo): "fooValue"}
//方式2:给已知对象添加属性
Object.defineProperty(obj,s2,{value:'barValue'})
//{Symbol(foo): "fooValue", Symbol(bar): "barValue"}
//方式3:给已知对象添加多个属性
Object.defineProperties(obj,{
[s3]:{value:'bazValue'},
[s4]:{value:'quxValue'}
})
//{Symbol(foo): "fooValue", Symbol(bar): "barValue", Symbol(baz): "bazValue", Symbol(qux): "quxValue"}
Object.getOwnPropertyNames()
:返回常规属性数组Object.getOwnPropertySymbols()
:返回符号属性数组Reflect.ownKeys()
:既返回常规属性也返回符号属性Object.getOwnPropertyDescriptors()
:返回包含常规属性和符号属性的对象
let s1=Symbol('foo')
let s2=Symbol('bar')
let obj={
[s1]:'fooValue', //符号属性
[s2]:'barValue', //符号属性
baz:'bazValue', //常规属性
qux:'quxValue' //常规属性
}
Object.getOwnPropertyNames(obj) //["baz", "qux"]
Object.getOwnPropertySymbols(obj) //[Symbol(foo), Symbol(bar)]
Reflect.ownKeys(obj) // ["baz", "qux", Symbol(foo), Symbol(bar)]
Object.getOwnPropertyDescriptors(obj) //{baz: {…}, qux: {…}, Symbol(foo): {…}, Symbol(bar): {…}}
因为符号属性是对内存中符号的一个引用,所以也可以直接创建并用做属性:
let obj={
[Symbol('foo')]:'fooValue',
[Symbol('bar')]:'barValue',
}
console.log(obj) //{Symbol(foo): "fooValue", Symbol(bar): "barValue"}
常用内置符号
用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为。它的用途就是重新定义它们,从而改变原生结构的行为。比如for-of
循环会在相关对象上使用Symbol.iterator
,那么就可以通过在自定义对象上重新定义Symbol.iterator
的值来改变for-of
在迭代该对象时的行为。
下面内容涉及原型、Generator函数、async函数、Class类、iterator,建议有了解后再阅读。
Symbol.hasInstance
一个方法,判断某个实例是否为某个构造器对象的实例,通过instanceof
使用。在对象上借助Symbol.hasInstance
可重新定义默认行为。
instanceof
可以确定一个对象实例的原型链上是否有原型。function Foo(){} //构造器 let f=new Foo(); //通过构造器创建实例 console.log(f instanceof Foo) //true,表示Foo是f的原型
- 以
Symbol.hasInstance
为键的函数会执行与instanceof
同样的操作。function Foo(){} let f=new Foo(); console.log(Foo[Symbol.hasInstance](f)) //true
- 自定义
Symbol.hasInstance
行为://例1 class Array1 { static [Symbol.hasInstance](instance) { return Array.isArray(instance); //更改了默认行为,此时使用instanceof是判断该实例是否为数组。 } } console.log([] instanceof Array1);//true //例2 class Bar{} class Baz extends Bar{ static [Symbol.hasInstance](instance) { return false } } let bar=new Bar() //Bar里面是默认行为 console.log(Bar[Symbol.hasInstance](bar))//true console.log(bar instanceof Bar)//true //Baz里面是更改后的行为 console.log(Baz[Symbol.hasInstance](bar))//false console.log(bar instanceof Baz)//false
Symbol.isConcatSpreadable
一个布尔值,判断是否可以打平数组元素,通过Array.prototype.concat()
使用。在对象上借助[Symbol.isConcatSpreadable
可重新定义默认行为。
concat()
:合并两个或多个数组,返回一个新数组。语法:旧数组.cancat(数组1,数组2...)
什么是打平?指在合并数组时,若把各数组元素拎出来合并,则为打平;否则就是不打平。下面是一个例子:let arr1=['foo'] let arr2=['bar'] console.log(arr1.concat(arr2)) //['foo','bar'] 为打平 console.log(arr1.concat(arr2)) //['foo',Array(1)] 未被打平
- 数组对象默认会被打平到已有数组
let arr1=['foo'] let arr2=['bar'] console.log(arr1.concat(arr2)) //['foo','bar'] //修改默认行为: arr2[Symbol.isConcatSpreadable]=false console.log(arr1.concat(arr2)) //['foo',Array(1)]
- 类数组对象默认不会被打平(指具有length属性的数组对象)
let arr=['foo'] let arrObj={length:1,0:'bar'} console.log(arr.concat(arrObj)) //['foo',{...}] //修改默认行为: arrObj[Symbol.isConcatSpreadable]=true console.log(arr.concat(arrObj)) //['foo','bar']
- 其他不是类数组对象默认不会被打平。如果将
Symbol.isConcatSpreadable
设置为true
,那么该对象会被忽略。let arr=['foo'] let other=new Set().add('bar') //Set数据结构,add()表示给Set结构添加某个值,打印other:Set(1) {"bar"} console.log(arr.concat(other)) //['foo',Set(1)] //修改默认行为: other[Symbol.isConcatSpreadable]=true console.log(arr.concat(other)) //['foo']
Symbol.iterator
一个方法,返回对象的默认迭代器,通过for-of
使用。在对象上借助Symbol.iterator
可重新定义默认行为:
class Emitter{
constructor(max){
this.max=max;
this.idx=0
}
//修改异步迭代器的默认行为
* [Symbol.iterator](){
while(this.idx<this.max){
yield this.idx++
}
}
}
function count(){
let emitter=new Emitter(5)
for await(const x of emitter){
console.log(x)
}
}
count()
//0
//1
//2
//3
//4
Symbol.asyncIterator
一个方法,返回对象默认的异步迭代器,通过for-await-of
语句使用。在对象上借助Symbol.asyncIterator
可重新定义异步迭代器行为:
//例1
class Emitter{
constructor(max){
this.max=max;
this.asyncIdx=0
}
//修改异步迭代器的默认行为
async* [Symbol.asyncIterator](){
while(this.asyncIdx<this.max){
yield new Promise((resolve)=>resolve(this.asyncIdx++))
}
}
}
async function asyncCount(){
let emitter=new Emitter(5)
for await(const x of emitter){
console.log(x)
}
}
asyncCount()
//0
//1
//2
//3
//4
//例2
const myAsyncIterable = {
async* [Symbol.asyncIterator]() {
yield "hello";
yield "async";
yield "iteration!";
}
};
(async () => {
for await (const x of myAsyncIterable) {
console.log(x);
}
})();
// "hello"
// "async"
// "iteration!"
Symbol.match
一个正则表达式方法,用于匹配字符串,通过String.prototype.match()
方法使用。在对象上借助Symbol.match
改变默认行为:
[例1]-专门匹配foo
的匹配器:
class FooMatcher{
//静态方法,可直接用类调用
static [Symbol.match](target){
return target.includes('foo')
}
}
console.log('foobar'.match(FooMatcher)) //true
console.log('barbar'.match(FooMatcher)) //false
[例2]-匹配指定字符串的匹配器
class StringMatcher{
constructor(str){
this.str=str
}
//非静态方法,创建的实例会继承该方法
[Symbol.match](target){
return target.includes(this.str)
}
}
console.log('foobar'.match(new StringMatcher('foo'))) //true
Symbol.replace
一个正则表达式方法,用于替换字符串,通过String.prototype.replace()
方法使用。
console.log('foobarfoo'.replace(/foo/,'bar'))
在对象上借助Symbol.replace
接收两个参数,可通过其改变默认行为:
[例1]-将某个字符替换成#!@?的替换器:
class CustomReplacer {
constructor(value) {
this.value = value;
}
[Symbol.replace](string) {
return string.replace(this.value, '#!@?');
}
}
console.log('football'.replace(new CustomReplacer('foo'))); // "#!@?tball"
[例2]-将所以匹配的字符全部换成指定字符的替换器:
class Replacer {
constructor(str,replacement) {
this.str = str;
this.replacement=replacement;
}
[Symbol.replace](target,replacement) {
return target.split(this.str).join(this.replacement)
}
}
console.log('foobarfoo'.replace(new Replacer('foo','bar'))); //barbarbar
Symbol.search
一个正则表达式方法,返回字符串中匹配正则表达式的索引,通过String.prototype.search()
方法使用。
console.log('foobar'.search(/bar/)) //3
在对象上借助Symbol.search
改变默认行为:
[例1]-专门查找foo的查找器:
class FooSearcher{
static [Symbol.search](target){
return target.indexOf('foo')
}
}
console.log('foobar'.search(FooSearcher)) //0
console.log('barfoo'.search(FooSearcher)) //3
[例2]-查找指定字符的查找器:
class StringSearcher{
constructor(str) {
this.str = str;
}
[Symbol.search](target){
return target.indexOf(this.str)
}
}
console.log('foobar'.search(new StringSearcher('foo'))) //0
console.log('barfoo'.search(new StringSearcher('bar'))) //0
Symbol.split
一个正则表达式方法,在匹配正则表达式的索引位置拆分字符,通过String.prototype.split()
方法使用。
console.log('foobazbar'.split(/baz/)) //["foo", "bar"]
在对象上借助Symbol.split
改变默认行为:
[例1]-根据foo拆分字符串的拆分器:
class FooSplitter{
static [Symbol.split](target){
return target.split('foo')
}
}
console.log('barfoobaz'.split(FooSplitter)) //["bar", "baz"]
[例2]-根据指定字符拆分字符串的拆分器:
class StringSplitter{
constructor(str) {
this.str = str;
}
[Symbol.split](target){
return target.split(this.str)
}
}
console.log('barfoobaz'.split(new StringSplitter('foo'))) //["bar", "baz"]
更多内置符号请查看https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol