标签: js 还有原型链要更新呀
作用域
- 作用域是查找变量的一套规则
- 如果查找的目的是对变量进行赋值,则进行LHS查询;如果查找的目的是获取变量的值,则进行RHS查询。Left/Right Hand Sidei,变量在等号左边或等号右边
- LHS查询:赋值操作,如=操作符、调用函数时传参
function foo(e) {
var b = a;
return a + b;
}
var c = foo(2);
- 例子中LHS查询有三处:b=a c=foo(2) 把2赋给a(隐式变量分配)
- RHS查询有四处:foo(2) =a a b
- 异常
- ReferenceError,引用一个未被定义的变量。
- TypeError,对结果的操作不合理,如引用一个undefined类型的值的属性
- SyntaxError,语法错误
- 如何判断当前是否在严格模式下:函数直接调用时this指向运行时环境的全局对象。在非严格模式下,浏览器中全局对象为Window,Nodejs控制台中全局对象为Global;在严格模式下,全局对象是undefined。因此可判断如下
var isStrict = (function(){return !this;}());
console.log('isStrict ' + isStrict);
- 编译。传统编译语言,代码执行前需要编译,包括三个过程:词法分析(将代码拆分为词法单元)、语法解析(将词法单元数组转换为语法树)、代码生成(生成可执行代码)
词法作用域VS动态作用域
- 编译的词法分析阶段知晓各标识符及如何声明的,因此可以在执行过程中进行查找。词法作用域意味着作用域在书写代码时函数声明的位置决定。
eval with- 词法作用域又叫静态作用域,和动态作用域的区别是动态作用域是在运行时而非定义时确定的。词法作用域关注函数在何处声明,动态作用域关注函数从何处调用,其作用域链是基于运行时的调用栈
- wiki:大多数现在程序设计语言都是采用静态作用域规则,如C/C++、C#、Python、Java、JavaScript;采用动态作用域的语言有Pascal、Emacs Lisp、Common Lisp(兼有静态作用域)、Perl(兼有静态作用域)。C/C++是静态作用域语言,但在宏中用到的名字,也是动态作用域。
// 词法作用域:
function foo() {
print a; // 输出2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
// 动态作用域
function foo() {
print a; // 输出3而不是2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
函数作用域
- 最小特权原则
- 在软件设计中,应该最小限度地暴露必要内容,而将其隐藏起来,比如某个模块或对象的API设计。
- 避免暴露
私有
的变量或函数,避免其被有意或无意地以非预期的方式使用。
function doSomething(a){
b = a + doSomethingElse(a * 2);
console.log(b * 3);
}
function doSomethingElse(e){
return e - 1;
}
var b;
doSomething(2); // 15
- 上例中b doSomethisElse均应是doSomething的内部内容,改进:
function doSomething(a){
function doSomethingElse(e){
return e - 1;
}
var b;
b = a + doSomethingElse(a * 2);
console.log(b * 3);
}
doSomething(2); // 15
- 立即执行函数表达式IIFE 的两种形式
(function(){console.log('a');})() // a
// 第一个()将函数变成表达式,第二个()立即执行这个函数
+function(){console.log('a');}() // a
// + 也会将函数变成一个表达式,js会解析成运算(bootstrap的js源码采用此种方式),第二个()立即执行这个函数
js中神奇的 +
new Date(); // Fri Aug 05 2016 16:58:08 GMT+0800 (CST)
+ new Date; // 1470387494773 (一个时间戳)
// 时间戳:unix时间,从`协调时间时`1970年1月1日0时0分0秒起至现在的总秒数
- IIFE进阶1:当作函数调用并传参,提高可读性,改进代码风格。
var a = 2;
(function IIFE(global){
var a = 1;
console.log(a); // 1
console.log(global.a); // 2
})(window);
- IIFE进阶2:倒置代码的运行顺序,将需要运行的代码放在第二位,在IIFE执行后当作参数传递进去
var a = 2;
(function IIFE(def){
def(window);
console.log(a); // 2
}(function def(global){
var a = 1;
console.log(a); // 1
console.log(global.a); // 2
}));
块级作用域
let ES6变量声明
- 隐式绑定在所在块级作用域
var foo = true;
if (foo) {
var bar = foo * 2;
console.log(bar); // 2
}
console.log(bar); // 2
var foo = true;
if (foo) {
let bar = foo * 2;
console.log(bar); // 2
}
console.log(bar); // ReferenceError: bar is not defined
- 显式创建一个块级作用域
var foo = true;
if (foo) {
{
let bar = foo * 2;
console.log(bar); // 2
} // 一个显式的块
console.log(bar); // ReferenceError: bar is not defined
}
console.log(bar); // ReferenceError: bar is not defined
- let声明的变量在块级作用域中不会提升
- 垃圾回收,let块级作用域中的内容执行完后被销毁
- let循环
for(let i=0;1<10;i++) {
console.log(i);
}
console.log(i); // ReferenceError: i is not defined
- let将i绑定在for循环的块中,不会污染全局变量,并且将其重新绑定在循环的每一次迭代中。
const ES6变量声明
- const声明的变量只在声明时赋值,不可修改,否则会报错
- const声明的变量必须在声明时就赋值。
var foo = true;
if(foo) {
var a = 2;
const b = 3;
const c = {};
const d; // SyntaxError: Missing initializer in const declaration
a = 3;
b = 4; // TypeError: Assignment to constant variable
c.a = 1;
console.log(c); // {a: 1}
console.log(c.a); // 1
}
console.log(a); // 3
console.log(b); // ReferenceError: b is not defined
提升(hoisting)
- 理解提升:引擎会在解释js代码前首先进行编译,编译的一部分工作就是找到所有声明,并用合适的作用域将他们包裹起来。所有声明会在代码执行前被处理。
- 编译阶段声明会被提升,赋值或其他运算逻辑留在原地在执行过程中被处理。
- 函数声明提升的同时,包含了实际函数的隐藏值。
foo(); // undefined;
function foo() {
console.log(a);
var a = 1;
}
// 此时foo()可正常调用,foo()函数内部也有变量提升
- 函数表达式和变量声明类似,变量标识符会提升,赋值没有被提升
foo(); // TypeError: foo is not a function
var foo = function() {
console.log(a);
var a = 2;
}
// 此时报错不是ReferenceError,因为此时变量标识符foo被提升到了顶部,var foo;但赋值未被提升,此时foo的值是undefined,对一个undefined值进行函数调用是非法操作,因此抛出TypeError异常
- 提升中函数优先
foo(); // 1
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
}
引擎解释:
function foo() {
console.log(1);
}
foo();
foo = function() {
console.log(2);
}
// var foo;是重复声明被忽略。
foo(); // 此时再调用foo()值为2,第二次定义的函数表达式覆盖了前面的函数声明。
- 一个函数声明会被提升到所在作用域的顶部
foo(); // TypeError: foo is not a function
var a = true;
if (a) {
function foo(){console.log(11);}
} else {
function foo(){console.log(22);} //
}
// 老版本中两个foo函数会被提升,第二个foo函数会覆盖第一个,因此foo()执行结果是22;新版本中if块内仅提升函数声明,但未包含实际函数的隐藏值,因此foo()执行结果是TypeError
作用域闭包
- 闭包:内部函数可以访问外部函数作用域,并持有对原始词法作用域的引用。
- 经典例子:循环与闭包
for(var i=0;i<5;i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
- 输出五次5的原因:循环中的五个函数在每次迭代中分别定义,但它们被封闭在一个共享的全局作用域,因此共享一个i的引用。
- 解决方法是给每次循环的函数创建一个单独的作用域
for(var i=0;i<5;i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log(j);
}, j*1000);
})()
}
// IIFE为每次迭代创建一个新的作用域,延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代都有一个正确的变量。
or
for(let i=0;i<5;i++) { // i在每次迭代都会声明
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
or
for(var i=0;i<5;i++) {
let j = i; // 创建块级作用域
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
this
- 隐式传递一个对象引用
- 一个函数被调用时,会创建一个执行上下文,包含函数在哪里被调用,函数的调用方式,传入的参数等,this是其中的一个属性,是函数被调用时发生的绑定。this既不指向函数本身也不指向函数的词法作用域。
- 直接调用 new dot(.) call apply bind
function fo(){this.x='x';};
// 直接调用
fo(); // ()代表立即执行
x; // 'x' this指向全局对象window
var fn = function(){'use strict';this.x='x';};
x; // ReferenceError: x is not defined,此时全局对象是undefined;
// new调用
var foo = new fo();
foo.x; // 'x'
// dot(.)调用
var aa = {x: 'aaa',fo: fo};
aa.x; // 'aaa'
aa.fo; // function fo(){this.x='x';};
aa.fo();
aa.x; // 'x'
// call
var b = {x: 'bb'};
b.x; // 'bb'
b.fo(); // TypeError: b.fo is not a function
fo.call(b);
b.x; // 'x'
// apply bind 用法和call相似,this均指向fo.call(args),args的第一个参数
对象
- javascript六种类型:string number boolean null undefined object
- 内置对象,对象子类型:string number boolean object function array date regexp error
- ES6增加了可计算属性名,用[]包裹,eg:
var prefix = foo;
var myObject = {[prefix + 'bar']: 'hello', [prefix + 'baz']: 'world'};
myObject['foobar']; // hello
myObject['foobaz']; // world
- 数组也是对象,但是最好用对象存储键值对,用数组存储数组下标值对;
// 给数组定义数字形式的属性名的bug
var myArray = ['hello', 'world', 42];
myArray.baz = 'bar';
myArray.length; // 3
myArray.baz; // 'bar'
myArray['3'] = 'test';
myArray.length; // 4
myArray; // ['hello', 'world', 42, 'test']
- 检查属性是否存在
var myObject = { a: 2 };
myObject['b']; // 2
myObject.b; // 2
'a' in myObject; // true
myObject.hasOwnProperty('a'); // true
// in操作符检查属性是否在对象中或prototype原型链中;hasOwnProperty仅检查属性是否在对象中。
- 属性描述符 ES5
- getOwnpropertyDescriptor查询对象的属性的属性描述符,格式:Object.getOwnpropertyDescriptor(对象名,'属性名');
Object.getOwnPropertyDescriptor(myObject,'a');
// Object {
// value: 2,
// writable: true, 可写,可修改属性的值value
// enumerable: true, 可枚举,出现在对象属性的遍历中
// configurable: true 可配置,可用defineProperty修改属性描述符,删除一个属性delete myObject.a; if false,除value和writable外的特性不能被改变。
// }
- defineProperty添加一个新属性或修改一个已有属性(的属性描述符),格式:Object.defineProperty(对象名,'属性名',{});
Object.defineProperty(myObject,'b',{
value: 3,
writable: true,
configurable: true,
enumerable: true
}); // Object {a: 2, b: 3}
Object.getOwnPropertyDescriptor(myObject,'b');
// Object {value: 3, writable: true, enumerable: true, configurable: true}
- 枚举属性 enumerable
var myObject = {};
Object.defineProperty(myObject,'a',{
value: 2,
enumerable: true
});
Object.defineProperty(myObject,'b',{
value: 3,
enumerable: false
});
myObject.b; // 3
'b' in myObject; // true
myObject.hasOwnProperty('b'); // true
for(var k in myObject) {
console.log(k+': '+myObject[k]);
} // a: 2
// myObject.b存在且可正常访问,但不出现在属性的遍历中
myObject.propertyIsEnumerable('b'); // false
// propertyIsEnumerable检查给定的属性名存在于对象中(不包括原型链中)&& 可枚举的
Object.keys(myObject); // ['a'] 返回可枚举属性
Object.getOwnPropertyNames(myObject); // ['a','b'] 返回所有属性
- 数组遍历注意事项:数组上应用for..in循环时不仅包含所有数值索引,且包含所有可枚举属性;因此遍历数组最好使用传统for循环或forEach循环。ES6中新增for..of循环遍历数据结构。数组有内置的@@iterator,因此for..of可直接遍历数组,而不能直接遍历对象。P122
var myArray = ['hello','world',42];
myArray.baz = 'bar';
myArray['3']='test';
myArray; // ["hello", "world", 42, "test"]
for(var k in myArray) {console.log(k,myArray[k]);}
// 0 hello 1 world 2 42 3 test baz bar
for(vari=0;i<myArray.length;i++){console.log(i,myArray[i])};
// 0 hello 1 world 2 42 3 test
myArray.forEach(function(e){console.log(e)});
// hello world 42 test
for(var v of myArray){console.log(v);};
// hello world 42 test
for(var v of myObject){console.log(v);};
// TypeError: myObject[Symbol.iterator] is not a function
网友评论