变量作用域

  1. 局部作用域
    var 声明局部变量,生命周期在函数内部. 而JavaScript的变量作用域实际上是函数内部,以及变量提升的作用,
    我们在for循环等语句块中是无法定义具有局部作用域的变量的:
    为了解决块级作用域,(如for循环), ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量:
  1. 全局作用域
    不在任何函数内定义的变量就具有全局作用域。
    JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的course属性.
    以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,
    因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象.
    JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。

命名空间

局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个自定义的全局变量中.

1
2
3
4
5
6
7
8
9
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};

把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。

常量

要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示这是一个常量,不要修改它的值:
ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域

解构赋值

ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
解构赋值在很多时候可以大大简化代码. 但是,需要在支持ES6解构赋值特性的现代浏览器中才能正常运行
注意:

  1. 对数组元素进行解构赋值时,多个变量要用[]括起来。
  2. 如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致
  3. 如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性
  4. 对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的
  5. 使用解构赋值对对象属性进行赋值时,如果要使用的变量名和属性名不一致,可以用下面的语法获取
  6. 解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题
  7. 如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误, 这是因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法。解决方法是用小括号括起来:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
    let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
    var {name, age, passport} = person;
    var {name, address: {city, zip}} = person;
    // 把passport属性赋值给变量id:
    let {name, passport:id} = person;
    // 如果person对象没有single属性,默认赋值为true:
    var {name, single=true} = person;
    var x, y;
    // 解构赋值:
    {x, y} = { name: '小明', x: 100, y: 200}; // 语法错误: Uncaught SyntaxError: Unexpected token =
    ({x, y} = { name: '小明', x: 100, y: 200}); //正确

方法

绑定到对象上的函数称为方法,和普通函数也没啥区别,但是它在内部使用了一个this关键字,这个this怎么理解?
在一个方法内部,this是一个特殊变量,它始终指向当前的调用对象.
JavaScript的函数内部如果调用了this,那么这个this到底指向谁?
答案是,视情况而定,根据当前调用者来确定. 且必须保证this指向正确,必须用obj.xxx()的形式调用!

1
2
3
4
5
6
var fn = xiaoming.age; // 先拿到xiaoming的age函数
fn(); // NaN
'use strict';
var fn = xiaoming.age;
fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined

这是一个设计错误,ECMA决定,在strict模式下让函数的this指向undefined,因此,在strict模式下,你会得到一个错误.
这个决定只是让错误及时暴露出来,并没有解决this应该指向的正确位置。
修复的办法:用一个that变量首先捕获this,用var that = this;,你就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。

另一个解决办法:apply
指定函数的this指向哪个对象,可以用函数本身的apply方法,它接收两个参数:
第一个参数就是需要绑定的this变量,
第二个参数是Array,表示函数本身的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空

另一个与apply()类似的方法是call(),唯一区别是:
apply()把参数打包成Array再传入;
call()把参数按顺序传入。

1
2
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

对普通函数调用,我们通常把this绑定为null。

原型链继承方式

JavaScript的原型继承实现方式就是:

  1. 定义新的构造函数,并在内部用call()调用希望“继承”的构造函数,并绑定this;
  2. 借助中间函数F实现原型链继承,最好通过封装的inherits函数完成;
  3. 继续在新的构造函数的原型上定义新方法。

类class

从ES6开始新的关键字class正式被引入到JavaScript中,class的目的就是让定义类更简单。
类的继承使用通过extends来实现.

浏览器对象

JavaScript可以获取浏览器提供的很多对象,并进行操作。
常用的浏览器对象有: window, navigator, screen, location, document, history

window对象充当全局作用域,且表示浏览器窗口. 兼容性:IE<=8不支持。
有innerWidth和innerHeight属性,可以获取浏览器窗口的内部宽度和高度。
内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。
outerWidth和outerHeight属性,可以获取浏览器窗口的整个宽高。

navigator对象表示浏览器的信息,最常用的属性包括:
navigator.appName:浏览器名称;
navigator.appVersion:浏览器版本;
navigator.language:浏览器设置的语言;
navigator.platform:操作系统类型;
navigator.userAgent:浏览器设定的User-Agent字符串。
注意: navigator的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的.

screen对象表示屏幕的信息,常用的属性有:
screen.width:屏幕宽度,以像素为单位;
screen.height:屏幕高度,以像素为单位;
screen.colorDepth:返回颜色位数,如8、16、24。

location对象表示当前页面的URL信息
location.protocol; // ‘http’
location.host; // ‘www.example.com’
location.port; // ‘8080’
location.pathname; // ‘/path/index.html’
location.search; // ‘?a=1&b=2’
location.hash; // ‘TOP’
要加载一个新页面,可以调用location.assign()。如果要重新加载当前页面,则调用location.reload()方法.

document对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点。
document的title属性是从HTML文档中的<title>xxx</title>读取的,但是可以动态改变.
document对象提供的getElementById()和getElementsByTagName()可以按ID获得一个DOM节点和按Tag名称获得一组DOM节点.
document对象还有一个cookie属性,通过document.cookie读取到当前页面的Cookie.
由于JavaScript能读取到页面的Cookie,而用户的登录信息通常也存在Cookie中,这就造成了巨大的安全隐患,
因为在HTML页面中引入第三方的JavaScript代码是允许的,为了解决这个问题,服务器在设置Cookie时可以使用httpOnly,
设定了httpOnly的Cookie将不能被JavaScript读取。这个行为由浏览器实现,主流浏览器均支持httpOnly选项,IE从IE6 SP1开始支持。
为确保安全,服务器端在设置Cookie时,应该始终坚持使用httpOnly。

history对象保存了浏览器的历史记录,JavaScript可以调用history对象的back()或forward (),相当于用户点击了浏览器的“后退”或“前进”按钮。
新手开始设计Web页面时喜欢在登录页登录成功时调用history.back(),试图回到登录前的页面。这是一种错误的方法。
任何情况,你都不应该使用history这个对象了。

参考

  • 廖雪峰官方网站