网站菜单

一些对JavaScript作用域的理解笔记

这是篇读书笔记,记录《你不知道的JavaScript》上卷的第一部分内容,同时也希望能帮助大家理解好作用域的知识点,梳理好的内容更容易理解和掌握 编译原理理解作用域之前,我们先要理解编译原理。JavaScript引擎进行编译的步骤和传统的编译语言非常相似,一般都经过三个步骤: 分词/词法分析 解析/语法分析 代码生成 分词/词法分析(Tokenizing/Lexing)这个过程会将由字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元。例如var a = 2;,会被拆分为var、a、=、2、; 解析/语法分析(Parsing)这个过程会将词法单元流(数组)转换为一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树被称为“抽象语法树”(AST) 代码生成这个过程会将AST转换为可执行代码 JavaScript引擎是要复杂得多,例如在词法分析和代码生成阶段有特定的步骤来对运行性能进行优化,也不会有大量的时间用来进行优化 理解作用域引擎,从头到尾负责整个JavaScript程序的编译及执行过程 编译器,从属于引擎,负责语法分析和代码生成等工作 作用域,从属于引擎,负责收集并维护由所有声明的标识符组成的一系列查询 面对var a = 2;这段程序的时候,引擎、编译器和作用域之间的工作流程大致如下: 1、编译器将这段程序分解为词法单元2、编译器将这段程序的词法单元解析成一个树结构(AST)3、编译器根据这个AST去代码生成4、编译器在代码生成阶段会询问作用域是否存在变量5、编译器生成引擎所需的代码6、引擎接到所需代码后,询问作用域是否有这个变量7、引擎找到这个变量后,直接使用这个变量去完成赋值 引擎怎么查找这个变量,就是LHS和RHS的情况了 LHS查询是识图找到变量的容器本身,从而可以对其赋值;RHS查询则是找到某个变量的值 从这个过程中,我们可以看到作用域出现了两次,一次是编译器询问作用域是否存在变量,另一次是引擎询问作用域是否有这个变量 第一次可能会出现让作用域在它的集合内声明一个新的变量,而第二次则不会,只会去找 那么很显然,作用域就是一套给编译器和引擎看的规则,用于确定在何处和如果查找变量(标识符) 词法作用域什么是词法作用域? 首先编译器的第一个工作是将程序分解为词法单元,这个阶段也叫词法化或单词化。在这个词法化阶段,词法分析器处理代码时会保持作用域不变,换句话说就是,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的。 例如 function foo(a){ var b = a*2; function bar(c){ console.log(a,b,c); } bar(b*3); } foo(2); //2,4,12 这个例子中有三个逐级嵌套的作用域全局作用域只有一个标识符:foo;foo所创建的作用域有三个标识符:a、bar和bbar所创建的作用域只有一个标识符:c 无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置所决定 函数作用域函数作用域? 属于这个函数的全部变量都可以再整个函数的范围内使用及复用,指的就是函数作用域 例如 function foo(a){ var b = a*2; function bar(c){ console.log(a,b,c); } bar(b*3); } bar(2); //ReferenceError console.log(a,b,c); //ReferenceError foo()所创建的作用域内有三个标识符,a、b、bar,bar()所创建的有标识符有c 函数的作用域? 区分函数声明与表达式最简单的方法是看function的关键字出现在声明中的位置,如果是第一个词,那么就是一个函数声明,否则就是一个函数表达式 函数声明与函数表达式之间最重要的区别在于它们的名称标识符将会绑定在何处 例如 var a = 2; function foo(){ var a = 3; console.log(a); } foo(); console.log(a); 这个foo被绑定在所在的作用域,所以可以直接调用 var a = 2; (function foo(){ var a = 3; console.log(a); })(); console.log(a); 而这个foo被绑定在函数表达式自身的函数中,而不是所处的作用域中 块作用域with关键字,是一个块作用域的例子,用with从对象中创建出的作用域仅在with声明中有效 try/catch的catch分句也会创建一个块作用域,其中的声明的变量仅在catch中有效 ES6引入的let和const关键字都可以创建块作用域变量...

从一道JavaScript题目学点正则

这是一篇普通的教程,同时也是我的一篇笔记。起因是看到一道题目的另外一种解法特别有意思,同时也做一点正则的笔记,好理解。 题目我印象中的这道题目是:有一组数组为[1,1,2,3,3,3,3,4,5,5,5,6,6]使用js把它变成 [[1,1],2,[3,3,3],4,[5,5,5].[6,6]] 解法有很多,我这里只列出两种 解法一我们平时的解法一般为:数组嵌套,将含有相同的值得数组放入新数组里面然后对新数组进行,对里面的嵌套数组进行个数判断并返回值,重新组成一个新数组新数组就是答案 var arr = [1,1,2,3,3,3,3,4,5,5,5,6,6]; var tempArr = []; var result = []; var i,len,item,lastArr; for(i = 0,len = arr.length;i < len;i++){ item = arr[i]; lastArr = tempArr.slice(-1)[0]; if(!lastArr || lastArr[0] != item){ lastArr = []; tempArr.push(lastArr); } lastArr.push(item); } for(i=0,len=tempArr.length;i 1 ? item : item[0]); } console.log(result); 解法二如果我们用正则的话,那解法如下: var arr = [1,1,2,3,3,3,3,4,5,5,5,6,6]; var result = "[" + (arr.toString() + ",") .replace(/(([^,]+,)\2+)/g,'[$1],') .replace(/,(]|$)/g,'$1') + "]"; console.log(JSON.parse(result)); 这样一下子就变得很高效,正则找出两个以上的相同值的位置,插入[]然后打印出来即可。 从这段代码,我们可以看出,先对把数组字符串化 var result = "[" + (arr.toString() + ",") + "]"; 变成[1,1,2,3,3,3,3,4,5,5,5,6,6,] 然后匹配两个以互为相同的值,套上[] var result = "[" + (arr.toString() + ",") .replace(/(([^,]+,)\2+)/g,'[$1],') + "]"; 最后是找到]前面多余的, var result = "["...

JavaScript的运算符

JavaScript 拥有如下类型的运算符 赋值运算符(Assignment operators) 比较运算符(Comparison operators) 算数运算符(Arithmetic operators) 按位运算符(Bitwise operators) 逻辑运算符(Logical operators) 字符串运算符(String operators) 条件 (三元) 运算符(Conditional operator) 逗号运算符(Comma operator) 一元运算符(Unary operators) 关系运算符(Relational operator) JavaScript 拥有二元(binary)和一元(unary)运算符, 和一个特殊的三元(ternary)运算符(条件运算符) 赋值运算符一个 赋值运算符(assignment operator) 赋一个基于它右边操作数值的值给它左边的操作数 名字 速记 等同于 赋值 Assignment x = y , x = y 加法赋值 Addition assignment x += y, x = x + y 减法赋值 Subtraction assignment x -= y , x = x - y乘法赋值 Multiplication assignment x = y , x = x y 除法赋值 Divisionassignment x /= y , x = x / y 求余赋值 Remainder assignment x %= y , x = x% y 求幂赋值 Exponentiation...

JavaScript闭包与箭头函数

闭包闭包是JavaScript中最强大的特性之一 JavaScript允许函数嵌套 内部函数可以访问定义在外部函数中的所有变量和函数以及外部函数能访问的所有变量和函数 外部函数不能够访问定义在内部函数中的变量和函数 当内部函数生存周期大于外部函数时,由于内部函数可以访问外部函数的作用域,定义在外部函数的变量和函数的生存周期就会大于外部函数本身 当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了 var pet = function(name) { // The outer function defines a variable called "name" var getName = function() { return name; // The inner function has access to the "name" variable of the outer function } return getName; // Return the inner function, thereby exposing it to outer scopes }, myPet = pet("Vivie"); myPet(); // Returns "Vivie" 下面例子,返回了一个包含可以操作外部函数的内部变量方法的对象 var createPet = function(name) { var sex; return { setName: function(newName) { name = newName; }, getName: function() { return name; }, getSex: function() { return sex; }, setSex: function(newSex) { if(typeof newSex == "string" &&...

JavaScript的函数

定义函数函数的定义(也称为函数的声明)由一系列的函数关键词组成 函数的名称 函数引数列表,包围在括号( )中并由逗号 , 区隔 函数功能,包围在花括号{ }中,用于定义函数功能的一些JavaScript语句 例如 function square(number) { return number * number; } 函数square使用了一个参数,叫作number这个函数只有一个语句,它说明该函数会将函数的参数(即number)自乘后返回函数的return语句确定了函数的返回值 如果你传递一个对象(pass an object),作为参数,而函数改变了这个对象的属性,这样的改变对函数外部是可见的 function myFunc(theObject) { theObject.make = "Toyota"; } var mycar = {make: "Honda", model: "Accord", year: 1998}, var x, y; x = mycar.make; // x 获取的值为 "Honda" myFunc(mycar); y = mycar.make; // y 获取的值为 "Toyota" // (make属性的值在函数中被改变了) 函数表达式 var square = function(number) { return number * number }; var x = square(4); // x 得到的值为16函数表达式也可以提供函数名,用于在函数内部使用来代指其本身 var factorial = function fac(n) {return n

JavaScript的异常处理

异常处理用throw 语句抛出一个异常并且用try...catch 语句捕获处理它 异常类型JavaScript可以抛出任意对象。但是通常用下列其中一种异常类型来创建目标更为高效 ECMAScript exceptions DOMException nsIXPCException (XPConnect) 抛出语句 throw expression; 下面的代码抛出了几个不同类型的表达式 throw "Error2"; // String type throw 42; // Number type throw true; // Boolean type throw {toString: function() { return "I'm an object!"; } }; 你可以在抛出异常时声明一个对象。那你就可以在捕捉块中查询到对象的属性。下面的例子创建了一个UserException类型的对象myUserException用在抛出语句中。 // Create an object type UserException function UserException (message){ this.message=message; this.name="UserException"; } // Make the exception convert to a pretty string when used as // a string (e.g. by the error console) UserException.prototype.toString = function (){ return this.name + ': "' + this.message + '"'; } // Create an instance of the object type and throw it throw new UserException("Value too...

JavaScript的流程控制和迭代语句

语句块 { statement_1; statement_2; statement_3; ... statement_n; } 这里 { ...; } 就是语句块。 var x = 1; { var x = 2; } alert(x); // 输出的结果为 2 var 定义的变量是声明局部和全局的let 定义的变量是块作用域的 条件判断语句 if...else 一种条件判断 if (condition) { statement_1; } [else { statement_2; }] //推荐使用严格的语句块模式,语句else可选 多种条件判断 if (condition_1) { statement_1; } [else if (condition_2) { statement_2; }] ... [else if (condition_n_1) { statement_n_1; }] [else { statement_n; }] 下面这些值将被计算出 false false undefined null 0 NaN 空字符串 ("") 例子 function checkData() { if (document.form1.threeChar.value.length == 3) { return true; } else { alert("Enter exactly three characters. " + document.form1.threeChar.value + " is not...

JavaScript的字面量和Unicode编码

字面值字面值是由语法表达式定义的常量、或是通过由一定字辞组成的语词表达式定义的常量 字面值是常量,其值是固定的,而且在程序脚本运行中不可更改 数组字面值(Array literals) 布尔字面值(Boolean literals) 浮点数字面值(Floating-point literals) 整数(Intergers) 对象字面值(Object literals) 正则表达式字面值(RegExp literals) 字符串字面值(String literals) 数组字面值数组字面值是一个封闭在方括号对([])中的包含有零个或多个表达式的列表,其中每个表达式代表数组的一个元素,数组字面值同时也是数组对象。 var coffees = ["French Roast", "Colombian", "Kona"]; var a=[3]; console.log(a.length); // 1 console.log(a[0]); // 3 多余逗号 var fish = ["Lion", , "Angel"]; 这个数组中,有两个已被赋值的元素,和一个空元素(fish[0]是"Lion",fish[1]是undefined,而fish[2]是"Angel";译注:此时数组的长度属性fish.length是3) 若你在元素列表的尾部添加了一个逗号,它会被忽略。 var myList = [ , 'home', , 'school']; 数组的长度是4,元素myList[0]和myList[2]缺失 var myList = ['home', , 'school', , ]; 数组的长度是4,元素myList[1]和myList[3]缺失 布尔字面值布尔类型有两种字面值:true和false 整数整数可以被表示成十进制(基数为10)、十六进制(基数为16)以及八进制(基数为8) 0, 117 and -345 (decimal, base 10) 015, 0001 and -077 (octal, base 8) 0x1123, 0x00111 and -0xF1A7 (hexadecimal, "hex" or base 16) 浮点数字面值语法 [(+|-)][digits][.digits][(E|e)[(+|-)]digits] 例子 3.14 -.2345789 // -0.23456789 -3.12e+12 // -3.12*10^12 .1e-23 // 0.1*10-23=10-24=1e-24 对象字面值对象字面值是封闭在花括号对 {} 中的一个对象的零个或多个"属性名-值"对的(元素)列表 var...

JavaScript的基本语法、变量声明和数据类型

注释// 单行注释 /* 这是一个多行注释 多行注释 */ /* ··· /* 嵌套注释 */ ··· */ 声明三种声明: var : 声明变量,可选择将其初始化为一个值。 let : 声明块范围局部变量(block scope local variable),可选择将其初始化为一个值。 const : 声明一个只读(read-only)命名常量。 变量在JavaScript语言中,一个标识符(identifier)必须以字母、下划线(_)或者美元($)符号开头;后续的字符可以包含数字(0-9)。因为JavaScript语言是区分大小写的,这里所指的字母可以是(大写的)“A”到字母“Z”和(小写的)“a”到“z”。 声明变量三种方式声明变量: var x = 42。这个语法可以同时用来声明局部和全局变量。 x = 42。这样就声明了一个全局变量并会导致JavaScript编译时产生一个严格警告。因而你应避免使用这种非常规格式。 let y = 42。这个语法可以用来声明语句块代码段的局部变量(block scope local variable)。 对变量求值试图访问一个未初始化的变量会导致一个 ReferenceError 异常被抛出 var a; console.log("The value of a is " + a); // logs "The value of a is undefined" console.log("The value of b is " + b); // throws ReferenceError exception 可以使用undefined来确定变量是否已赋值 var input; if(input === undefined){ doThis(); } else { doThat(); } undefined值在布尔类型环境中会被当作false var myArray = new Array(); if (!myArray[0]) myFunction(); 数值类型环境中undefined值会被转换为NaN(Not a Number) var...

JavaScript的函数

定义函数函数的定义(也称为函数的声明)由一系列的函数关键词组成 函数的名称 函数引数列表,包围在括号( )中并由逗号 , 区隔 函数功能,包围在花括号{ }中,用于定义函数功能的一些JavaScript语句 例如 function square(number) { return number * number;}函数square使用了一个参数,叫作number这个函数只有一个语句,它说明该函数会将函数的参数(即number)自乘后返回函数的return语句确定了函数的返回值 如果你传递一个对象(pass an object),作为参数,而函数改变了这个对象的属性,这样的改变对函数外部是可见的 function myFunc(theObject) { theObject.make = "Toyota";} var mycar = {make: "Honda", model: "Accord", year: 1998},var x, y; x = mycar.make; // x 获取的值为 "Honda" myFunc(mycar);y = mycar.make; // y 获取的值为 "Toyota" // (make属性的值在函数中被改变了) 函数表达式var square = function(number) { return number * number};var x = square(4); // x 得到的值为16函数表达式也可以提供函数名,用于在函数内部使用来代指其本身 var factorial = function fac(n) {return n