特点
- js大量借鉴了C,java,Perl的语法,十分的宽松
- js是区分大小写的
变量,函数名
注释
使用c格式的注释
// 这是一个注释
/*
* 这是一个多行注释
* 除了开头结尾,每行前面的*不是必须的,但是推荐这么做
*/
严格模式
ES3的一些特性会导致代码运行有一些不确定性,一些特性也会让写出来的代码不太安全,在ES5里面为了避免这种不确定性,推出了严格模式。
严格模式下,js运行结果也会不同,要开启严格模式使用下面的代码
// 在一个js文件的开头写则这个js都是严格模式运行,在一个函数的第一行写,这个函数使用严格模式运行
"use strict";
需要注意,不是所有的浏览器都支持严格模式。
变量
定义变量使用var,注意在函数中var的变量是局部变量
function test(){
var message = "hi";
}
test();
// 会报错
alert(message);
在局部变量中不定义直接使用的变量是全局变量,可以访问,但是严格模式不允许变量不定义就使用
function test(){
message = "hi";
}
test();
// 可以访问,但是严格模式下报错,不推荐
alert(message);
变量的声明提升
虽然我们写 var a = 1;认为是定义了这个变量,但是事实上这一句代码包含两步动作:
而无论是变量定义还是函数定义,都存在提升的情况,上面的过程1会在程序运行之前全部执行完成,而赋值语句会在代码位置执行,所以就会出现下面的情况
alert(a); // 提示undefined,不报错
var a = 1;
alert(a); //报错 a未定义
关于提升我们还需要注意额外的两点:
- 函数定义也会提升
- 使用ES6 中let定义的变量不能在定义之前使用
关于之前讲的,大家在看懂的基础上,需要注意,js由于语法比较松散,会造成很多看似奇怪的问题,一方面,我们在学习的时候要搞清楚这些原理,比如上面讲的 定义提升而赋值不提升,另一方面,我们应该养成好的编程习惯尽量的避免奇怪问题的发生,在这里,我们可以这样做:
- 不管是使用var 还是 let 都先定义再使用
- 函数的定义不一定非要在使用之前,这个就比较灵活了,可以把主函数写在最上面,之后补上子函数的定义
变量的作用域
js的作用域是用作用域链来运行的,作用域链可以向上查找不能向下查找。举例说明一下:
// 在A的作用域中,不能访问到b
// 但是在B的作用域中,能访问到a
function A(){
var a;
function B(){
var b;
}
}
js中,被function保护的仍然是局部变量
function A(){
var a = 1;
function B(){
var b = 2;
}
}
A();
alert(a); // 不能访问a,a是局部变量
我们要分清楚局部作用域和块级作用域,局部是function包起来的作用域,块作用域是if switch for等包起来的作用域,之后我们会再讲到块的定义,**大括号括起来就是一个块。**在ES6的let没有出现之前,js是没有块级变量的。
// 没有块变量
if(true){
var a = 1;
}
alert(a); // 输出1,一旦if中的代码执行,a就被加入到了当前的环境作用域中
if(false){
var a = 1;
}
alert(a); // 输出undefined,目前环境作用域中没有这个变量
作用域链只能向上查找,有两个情况会打破这种规范,都十分好理解:
注意:
ES6 - 声明变量
- var 以前就有
- function 以前就有
- let 新增,下面讲到
- const 新增,下面讲到
- import 新增,以后讲
- class 新增,以后讲
注意我们之前讲到使用var定义的变量实际上是window的属性,但是新增的集中声明方式声明的变量不会是window的属性,这也是一个很大的改良。
var a = '1';
alert(window.a); //1
let b = '2';
alert(window.b); //undefined
ES6 - let
在ES6之前没有块级作用域,ES6规定了块变量,使用let定义
下面对于let的使用场景做一些简单的描述:
- 我们之前说了,function括起来就是一个作用域,现在我们说,大括号括起来就是一个块
{
let a = 1;
var b = 2;
}
alert(b); //2
alert(a); //a is not defined
- for 返回函数的经典问题,这个是我们之前用于证明var定义的不是块变量而是局部变量的经典例子,let解决了这个问题
// 使用var 返回都是5
// 因为for使用的i每次都是同一个i,导致后一次遍历a的时候返回的i都是最后变成5的i
var a = [];
for(var i = 0; i < 5; i++){
a[i] = function(){
alert(i);
};
}
for(var j = 0; j < 5; j++){
a[j](); // 返回都是5
}
// 使用let 返回 0 1 2 3 4
// 因为let定义的i是块作用域的变量,只在这个块生效,for使用的i每次都不是同一个i,所以后一次遍历a的时候返回的i都是各自作用域的i
var a = [];
for(let i = 0; i < 5; i++){
a[i] = function(){
alert(i);
};
}
for(var j = 0; j < 5; j++){
a[j](); // 返回 0 1 2 3 4
}
- 注意for的一个特别之处,一个for会定义出两个父子关系的块作用域,设置的循环变量在一个块作用域中,大括号中循环体在一个子作用域中,所以使用i循环,在循环体中还能定义另一个i,这两个i互不影响
for(let i = 0; i < 5; i++){
let i = 'a';
alert(i); // 打印5个i
}
这里我们需要注意,虽然可以这么写,但是我们并不建议这么做,大家知其然,但是要尽量避免奇怪代码,这就是我们常说的代码的“易读性”。 - 注意只要使用let,变量就不能重复定义,使用两个var定义可以,只要有let就不能重复定义
// 可以使用 a =2
var a = 1;
var a = 2;
// 报错
let a = 1;
var a = 2;
// 报错
var a = 1;
let a = 2;
// 报错
let a = 1;
let a = 2;
- let使用的时候,需要先定义后使用,这个跟ES5中var在严格模式下是一致的
- 在块中尽量使用块变量,以避免由于不小心定义了和外部变量一样的名称的变量再加上声明提升导致的奇怪的问题
var a = [1];
function f(){
alert(a); // undefined
if(false){
var a = 1;
alert(a);
}
};
f();
var a = [1];
function f(){
alert(a); // [1]
if(false){
let a = 1;
alert(a);
}
};
f();
ES6 - let 和 const造成的暂时性死区(TDZ)
我们前面学了在let定义之前使用该变量会报错,我们就会想,说是会报错,我们不这样用就行了呗。而我们这一节就是要告诉大家,这么用会造成的后果。
在一个块中,如果出现了let 或者 const,这个块就被锁起来了,他们声明的变量无论是否有全局声明过同名变量都不能使用,这就叫暂时性死区,我们看下例子:
var a = 1;
{
alert(a); //报错 a is not defined
let a;
}
这对我们有什么影响呢?
ES6-块作用域
ES6-使用const定义常量
我们使用const定义一个声明了之后就不能改变的常量,注意下面几点:
- const声明的时候就必须初始化值,不初始化会报错
- const声明了之后修改其值会报错
- const也是块作用域生效
- 与let一样声明不会提升,会出现暂时性死区
- 不可重复声明
- 对于对象不生效,只能保证对象的指针不变,不能保证对象里面的属性不变,创建不能修改的对象应该用Object.freeze
var o = Object.freeze({ name: '123' });
alert(o.name);
o.name = '345'; //严格模式报错
alert(o.name);
这个方法只能冻结对象的属性,如果对象的属性还是对象,如果都需要冻结,需要用到递归var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};