有你不知道的JavaScript
作用域
理解作用域,首先需要理解程序的运行过程中的几个重要角色
-
引擎
从头到尾负责整个JavaScript程序的编译及执行过程
-
编译器
负责语法解析、代码生成
-
作用域
负责收集并维护由所有声明的变量组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
下面举个小栗子来说明三者之间的联系
var a = 2
运行这行代码时会执行两个操作:首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后再运行阶段引擎会在作用域中查找该变量,如果能找到就会对其赋值(未找到赋值为undefind)。
词法作用域和动态作用域
词法作用域:词法作用域就是定义在词法阶段的作用域,或者说是,词法作用域是由你写代码时将变量和块作用域写在哪里决定的,因此当词法分析分析器处理代码时会保持作用域不变。
动态作用域:动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心在何处调用,或者说是,作用域链是基于调用栈的,而不是代码中的作用域嵌套。
JavaScript使用的是词法作用域
函数作用域
含义:属于这个函数的全部变量都可以在整个函数范围内使用及复用
“隐藏”变量和函数
function foo(a) {
var b;
b = a + bar(a * 2);
function bar (a) {
return a + 1;
}
return b
}
foo (2)
b // undefind
bar() // undefind
// 全局变量中无法访问函数内部变量与函数
规避冲突
立即执行函数(IIFE)
// 避免变量污染全局
var a = 2;
(function IIFE (glob) {
var a = 3
log( a ) // 3
log( glob.a ) // 2
})(window)
// 解决undedind被覆盖
undefind = true
(function IIFE ( undefind ) {
var a;
if (a === undefind) {
log( "a is undefind" )
}
})()
// 倒置代码运行顺序
var a = 2;
(function IIFE ( def ) {
def (window)
})(function (glob) {
var a = 3
log( a ) // 3
log( glob.a ) // 2
})
块作用域
“假”块级作用域
var foo = true
if (foo) {
var bar = foo * 2
}
console.log('bar', bar) // 仍然能访问 bar ,var 声明的变量写在哪里都是一样的,这仅仅只是看起来像是块级作用域,将var修改为let则会访问不到bar
try/catch块作用域
ES3中规定的 try/catch 会创建块级作用域
try {
undefind()
} carch (err) {
log (err) // 能正常执行
}
log(err) // err not find
let
闭包
当函数可以记住并访问所在的词法作用域时,就产生了闭包。
function foo () { var a = 2; function bar () { log (a) } bar()}foo() // 2 闭包的作用
应用
循环和闭包
for (var a = 0; i < 5; i++) { (function(index) { setTimeout(function() { log( index ) // 0, 1, 2, 3, 4 闭包的作用 }, 10) })(i)}
模块封装
function CoolModule () { var something = 'cool' var another = [1, 2, 3] function doSomething() { log(something) } function doAnother() { log(another) } return { doSomething, // 只对外暴露两个接口,隐藏内部变量 doAnother }}
模块模式必须具备两个条件:
- 必须有外部的封闭函数,该函数必须只被调用一次(每次调用都会被创建一个新的模块实例)
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态
this
当一个函数被调用时,会创建一个活动记录(上下文)。这个记录会包含函数在哪被调用(调用栈)、函数的调用方式、传入的参数等信息。this在函数执行过程中用到,它与绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
绑定规则
默认绑定
独立的函数调用,无法使用其他规则时的默认规则
function foo() { log(this.a) }var a = 2foo() // window
隐式绑定
调用的位置 是否有上下文
function foo() { log(this.a) }var obj = { a: 2, foo: foo}obj.foo() // obj
当函数引用有上下文时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象,或者是说,对象属性引用链中只有最后一层在调用位置起作用
显示绑定
function foo () { log(this.a)}var obj = { a: 2}foo.call(obj) // obj
new绑定
使用 new来构造foo(…)时,会构造一个新对象并把它绑定到foo(…)调用中的this
function foo(a) { this.a = a}var bar = new foo(2)log(bar.a) // bar
优先级
new绑定 > 显示绑定 > 隐式绑定
new操作步骤
- 构建一个全新的对象
- 这个新对象会被执行[[prototype]]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
bind、call、apply区别
// 调用区别Function.call(obj[, param1[, param2[, …[, paramN]]]])Function.apply(obj[, argArray])Function.bind(thisArg[, arg1[, arg2[, ...]]])// bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。
对象
属性描述符
var obj = { a : 2}Object.getOwnPropertyDescriptor(obj, "a");------------结果-------------{ value: 2, writable: true, 可写 是否可以修改值 enumerable: true, 可枚举 configurable: true, 可配置 }
configurable
为true,能通过defineProperty()
修改属性描述符,为false之后 ,不能再改回来,单向操作,并且还能 禁止 删除这个属性
对象常量
结合writable: false
和configurable: false
(不可修改、重定义或者删除)
禁止扩展
禁止对象添加新属性并且保留已有属性,可以使用Object.preventExtensions()
var obj = {a: 2}Object.preventExtensions(obj)obj.b = 3log(obj.b) // undefind
密封
Object.seal()
会创建一个密封对象,这个方法的实质是Object.preventExtensions()
结合configurable: false
,所以密封之后,所有属性不可扩展,不可重新配置或者删除现有的属性,但是可以修改属性值
冻结
Object.freeze()
冻结一个对象,实际上调用Object.seal()
并且结合writable: false
,禁止对于对象本身及其任意直接属性进行修改(这个对象引用的其它对象还是不受影响的)
深度冻结
在冻结的基础上,遍历属性,为对象则递归冻结
常用方法
调用方法 | 作用 | |
---|---|---|
Object.preventExtensions() | 阻止扩展 | |
Object.seal() | 密封 | |
Object.freeze() | 冻结 |
可枚举
调用方法 | 作用 | |
---|---|---|
for…in… | 遍历 | |
Object.keys() | 返回key数组 | |
Objeck.asign() | 拷贝 |