变量
js中的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据,换句话说每个变量仅仅是一个用于保存值的占位符而已
var message = 'hi';
message = 100;
上述示例中,变量message开始保存了一个字符串值'hi',然后又被一个数字值100取代。虽然不建议修改变量所保存值的类型,但这种操作在js中是有效的。
有一点必须注意,即用var定义的变量将成为定义该变量的作用域中的局部变量。也就是说,如果在函数中使用var定义一个变量,那么这个变量在函数退出后就会被销毁,例如:
function test(){
var message = 'hi';//局部变量
}
test();
alert(message); // error!
function test2(){
message2 = 'hi2';
}
test2();
alert(message2); //hi2
第二种省略了var操作符,因此message2成了全局变量,调用test2后,这个变量就有了定义,可以在函数外部任何地方被访问到。声明变量的时候尽量使用var操作符定义,避免污染全局环境。
数据类型
JavaScript中包含两种类型,基本类型和对象类型
基本类型
基本类型:
- Undefined(未定义)
- Null (空值)
- Boolean (布尔值)
- Number (数值)
- String (字符串)
- Symbol
注意
- string、number、boolean和null undefined这五种类型统称为原始类型,表示不能再细分下去的基本类型;
- symbol是ES6中新增的数据类型,表示独一无二的值,通过调用Symbol()函数生成,且Symbol函数不能通过new调用。
- null和undefined通常被认为是特殊值,这两种类型的值唯一,就是其本身
对象类型
对象类型也叫引用类型即Object。Object中还包含了更为具体的引用类型,如Array Function Date RegExp Error等。
如何更好的理解基本类型和对象类型?
例1:
var a = 1;
var b = a;
a++;
console.log(a); //2
console.log(b); //1
解析: 把一个基本类型a赋值给变量b时,其实是分配了一块新的内存空间,因此上述变量a,b的变量对彼此无影响
例2:
var obj1 = {};
var obj2 = obj1;
obj2.name = 'hello';
console.log(obj1.name); //'hello'
console.log(obj2.name); //'hello'
解析: 对于引用类型来说,变量保存的是指针,该指针指向真正堆内存中存储的值,因此引用类型的赋值其实是指针的赋值,obj1和obj2都指向同一个对象,所以对obj1或obj2的操作都会相互影响
延申一: 变量提升
例1:
a = 2;
var a;
console.log(a); // 打印2
解析:如果按照程序自上而下执行的思想,上述代码应该会输出undefined;然而javascript并不是严格的自上而下执行的语言。
这段代码最后输出结果2,就是因为存在变量提升的概念(hoisting)。js引擎在执行js代码的时候,会将当前作用域的所有变量声明都提升到程序的顶部,因此上述的代码其实等价于 var a; a = 2; console.log(a);这样的话输出2就很好理解了。
例2:
console.log(a);
var a = 2;
解析:你觉得上述代码会报错还是输出2,或是其他的呢?
事实上上述代码会输出undefined。前面说过,变量声明会被提升到顶部,但是赋值语句不会提升,所以上面的代码等价于var a; console.log(a); a = 2;
例3:
console.log(a);
function a(){};
var a = 20;
解析: 上述代码会打印出function a(){}而不是undefined/20,因为变量提升会将函数声明和变量声明提升且函数声明优先于变量提升,所以这里会打印function a(){}。
注:如果存在多个函数声明,则最后面的函数声明会覆盖前面的
为什么有变量提升
js和其他语言一样,都要经历编译何执行阶段。而js在编译阶段的时候,会搜集所有的变量声明并且将声明提前(赋值和表达式不会提前),而不改变其他语句的顺序。
const和let声明与var声明的不同
const和let是ES6新增的语法,const一般用作常量声明,初始化后不允许重新赋值,let和const后面不允许出现同名变量且存在暂时性死区,也就是说在let声明变量之前使用该变量会报错,例如:console.log(a); let a = 20;(报错)
延申二:类型判断
- 对于原始类型,通常使用typeof操作符来判断变量类型
typeof 'a' -> 'string'
typeof 1 -> 'number'
typeof true -> 'boolean'
typeof Symbol(1) -> 'symbol'
typeof undefined -> 'undefined'
typeof null -> 'object' //由于历史遗留原因返回'object'
- 引用类型,通过Object.prototype.toString判断
Object.prototype.toString.call([1,2,3]) -> '[object Array]'
Object.prototype.toString.call(new Date()) -> '[object Date]'
延申三:类型转换
- 转Boolean
在进行条件判断时,除了undefined,null,false,NaN,'',0,-0,其他所有值都转为true,包括所有对象 - 四则运算符
1. 加法运算,其中一方是字符串类型,就会把另一个也转为字符串类型
2. 其他运算只要其中一方是数字,那么另一方就会转为数字
3. 加法运算会触发三种类型转换:将值转换为原始类型,转为数字,转为字符串 - 对象转基本类型
对象在转换为原始类型时,首先会调用valueOf,然后调用toString.并且这两个方法可以重写.
var a = {
valueOf () {
return 'test';
}
}
console.log(a + '') -> test
除此以外,还可以重写Symbol.toPrimitive,该方法转原始类型时调用的优先级最高
var a = {
valueOf(){
return 1;
},
toString(){
return 2;
},
[Symbol.toPrimitive](){
return 3;
}
}
1 + a => 4
'1' + a => '13'
- ==和===
当进行==比较时,如果左右两边类型不同,会将类型转成一致然后比较如:1 == '1'返回true
当进行===比较时,如果左右两边类型不同,直接返回false。1 === '1'返回false, 因此在实际项目中尽量避免使用==,以防出现意想不到的错误。