主要结合最近的实际开发经验,以及著名的红宝书,在加上本身的理解,若有错误或补充,可在评论中指出,欢迎讨论。
本文主要介绍js变量中一些不为人知或者容易混淆的点,对常规信息采用略过形式,
因为ES5在各大浏览器中的实现标准略有差别,故而测试环境主要采用最为经常使用的Chrome,版本信息以下
相关信息:java
数据类型共分为两种,简单数据类型(也叫作基本数据类型)和复杂数据类型,前者主要包括:Number、String、Boolean以及并不经常使用的Undefined和Null,后者则只有Object。
堆栈问题:基本数据的保存在栈内存中,而复杂数据也就是引用类型则保存在堆内存中,在对前者复制时将会开辟一个新的内存,而对后者的复制仅仅只是复制了堆内存的指针,而对于这个存储了指针的值则是存储在了栈内存中。设计模式
这里没有对指针作过多的解释,具体定义能够查看c或者java中对指针的介绍。
不过在JavaScript中也能够认为,万物基于Object,也是本文的重点介绍对象。
至于检测相关类型,简单归纳就是:基本数据类型用typeof,复杂数据类型用instanceof,这二者将会在后面频繁见到,这里不作过多介绍。数组
Number在ES中分为整数与浮点数,也可以使用八进制(0)或十六进制(0x)等,若是小数点后为0,则默认解析为整数型。
因为内存限制,Number存在一个极限,若是运算结果超过了这个极限,数值就会变为无穷,即Infinity(使用isFinite()判断),在大多数浏览器中规则:浏览器
数值 | 调用方式 | |
---|---|---|
最大值 | 1.7976931348623157e+308 | Number.MAX_VALUE |
最小值 | 5e-324 | Number.MAX_VALUE |
正无穷 | Infinity | Number.NEGATIVE_INFINITY |
负无穷 | -Infinity | Number.POSITIVE_INFINITY |
此处须要介绍一个特殊的数值NaN(Not a NUmber),即非数值(没错,他叫非数值,倒是一个特殊的数值),他一般用来表示,本来要返回一个数值,可是没有返回数值的状况,不会报错,常见状况有:数据结构
最特殊的点是,他不等于任何值,包括他本身,NaN == NaN //false
因此为了断定NaN数值,ES定义了isNaN()app
isNaN(NaN); //true isNaN(10); //false isNaN('10'); //false isNaN('blue'); //true,没法转化为数值 isNaN(true); //true,转换为1,
那么问题又来了,为何字符串返回也是true了呢?
上面有写到,NaN是在本来要返回一个数值却不能返回数值的状况下返回的特殊值,在此处将字符串转换为Number过程当中,没法转化,故而返回NaN。
关于将非数值转换为数值的方法:Number(),parseInt(),parseFloat(),
这里略过常见的转换规则,下面写出几个较为少见的规则:函数
Number('10abc') //NaN parseInt('10ab20c') //10,就近原则, parseInt('1azzz2b',16) //26 parseFloat('0xA2') //0
在String中,'和"没有区别,可是要先后匹配,也能够包含一些相似n,r,'的特殊字符字面量。
对于内存而言,字符串实际上是不可变的,对一个字符串进行的更改其实是进行了销毁再重建的流程。
讲一个值转换为字符串,用的最多的就是toString(),能够传参改变进制,null和undefined没有这个方法,为了解决这个问题,ES定义了转型函数String(),能够将任何类型转换为字符串。测试
var num=10; num.toString(2); //'1010' String(null); //'null' String(undefined); //'undefined;
在ES中,不少对字符串操做的方法,一样也能对Array操做,并且用法上一般都是相同的,如concat,splice,slice等,甚至对一个字符串使用下标同样可以访问对应的字符,同时,字符串也有一些访问指定字符的方法this
var str='hello world'; str[2] //'l'; str.charAt(1); //'e' str.charCodeAt(1) //'101'返回的是字符编码,
因为字符串相关方法其实重合度挺高的,若是一一介绍过去本文会显得斑驳,因此这里再也不用大篇幅去描述和贴代码,如下使用关键字+核心描述的形式介绍,编码
此处略过一些你们都熟用的方法如concat,splice,indexof,split等
复杂数据类型只有一种,那就是object,可是由其派生的类型则有许多,例如:Function、Array等,这些都是在开发中经常使用的数据结构,其实,万物基于Object,
String,Number这些基本的数据类型,实际上也是一个funciton,这一点,当你在实例化一个字符串的时候就能发现
var str1=new String('dutny'); //{'dutny'} var str2='dutny'; //'dutny' str1==str2; //true str1===str2; //false
如上也是能够实例化字符串的,而且在非全等条件下两种声明方式获得的字符是相等的,而在全等条件下则是表示为false,这是由于非全等条件下的比较,是通过转换后的比较,即
12=='12'; //true 12==='12' //false
实际上在上述实例化过程后,str1表现为一个object,而str2表现为一个String,由此在非全等条件下类型转换后的表现形式是相等的,即两个都是'dutny',
typeof str1; //object typeof str2 //string
这里有点扯远了,综合上述,乍一看,彷佛没有经过new操做符实例化的str2没有表现为对象,实际上并不是如此,由于在js中,new操做符实例化的变量会为变量在堆内存开辟一块新的内存,而直接赋值则直接将变量保存在栈内存,故而str1表现为{'dutny'},而str2表现为'dutny'。
str1.__proto__===String.prototype; //true str2.__proto__===String.prototype //true
这也就解释了,当你使用了直接赋值的方法,对多个变量直接赋值为同一个内容物,实际上这几个被直接赋值的变量是指向为同一块内存,由于你没有new出一块新的内存,虽然如此,但在开发中依旧不建议是用new操做符为变量赋值,一则是在实际开发中并不会出现同一个内容物屡次赋值的重复性工做,二则是对内存的占用较大,即便js中有垃圾回收也难保不会出现问题。
扯了上面那么多,不过只是证实了String实际上是objec的实现,其实仍是那句话,在js中,万物基于Object,甚至连null也是基于Object。
typeof null //object
其实对于万物基于object这个说法,目前并非一个官方的说法,或者说没有权威性的官方说明,只是各人对于各人的理解不一样,例如说字符串的对象属性能够说成是原型设计模式而共享的来自构造函数的原型属性,可能对于我来讲,这个说法或多或少仍是受到了java的影响,因此比较偏向这个方向,你们不用刻意的去追求哪个说法。
回到正题,简述了一下object,首要即是介绍Function了,因为堆栈内存的特性,因此实例化一个函数的时候,其实能够将函数名看成这个指针,也就可以理解函数的重载问题,以及函数也能够赋值。
接下来讲到function的内部属性arguments和this,其实函数对于传参并无严格的规定,你能够违反函数定义的参数数量,传递超出数量的参数,也能够少传.
function say(){ console.log('i am dutny') }; say('tom'); //i am dutny
在这个基础上,若是咱们超量了,那么怎么在函数中访问超出的那个变量呢?这就要引入arguments概念.
function say(){ console.log('i am '+arguments[0]) }; say('dutny'); //i am dutny
能够看到,arguments也是按照下标顺序访问,初次以外,arguments还有callee属性,它指向所在函数的自己,有了这个属性能够解决不少问题,例如在当即执行的匿名函数能够进行递归或其余调用本身的操做,在函数名字发生改变的状况下依旧可以正确的调用本身。除此以外,函数内部还定义了caller属性,这个属性保存着调用当前函数的函数的引用,在全局做用域中则是null,他是函数自己的属性,能够经过函数名调用,再结合上述的callee使用。
function say(){ console.log(arguments.callee.caller); } function person(){ let name='dutny'; say(); } person(); //显示person的源代码,
再说this,这是一个比较复杂的点,各类详解博客也解释的很是详细,通俗点说,他指向调用当前函数的做用域。
var name='dutny_a'; function say(){ console.log(this.name); }; var sco={name:'dutny_b'}; sco.say=say; say(); //dutny_a sco.say(); //dutny_b
从上能够看出:在全局做用域环境下调用say,this指向的是全局做用域,在特定的局部做用域下调用say,this指向的则是局部做用于。
更多this相关的详细信息有挺多博客详尽的描述了,这里再也不赘述,有兴趣的朋友能够本身去看看博客或者相关书籍
说道this,就要引入两个由ES定义的两个非继承而来的函数的方法:apply()和call(),这两个方法都是为了去指定函数调用的做用域而出现的,不一样的是,在使用call()时,须要一一指定须要传入的参数,而apply()则能够选择传入参数数组或是arguments。
var name='dutny_a'; var _this=this; function say(age){ console.log('name:'+this.name+',age:'+age); }; function reSay(age){ var sco={name:'dutny_b'}; say.apply(sco,arguments); // say.call(_this,age) // name:dutny_a,age:25 } reSay(25); //name:dutny_b,age:25
其实除了上述两个方法,ES还定义了bind()方法,也是函数的方法,只有一个参数,指定当前调用函数的做用域,这里不作赘述。
再说一说Array,
方法 | 做用 | 方法 | 做用 | |
---|---|---|---|---|
栈方法 | push() | 尾部推入 | pop() | 尾部弹出 |
队列方法 | push() | 尾部推入 | shift() | 头部弹出 |
位置方法 | indexOf() | 从头查找 | lastIndexOf() | 从尾查找 |
归并方法 | reduce() | 从头归并 | reduceRight() | 从尾归并 |
其余 | unshift() | 头部推入 |
还有经常使用的迭代方法,每一个迭代方法接受两个参数,在每一项上运行的函数和运行该函数的做用域(可选),传入这些方法的函数接受三个参数:数组项的值、该项在数组中的位置和数组自己:
除此以外,还要注意的一点是,数组的排序方法sort(),默认是按照字符串比较排序,因此会出现以下状况:
var arr=[24,1,3]; console.log(arr.sort()); //[1,24,3]
因为在字符串的比较中,'24'中的'2'是小于'3'的,因此出现了如上状况,若是想要避免这种状况,sort()能够接受一个比较函数做为参数:
var arr=[24,1,3]; function compare(value1,value2){ return value2-value1; }; console.log(arr.sort(compare)); //[24, 3, 1]
若是想要反序能够。使用reverse()反转数组。
以上差很少就是这篇博客的内容了,原本重点在object这边,结果发现隔天再写的时候有点没有思路了,因此暂时就写这么多吧,若是有补充或者纠错的能够在评论区讨论,