特点
- 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;认为是定义了这个变量,但是事实上这一句代码包含两步动作:
- var a 声明变量
- 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,目前环境作用域中没有这个变量
作用域链只能向上查找,有两个情况会打破这种规范,都十分好理解:
- with语句(严格模式不可用)。with(location)会创建一块顶层作用域,在这里面定义的变量会定义在顶层作用域中,自然可以被其上层作用域获得。这个语句等于开挂将自己定义的变量加到了运行环境的作用域中。
- 下一个会打破规范的是try catch finally,这个也好理解,在catch块中定义的变量,在外部也能访问,因为catch(ex)还会传递参数进去,所以catch被认为是一个函数,自己创建了自己的下级作用域,但是它里面的变量会被暴露出来,即打破了作用域链的规范
try { throw "A"; } catch (error) { var a = "2"; }finally{ } alert(a); // 输出2 // 这样访问对性能有一定的影响,所以应该尽量避免这么用,尽量不要再catch中定义变量在外部访问
注意:
- 不写var,变量会被定义到全局环境中,在严格模式下不允许未定义就使用一个变量
- 变量的值会顺着作用域链向上查找,如果这个链中有多个,只要找到一个就停止了,这里看一个例子
var a = 1; function A(){ var a = 2; return a; } alert(A()); // 结果 2 alert(a); // 结果 1
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;
}
这对我们有什么影响呢?
- typeof有可能会报错了,之前typeof判断一个变量如果没有声明就会报undefined,但是现在,如果在块中变量定义之前使用typeof,就会报错
var a = 1; { typeof a; //报错 a is not defined let a; }
ES6-块作用域
- 我们说let定义了一个块级的变量,反过来也可以说,let的出现使得js新增了块级作用域
- 块作用域可以任意嵌套
- 外层作用域不能探知里层作用域的块变量,里层作用域能探知外层作用域的块变量
- 里层可以定义与外层相同名称的块变量(C#不允许这么做)
- 我们对比一下块作用域出现之前我们使用函数作用域模拟块作用域的做法
(function(){ var a = 1; // 使用函数作用域模拟块作用域 })();
{ let a = 1; }
- 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] ); } }); };