JS中的变量类型
js中的变量分为两种类型,一种是值类型,另外一种是引用类型。值类型包括undefined,null,boolean,string,number;引用类型是object。
这里需要注意下,有两个比较特殊的值 —— null 和 undefined。
null用来表示空对象引用(用以理解)。典型用法:
- 作为函数的参数,表示该函数的参数是空对象引用,即没有指向。
- 作为对象原型链终点
undefined用来表示缺少值。即此处应该有值,但是还没有定义。典型用法:
- 变量已经被声明了,但是还没有被赋值,就等于undefined
var i; console.log(i); // undefined
- 调用函数时,应该提供的参数没有提供到,该参数的值为undefined
function foo (x) { console.log(x); // undefined } foo();
- 对象没有赋值的属性,该值为undefined
var o = {};
- 函数没有返回值时,默认是返回undefined
function foo () { console.log(1); } var result = foo(); console.log(result); // undefined
值类型和引用类型区别
- 存储位置上:值类型存储在栈中,引用类型储存在堆中
- 存储方式上:值类型存储的是实际的数值,引用类型则是保存在堆内存中对象的引用(因为JS不允许直接操作内存)。
- 数据类型上:基本类型值指的是简单的数据段,引用类型值指那些可能由多个值构成的对象。
总结:值类型是按值访问的,引用类型的值是按引用访问的(准确地说,应该是当复制保存着内存中对象的引用的变量时,操作的是对象的引用;而为内存中的对象添加属性的时候,操作的是实际的对象)
typeof操作符和instanceof操作符的使用
使用typeof检测数值会出现以下结果:undefined,number,boolean,string,object,function
所以说使用typeof只能检测值类型,检测引用类型是得不到我们想要的结果。所以我们使用instanceof检测引用类型。
JS引用类型
js内置函数
Object,Array,Function,Date,RegExp,Boolean,Number,String,Error
js内置对象(不是函数)
Math,Global,JSON(有两个API —— JSON.parse()和JSON.stringify();也是一种数据格式)
JS变量的隐式类型转换(javascript中的比较)
抽象相等比较算法
比较运算x == y
,其中x
和y
是值,产生true
或者false
。这样的比较按如下的方式进行:
- 若
Type(x)
与Type(y)
相同,则- 若
Type(x)
为Undefined
,返回true
。 - 若
Type(x)
为Null
,返回true
。 - 若
Type(x)
为Number
,则- 若
x
或者y
为NaN
,返回false
。 - 若
x
为+0
且y
为-0
,返回true
。 - 若
x
为-0
且y
为+0
,返回true
。 - 若
x
与y
为相等数值,返回true
。 - 返回
false
。
- 若
- 若
Type(x)
为String
,则当x
和y
为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true
。否则,返回false
。 - 若
Type(x)
为Boolean
,当x
和y
同为true
或者同为false
时返回true
。否则,返回false
。 - 若
Type(x)
为Object
,当x
与y
为引用同一对象时返回true
。否则,返回false
。
- 若
- 若
x
为Null
且y
为Undefined
,返回true
。 - 若
x
为Undefined
且y
为Null
,返回true
。 - 若
Type(x)
为Number
且Type(y)
为String
,返回x == ToNumber(y)
(即y先转化为Number类型再比较)。 - 若
Type(x)
为String
且Type(y)
为Number
,返回ToNumber(x) == y
(即x先转化为Number类型再比较)。 - 若
Type(x)
为Boolean
且Type(y)
为Number
,返回比较ToNumber(x) == y
的结果(即x先转化为Number类型再比较)。 - 若
Type(x)
为Number
且Type(y)
为Boolean
,返回比较x == ToNumber(y)
的结果(即y先转化为Number类型再比较)。 - 若
Type(x)
为String
或者Number
,且Type(y)
为Object
,返回比较x == ToPrimitive(y)
的结果(即调用Object的valueOf()
方法或者toString()
方法得到值再比较)。 - 若
Type(x)
为Object
,且Type(y)
为String
或者Number
,返回比较ToPrimitive(x) == y
的结果(即调用Object的valueOf()
方法或者toString()
方法得到值再比较)。 - 其余情况返回
false
。
注意:false == ""
也是true
。
1. false -> Number(false) -> 0
2. "" -> Number("") -> 0
3. 0 == 0 -> false == ""
false == 0 // true
false == undefined // false
false == null // false
null == 0 // false
null == "" // false
undefined == 0 // false
undefined == "" // false
注:相等运算符不总是传递的。 例如两个不同的String
对象,都表示相同的字符串;==
运算符认为每个String
对象都与字符串的值相等,但是两个字符串对象互不相等。
new String("a") == "a"; // true
"a" == new String("a"); // true
new String("a") == new String("a"); // false
严格相等比较算法
比较运算x === y
,产生true
或者false
。这样的比较按如下的方式进行:
- 如果
Type(x)
与Type(y)
结果不一致,返回false
。 - 若
Type(x)
与Type(y)
相同,则- 若
Type(x)
为Undefined
,返回true
。 - 若
Type(x)
为Null
,返回true
。 - 若
Type(x)
为Number
,则- 若
x
或者y
为NaN
,返回false
。 - 若
x
为+0
且y
为-0
,返回true
。 - 若
x
为-0
且y
为+0
,返回true
。 - 若
x
与y
为相等数值,返回true
。 - 返回
false
。
- 若
- 若
Type(x)
为String
,则当x
和y
为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true
。否则,返回false
。 - 若
Type(x)
为Boolean
,当x
和y
同为true
或者同为false
时返回true
。否则,返回false
。 - 若
Type(x)
为Object
,当x
与y
为引用同一对象时返回true
。否则,返回false
。
- 若
Falsy 和 Truthy
- Falsy值:当进行逻辑判断时均为
false
。包括false, null, undefined, +0, -0, NaN, "", ''
- Truthy值:其余的值均为
Truthy
,当进行逻辑判断时均为true
。infinity, [], "0"
都是 Truthy 值。
两个很经典的面试题
1. [] == ![] // true
-
等号右边有
!
,优先级比==
更高,优先计算右边的结果。[]
为非假值,所以右边的运算结果为false
![] => false
-
==
的任意一遍有boolean
类型的值先把这个值转换成number
类型,右边转换成了 0Number(false) => 0
-
==
分别是number
和object
类型的值时,需要把object
转换成了number
类型。对object
进行ToNumber
操作Number([].valueOf()) => 0
2. ++[[]][+[]]+[+[]] // “10”
-
++[[]][+[]]+[+[]] =>
++[[]][+[]] + [+[]]
-
因为 +[] === 0,所以原式 =>
++[[]][0] + [0]
-
因为 [[]][0] 的意思是获取 [[]] 的第一个元素,故返回 [[]] 的第一个数组 [],原式 =>
1 + [0]
-
当数组只有一个元素时,返回结果就是它自身的第一个元素,原式 =>
1 + "0" => "10"
JS运算符
图片可能不是很清晰,这个是MDN的传送门
一元加
- 一元操作符以一个加号(+)表示,放在数值前面,对数值不会产生任何影响
- 在对非数值应用一元操作符时,该操作符会像Number()转型函数一样对这个值执行转换。(布尔值false和true将转为0和1,字符串值会被按照一组特殊的规则进行解析,而对象是先调用它们的valueOf()和toString(),再转换得到的值)
- 它可以将字符串转换成整数和浮点数形式,也可以转换非字符串值 true,false 和 null。小数和十六进制格式字符串也可以转换成数值。负数形式字符串也可以转换成数值(对于十六进制不适用)。
+3 // 3
+"3" // 3
+true // 1
+false // 0
+null // 0
+undefined // NaN
+function(val){ return val;} // NaN
true + 5 // 6
false + 5 // 5
null + 5 // 5
undefined + 5 // NaN
'' + 5 // '5'
[] + 5 // '5'
Number(), parseInt(), +
一样,都可以用来进行数字的转换。区别在于,当转换的内容包含非数字的时候,Number()
会返回 NaN(Not a Number)
,+
和 Number()
一样会返回 NaN
。parseInt()
要看情况,如果以数字开头,就会返回开头的合法数字部分,如果以非数字开头,则返回 NaN
。
一元减
- 一元减操作符主要用于表示负数
- 将一元减操作符应用于数值时,该值会变成负数
- 当应用于非数值时,一元减操作符遵循与一元加操作符相同的规则,最后再将得到的数值转换为负数
一道很有趣的面试题
function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}
//请写出以下输出结果:
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3
第一问
首先此题先声明定义了一个 ·Foo
函数,之后 Foo创建了一个 getName
静态属性,是一个匿名函数。再之后 Foo 的原型对象创建了一个 getName
的属性,也是一个匿名函数。通过函数变量表达式创建了一个 getName
函数,最后声明定义 一个 getName
函数。
第一问中的 Foo.getName
访问的是 Foo 函数上存储的静态属性,所以是 2
第二问
第二问中直接调用 getName
函数。访问的是当前上下文作用域中的 getName
函数。在这个说一下,这里涉及了两个坑,一个是变量声明提升,二是函数表达式。变量声明提升就不说了,说一下函数表达式。
函数表达式
var getName = function () {}
与 function getName () {}
都是声明语句,区别就是 var getName = function () {}
是函数表达式;而 function getName () {}
是函数声明。
所以这一问中 var getName = function () { alert (4); }; function getName() { alert (5); };
先声明 function getName
,此时要 var getName
声明,因存在同名则跳过,然后执行代码时, var getName
等号右边的值 function () { alert(4); };
赋给了 getName
变量。所以最终输出 4
第三问
第三问中, Foo().getName();
先执行了 Foo 函数,然后调用 Foo 函数返回的对象的 getName
属性指向的函数。
Foo 函数的第一句修改了外层作用域中的变量 getName
的值。 之后 Foo 函数的返回值是 this
。此处的直接调用方式,this
指向 window
对象。所以相当于执行 window.getName()
,此时 getName
已经被修改了,所以最终输出为 1
第四问
同第三问,相当于执行 window.getName()
,最终输出为 1
第五问
第五问考察的是 js 的运算符优先级问题,成员访问(.
)的优先级高于 new
,相当于 new (Foo.getName)();
。所以实际上将 getName
函数作为了构造函数来执行,最终结果为 2
第六问
第六问 new Foo().getName()
,首先带参new操作符
和成员访问(.
)优先级一样,所以先碰到谁就先执行谁,这里是 new
。实际执行为 (new Foo()).getName();
,执行完 new 操作后有返回值,在这里顺便讲讲 js 中的构造函返回值问题。
构造函数的返回值
1.没有返回值则按照其他语言一样返回实例化对象
2.若有返回值则检查其返回值是否为引用类型。如果是非引用类型,如基本类型(string, number, boolean, null, undefined
)则与无返回值相同,实际返回其实例化对象
3.若返回值是引用类型,则实际返回值是这个引用类型
原题中,返回的是 this,而 this 在构造函数中本来就代表着当前实例化对象,所以 Foo 函数返回实例化对象。之后调用实例化对象的 getName
函数,因为实例化对象中没该函数,所以向 Foo 函数的原型对象中询找,找到后并执行,最终结果为 3
第七问
第七问中,new new Foo().getName();
同样是运算符优先级的问题。实际执行为:new ((new Foo()).getName)();
。先初始化 Foo 的实例化对象,然后取得函数 Foo 原型对象上的 getName
函数作为构造函数,使用 new 操作,最终输出为 3
JS内存管理
js中内存的生命周期
// 1.分配需要使用的内存
// 2.使用分配的内存进行读写
// 3.不需要时释放,归还内存
var obj = { age: 22 };
obj.age = 20;
obj = null;
而第三步释放内存需要了解一下,JS中是有着自动垃圾收集机制的。自动垃圾收集机制的原理是找到那些不再使用的值,释放掉它们所占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。
JS中最常用的垃圾收集方式标记清除。前面的代码的obj = null
只是做了一个释放引用的操作,让obj原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集时找到并且释放掉。
在局部作用域中,函数执行完后,局部变量没有存在的必要,所以垃圾收集器很容易做出判断并回收。而全局变量垃圾收集器很难判断它什么时候需要释放掉内存,所以要尽量避免使用全局变量。