你们好,本系列文章是总结我学习ES5的一些笔记以及一些认为比较重要的知识点,其中在ES部分有以下整理:html
基石:ES5基础(一) 数据类型&类型转换面试
基石:ES5基础(二) 对象&对象特征属性算法
基石:ES5基础(三) 原型&原型链&继承数组
基石:ES5基础(四) 函数&做用域&闭包bash
我知道,有不少大佬已经hold这些知识点,而且很是高赞。但人家的终归是人家的.. 没法亲力亲为,何谈印象深入。闭包
首先感谢各位阅读本文章,因为弟弟刚学不久,虽然对语言的高度认识还不够,对写文档的能力弱一逼,以及总结概括技巧很是拙劣。可是我知道你们都是敞亮人都是哥哥,会鞭策好我让新萌获得最快成长,谢谢你们的海涵!!函数
首先先扔几道高频面试题来开始咱们本次ES5之旅。post
请描述一下原始值
和引用值
的区别学习
请简单解释一下 0.1 + 0.2 != 0.3
ui
简述 1.abc=123
经历了哪些过程
4 + [1,2,3]
、'a' + + 'b'
以及[] == ![]
分别返回什么?
判断类型
的方法以及不一样,了解过$.type
吗?
在ES规范
中规定了数据类型分为两大阵营,一方是原始值,另一方是引用值。我刚接触ES的时候就一直有一个疑惑,为何计算机要设计原始值以及引用值?直接放一个锅里不行吗?后来当我学过堆和栈的概念以后,仿佛理解了一些。
首先回顾一下原始值和引用值分别是什么。
6.1ECMAScript Language Types
An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language. The ECMAScript language types are Undefined, Null, Boolean, String, Symbol, Number, and Object. An ECMAScript language value is a value that is characterized by an ECMAScript language type.
manipulate:操纵
characterized by:特色
在ECMA标准中,规定了ES有以下几种数据类型供给咱们操做
Undefined Null Boolean String Symbol(ES6新增) Number 以及 Object.
经过ECMA标准以及在红宝书上咱们学习到对于数据类型的讲解和分类,分为原始值和引用值。首先看一下在ECMA标准中是如何定义着7种数据类型。
6.1.1The Undefined Type
The Undefined type has exactly one value, called undefined. Any variable that has not been assigned a value has the value undefined.
assign: 指派
exactly: 确切
确切的说Undefined类型只有一个值,是undefined。表示任何的变量若是没有被分配值那么默认是undefined.注意和null的区别
6.1.2The Null Type
The Null type has exactly one value, called null.
Null类型只有一个值,是null.官方文档没有给出额外的信息,可是在红宝书中说过
从逻辑的角度讲null是用来表示一个空指针,而且typeof返回object
。一般是用来表示一个对象已经声明,最好使用null来初始化而不是其余值。
根据ECMA-262
规定对他们的相等性判断是返回true,其根本缘由是undefined是派生自null。
可是他们的语义和用途是不一样的.任何状况下咱们都不必对变量显示赋值为undefined。对于对象变量尚未保存对象时,咱们应该明确的让此对象等于null.
6.1.3The Boolean Type
The Boolean type represents a logical entity having two values, called true and false.
represents: 表明
logical: 逻辑学
6.1.4The String Type
The String type is generally used to represent textual data in a running ECMAScript program, in which case each element in the String is treated as a UTF-16 code unit value.
treat: 对待
String类型一般用于表示正在运行的程序中文本数据,每一个元素都被视为UTF-16代码单元值。
6.1.6The Number Type
The Number type has exactly 18437736874454810627 (that is, 264-253+3) values, representing the double-precision 64-bit format IEEE 754-2008 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic, except that the 9007199254740990 (that is, 253-2) distinct “Not-a-Number” values of the IEEE Standard are represented in ECMAScript as a single special NaN value.
specifiey: 指定
distinct: 明显的
Number类型中能表示那么多的值,由于采用的是IEEE二进制双精度浮点数运算标准。这为0.1 + 0.2 != 0.3埋下了伏笔。在ECMA中,使用一个特殊值NaN表示不在标准范围内的值。
all NaN values are indistinguishable from each other.
indistinguishable: 难分辨
全部的NaN彼此之间是没法区分的,也就是说 NaN == NaN为 false
,可是请注意在ES6中NaN使用Object.is来判断是相等的。
Note that there is both a positive zero and a negative zero. For brevity, these values are also referred to for expository purposes by the symbols +0 and -0, respectively.
negate: 取消/负的
注意,在ECMA中,对于0是分+0和-0的。咱们知道在ES5中对于-0 ===+0
为true.可是在ES6使用Object.is判断为不相等。
6.1.7The Object Type
An Object is logically a collection of properties. Each property is either a data property, or an accessor property:
对象在逻辑上是属性的集合,每一个属性或者是数据属性,或者是访问器属性。本身分一类丝绝不过度,除了经常使用的Object
,Array
、Function
等都属于特殊的对象;数据属性和访问器属性会在对象部分详解。
简单回顾后,引出核心问题之一,简述一下原始值和引用值的区别?
JavaScript中的原始类型的值被直接存储在栈中,在变量定义时,栈就为其分配好了内存空间,因为栈中的内存空间的大小是固定的,那么注定了存储在栈中的变量就是不可变的。 栈内存特色:
多数状况下,基本类型直接表明了最底层的语言实现。
全部基本类型的值都是不可改变的。但须要注意的是,基本类型自己和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而原值不能像数组、对象以及函数那样被改变。
var str = "SU";
str.slice(1);
console.log(str) // SU;
str += "1";
console.log(str) // SU1;
复制代码
当咱们调用方法后,是在原字符串的基础上进行操做产生一个新的字符串,而并不是直接修改str,着印证了字符串的不变性
。 但你会发现第二次str被修改了,看似是str被改变了实际上是发生了下面的代码
var str = str + 1;
//等同于str += 1;
复制代码
是由于JavaScript中的原始值存放在栈
中,在变量定义的同时就分配好了内存空间。因此 str += 1其实是在内存中开辟了一个新空间
,并不是是改变字符串的值,并不违原始类型的不可变性。
引用类型的值实际存储在堆内存中,它在栈中只存储了一个固定长度的地址,这个地址指向堆内存
中的值.
var spy = {
age : 23,
}
console.log(spy) //age : 23,
spy[daughter] = "qianqian";
console.log(spy) //age : 23,daughter : qianqian
复制代码
印证了引用类型的可变性。
堆内存特色:
原始值复制后的变量指向的内存空间彻底不一样,变量参与任何操做都互不影响。
var foo = 123;
var bar = foo; //bar : 123
bar ++; // 124
console.log(foo) // 123
复制代码
引用值复制的是栈中存储的地址,会指向堆内存中同一个对象。改变其中一个任意一个变量的数据就会致使相同地址的变量发生改变。
var spy = {
age : 23,
}
spy[daughter] = "qianqian";
var lcy = spy;
lcy[sex] = "female" ;
console.log(lcy); //age:23,sex:female,daughter:qianqian
console.log(spy); //age:23,sex:female,daughter:qianqian
复制代码
将spy这个对象复制给lcy对象后,操做lcy对象进行设置属性和赋值,会发现spy对象也被一样操做。
所以一般使用浅拷贝和深拷贝来实现对引用值进行复制。
对于原始类型,比较时会直接比较它们的值,若是值相等,即返回true。
var a == 1;
var b == 1;
console.log(a == b) // true;
b = 3;
console.log(a == b) // false;
复制代码
对于引用类型,比较时会比较它们的引用地址,虽然两个变量在堆中存储的对象具备的属性值可能相等,可是它们被存储在了不一样的存储空间,所以比较值为false。
var arr1 = [1];
var arr2 = [1];
console.log(arr1 == arr2) //false;
复制代码
函数参数传递的并非变量的引用,而是变量拷贝的副本,当变量是原始类型时,这个副本就是值自己;当变量是引用类型时,这个副本是指向堆内存的地址。因此,请默念三遍:ECMAScript中全部函数的参数都是按值传递的
function add(num){
num++;
return num;
}
var a = 1;
console.log(add(1),a); // 2 1;
复制代码
当原始值1做为函数参数传递给函数时,可以证实是传递的值自己。就和原始值赋值是同样的性质(本质是arugments作缓冲层)
function setName(obj){
obj[name] = "spy";
return obj;
}
function createNew(obj){
obj[name] = "spy";
obj = new Object(); //notice
return obj;
}
var obj = new Object();
setName(obj);
console.log(obj); // spy;
var newObj = createNew(obj);
console.log(obj);// spy;
console.log(newObj); // {}
复制代码
解释一下notice标记的代码,此时的obj是隶属于createNew函数的内部变量,在函数执行结束后会当即销毁。在这里只是更新了内部同名属性obj
的指针,可是不会影响外部的obj。所以hold参数是按值传递的。
请默念三遍:ECMAScript中全部函数的参数都是按值传递
在ECMA标准中,还规定了3种特殊引用类型:Boolean
Number
String
统称为基本类型包装类,他和引用类型同样,也具备与各自的基本类型相应的特殊行为.
var name = "Su";
var length = name.length();
复制代码
咱们知道,基本类型值不是对象,于是从逻辑上 讲它们不该该有方法(尽管如咱们所愿,它们确实有方法.其实,为了让咱们实现这种直观的操做,后台已经自动完成了一系列的处理。
所以能够理解成以下
var name = new String("Su");
var length = name.length();
name = null;
复制代码
这样就能够将基本的字符串和对象同样看待。同时分别适用于Boolean和Number。
注意undefined和null没有基本包装类
,也就意味着他们没有方法,设置不了属性。
undefined.name = 'Su'; //Uncaught TypeError: Cannot set property 'name' of undefined
undefined.length(); //Uncaught TypeError: Cannot read property 'length' of undefined
复制代码
由于undefined是派生自null,所以null也一样没有方法和没法设置属性。所以能够经过包装类的角度
解释为何undefined和null的调用方法和设置属性会报错。
引用类型与基本包装类型的主要区别就是对象的生存期
自动建立的基本包装类型的对象,则只存在于一 行代码的执行瞬间,而后当即被销毁。这意味着咱们不能在运行时为基本类型值添加属性和方法。
能够通俗的理解为给基本类型设置属性后,再次访问这个属性会返回undefined(非严格模式)。
var spy = "Su";
spy.sex = "male";
console.log(spy.sex); // undefined;
复制代码
在这里只是简单的介绍了一下基本包装类,在后面的学习过程当中,会详细的剖析。
由于ECMASciprt是弱语言,因此常常会发生类型转换,例如刚才阐述的基本包装类也属于一种隐式类型转换。
类型转换分为两种,隐式转换即程序自动进行的类型转换,强制转换即咱们手动进行的类型转换。
在这里咱们并不讨论API层面的强制转换,Focus在隐式类型转换。
在if
语句或者逻辑语句时,若是只有单个变量,会先将变量转换为Boolean
值,只有下面几种状况会转换成false
,其他被转换成true
:
null undefined ' ' NaN 0 false
首先咱们要知道,在 JS 中类型转换只有三种状况,分别是:
在条件判断时,除了 undefined
, null
, false
, NaN
, ''
, 0
, -0
,其余全部值都转为 true
,包括全部对象。
对象发生类型转换时,会调用内部的[[ToPrimitive]]
规则,这个算法逻辑一般能够总结为:
x.valueOf()
,若是转换为原始类型,就返回转换的值x.toString()
,若是转换为原始类型,就返回转换的值'[oject Array]' == [ ] // true
'1,2,3' == [1, 2, 3] // true
复制代码
同时也能够利用 Symbol.toPrimitive
,重写[[ToPrimitive]]
规则。该方法在转原始类型时调用优先级最高。
var test = {
valueof () {
return 1;
},
toString() {
return 2;
},
[Symbol.toPrimitive]() {
return 3;
}
}
var a = 1;
a + test; // 4
复制代码
同时要注意类型转换和优先级的组合
[] == ![]
左侧根据[[ToPrimitive]]
转换成0,!
的优先级大于==
因此会将[]转化成false后进行比较。
+
运算符不一样于其余运算符,总结其特色为:
1 + "abc" // "1abc" notice 1
true + true // 2 notice 2
4 + [1,2,3] // "41,2,3" notice2 和对象转原始值
复制代码
在作+
运算时,要注意优先级,例如
"a" + + "b"
结果为aNaN
能够理解为a +(+"b")
一般状况下+ "1"
是用来强制转换。
*、-、/
运算符就相对简单一些,要其中一方是数字,那么另外一方就会被转为数字
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN
复制代码
==
操做符对比双方类型不同的话就会发生类型转换。
判断流程为:
boolean
,是的话就会把 boolean
转为 number
再进行判断object
且另外一方为string
、number
或者 symbol
,是的话就会把 object
转为原始类型再进行判断。a == 1 && a == 2 && a == 3
如何分析呢?
这是在ConardLi大佬博客中看到的一道题,关于变量和类型
大佬讲的更细致,我会在最后将文章连接放出来。
可以判断原始类型,除了null(二进制000问题)
typeof
对于原始类型来讲,除了 null
均可以显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
复制代码
没法判断引用值,除了Function类型
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
复制代码
内部机制是经过原型链进行判断,能够判断引用值类型,一般状况下是没法对原始值进行判断。
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true
var str = 'hello world'
str instanceof String // false
var str1 = new String('hello world')
str1 instanceof String // true
复制代码
若是此方法在自定义对象中未被覆盖,toString才会达到预想的效果进行类型判断。事实上,大部分引用类型好比Array、Date、RegExp等都重写了toString方法,所以咱们一般使Object原型未被覆盖的toString()方法。
若是本篇文章您以为有什么地方有问题,必定要给我指正。若是您以为还不错,而且期待后续文章,点赞关注走一波~
梳理一下欠下待整理的文章:
ConardLi大佬:【JS 进阶】你真的掌握变量和类型了吗