实战JavaScript+JQuery实现网页交互 2变量 2.1 ECMAScript语法特征 (1)区分大小写:变量、函数名、运算符等都是区分大小写的。比如:变量 test与变量 TEST 是不同的。 (2)弱类型:ECMAScript是一种弱类型语言,变量无特定的类型,用var定义变量时,直接初始化为任 意值。 var color=”red”; var mun=25; (3)分号:ECMAScript 则允许开发者自行决定是否以分号结束一行代码。好的代码编写习惯是使用分 号,因为没有分号,有些浏览器就不能正确运行。 (4)注释:单行注释以“//”开头;多行注释以“/*”开头,以“*/”结尾。 (5)花括号表示代码块:代码块表示一系列按顺序执行的语句,这些语句被封装在“{”和“}”之间。 例如: var flag=false; if(flag){ alert(1); //或者使用console.log(test1)写入浏览器控制台 } else{ alert(0); } 2.2 变量 变量是程序设计语言中,用来储存计算结果或表示某个值的抽象概念。程序中的 数据赋给一个简短、易于记忆的名字,称为变量。变量可以通过变量名访问。 2.2.1 变量的声明 (1)var声明变量 变量可以不声明,解释程序会为变量自动创建一个值,无需明确的类型声明。但是,建议程序员先声明后使用,用var来声明变量。例如: var test = ”hi”; var test1=”hi”, age=25; 变量可以存放不同类型的值,这是弱类型变量的优势。例如,可以把变量初始化为字符串类型的值,后续将它修改为数字值,如下所示: var test=”hello”; console.log(test); test=55; console.log(test); 代码将输出字符串值和数字值。但是,好的编码习惯是始终存放相同类型的值。另外,JS也允许变量先使用后声明。 2.2.1 变量的声明 (2)let声明变量 let是ES6中新增命令用于声明变量,let命令在代码块{}内有效,在{}之外不能访问。let和var的区别体现在作用域上。var的作用域被规定为一个函数作用域,而let则被规定为块作用域,块作用域比函数作用域小。但是,如果两者既没在函数中,也没在块作用域中定义,那么两者都属于全局作用域。 { let x = 2; //let声明变量 } // 离开了块作用域,就不能访问x变量了 var允许在同一作用域中声明同名的变量,而let不可以。ES6中还有一个声明变量的命令const,const和let都是在声明的块作用域中有效,但是let声明的变量可变,值和类型都可以改变,没有限制。const声明的变量不能改变,所以,const一旦声明一个变量,就必须马上初始化。 2.2.1 变量的声明 (3)变量名 变量名需要遵守的规则: 第一个字符必须是字母、下划线(_)或美元符号($); 变量名由下划线、美元符号、字母或数字字符组成; 变量名不能是保留字,且大小写敏感; 变量名第一个字符建议仅用字母开头。下面的变量都是合法的: var test; var $test1; var $123; var _test1$ 2.2.1 变量的声明 (4)命名规则 Camel标记法:首字母是小写的,接下来的字母都以大写字符开头。 var myTestValue=0; Pascal标记法:首字母是大写的,接下来的字母都以大写字符开头。 var MyTestValue=”hello”; 匈牙利标记法:在以 Pascal 标记法命名的变量前附加一个小写字母(或小写字母序列),说明该变量的类型。例如,i 表示整数,s 表示字符串。 var iMyTestValue=0, sMySecondValue=”hello”; 2.2.1 变量的声明 (5)关键字 ECMA-262 定义了ECMAScript 支持的一套关键字(keyword)。这些关键字标识了 ECMAScript 语句的开头或结尾。根据规定,关键字是保留的,不能用作变量名或函数名。 下面是 ECMAScript 的关键字: break,case,catch,continue,default,delete,do,else,finally,for,function, if,in,instanceof,new, return, switch, this, throw, try, typeof, var, void, while, with 2.2.1 变量的声明 (6)保留字 ECMA-262定义了ECMAScript 支持的一套保留字(reserved word)。保留字在某种意思上是为将来的关键字而保留的单词。因此保留字不能被用作变量名或函数名。 保留字的完整列表如下: abstract,boolean,byte,char,class,const,debugger,double,enum,export,extends,final,float,goto,implements,import,int,interface,long,native,package,private,protected,public,short,static,super,synchronized,throws,transient,volatile 10 2.2.1 变量的声明 (7)标识符 标识符是用户编程时使用的名字,用于给变量、常量、函数、语句块等命名,以建立起名称与使用之间的关系。标识符通常由字母和数字以及其它字符构成。 2.2.2 变量的数据类型 JS有5种原始类型,分别是数字number,字符串string,布尔boolean,空null、未定义undefined,两种引用数据类型(对象object,数组array)。ES6引入一种新的原始数据类型Symbol,表示独一无二的值。 11 2.2.2 变量的数据类型 (1)number类型(数值类型) 这是ECMA-262 中定义的最特殊的类型。这种类型既可以表示 32 位的整数,也可以表示 64 位的浮点数。直接输入的(而不是从另一个变量访问的)任何数字都是number类型的字面量。 下面的代码声明了存放整数值的变量iNum,值由字面量55定义: var iNum=55; 八进制字面量的首数字必须是0开头,其后的数字可以是八进制数字(0-7)。 var iNum=55; //等于十进制整数45 十六进制的字面量,必须是0x开头,其后是十六进制数字(0 到 9 和 A 到 F),字母可以是大小写。 var iNum=0x1f; //等于十进制整数31 var iNum=0xAB; //等于十进制整数171 定义浮点数,必须包括小数点和小数点后的一位数字(例如,用 1.0 而不是 1)。 var fNum=3.0; 浮点字面量在进行计算前,存储的是字符串。可以用科学计数法表示浮点数,可以把一个数表示为数字(包括十进制数字)加 e(或 E),后面加乘以10的倍数。 var fNum=3.14e7; //科学计数法转化成计算的值为:3.14 x 107 浮点数精度不能做精准运算,如0.1 + 0.2,返回0.300000000000004 12 2.2.2 变量的数据类型 (2)string类型(字符串类型) string 类型是唯一没有固定大小的原始类型。字符串中每个字符都有特定的位置,首字符从位置 0 开始,第二个字符在位置 1,依此类推。字符串中最后一个字符位置一定是字符串的长度减1。 字符串字面量是由双引号(")或单引号(')声明的。由于 ECMAScript 没有字符类型,所以可使用这两种表示法中的任何一种。 13 2.2.2 变量的数据类型 (3)boolean类型(布尔类型) boolean 类型是JS中最常用的类型之一。它有两个值 true 和 false (即两个 Boolean 字面量)。 var bFound = true, bLost=false; (4)undefined类型(未定义类型) undefined 类型只有一个值,即 undefined。当声明的变量未初始化时,该变量的默认值是 undefined。undefined 类型的字面量。下面代码段可以测试该变量的值。 var oTemo; alert(oTemp == undefined); 代码显示 "true",说明这两个值确实相等。可以用typeof运算符显示该变量类型。 14 2.2.2 变量的数据类型 (5)null类型(空引用类型) null类型只有一个值,即null,表示一个空对象引用。值undefined实际上是从值 null 派生来的,因此 ECMAScript 把它们定义为相等的,null 和 undefined 的值相等,但类型不等。 typeof undefined // undefined typeof null // object null === undefined // false null == undefined // true 尽管这两个值相等,但它们的含义不同。undefined 是声明了变量,但未对其初始化时赋予该变量的值,null 则用于表示尚未存在的对象。如果函数或方法要返回的是对象,当找不到该对象时,返回的通常是null。 15 2.2.2 变量的数据类型 (6)Symbol类型(符号类型) ES5的对象属性名都是字符串,这容易造成属性名的冲突。ES6引入了Symbol类型,保证每个属性的名字都是独一无二的,从根本上防止属性名的冲突。Symbol值,通过Symbol函数生成。对象的属性名可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。 let s1 = Symbol(); let s2 = Symbol(); console.log(typeof s1) //输出symbol,注意大小写 s1=s2 // fasle 2.2.2 变量的数据类型 (7)引用类型 引用类型包括了对象object和数组array。对象object也是变量,是一种特殊的数据。对象由大括号分隔。在括号内部,对象的属性以名称和值对的形式 (name : value) 来定义,对象可以包含多个属性/值对,用逗号分隔,名称和值由冒号分隔。 创建object类型数据三种方法 定义和创建单个对象,使用对象字面量方式,即使用花括号{名称:值对}。 var person = { name:"Bill", age:62, }; 定义对象构造器,然后创建构造类型的对象。 function person(name,age){ //本例使用函数构造器 this.name=name; this.age=age; } 定义和创建单个对象,通过关键词 new。 var person = new Object(); person.name = "Bill"; person.age = 50; 对象属性有两种寻址(访问)方式 name=person.name; name=person["name"]; 2.2.2.2 原始数据类型和引用数据类型 (1)原始数据类型 JS原始数据类型(primitive type):Undefined、Null、Boolean、Number、String。ES6新增了Symbol类型。使用运算符typeof,可以返回变量或值的原始类型。 例如:var sTemp = "test string"; var s1=Symbol(); console.log(typeof sTemp); //输出string console.log(typeof 86); //输出number console.log(typeof s1); //输出symbol 对变量或值调用 typeof 运算符将返回其类型,类型值可以是下列一: ● Undefined,如果变量是 Undefined类型 ● boolean,如果变量是 Boolean类型 ● number,如果变量是 Number类型 ● string,如果变量是 String类型 ● symbol,如果变量是 Symbol类型 ● object, 如果变量是引用类型Object或Null类型 2.2.2.2 原始数据类型和引用数据类型 (2)引用数据类型 引用数据类型:对象(Object)和数组(Array)。 JS是面向对象的语言,但JS不使用类。在ES6前,JS不会创建类,也不会像其它面向对象语言一样,通过类来创建对象。JS是基于prototype,而不是基于类的。所有JS对象都会从一个prototype(原型对象)中继承属性和方法。 Boolean 对象 Boolean对象是boolean原始类型的引用类型。创建Boolean对象只需要传递布尔值作为参数。Boolean对象将覆盖 Object 对象的 ValueOf() 方法,返回原始值,即 true 和 false。ToString()方法也会被覆盖,返回字符串 "true" 或 "false"。 var oBooleanObject = new Boolean(true); 注意:在JS中很少使用 Boolean 对象,一般直接使用Boolean原始值true和false。 19 2.2.2.2 原始数据类型和引用数据类型 Number 对象 Number 对象是 number 原始类型的引用类型。要创建 Number 对象,采用下列代码创建值为26的对象: var oNumberObject = new Number(26); Number 对象,要得到数字对象的原始值,只需要使用 valueOf() 方法。 Number 对象处理数值的专用方法 l toFixed()方法,返回数字的字符串表示,并指定小数位数。 l var oNumberObject = new Number(68); l alert(oNumberObject.toFixed(2)); //输出 "68.00" l toExponential(),返回科学计数法表示的数字的字符串形式。 l var oNumberObject = new Number(68); l alert(oNumberObject.toExponential(1)); //输出 "6.8e+1“ l toPrecision()方法,不使用参数,返回数字的预定形式或指数形式。若使用参数,用来指定位数个数。 l var oNumberObject = new Number(68); l alert(oNumberObject.toPrecision(1)); //输出 "7e+1" l alert(oNumberObject.toPrecision(2)); //输出 "68" l alert(oNumberObject.toPrecision(3)); //输出 "68.0" 20 2.2.2.2 原始数据类型和引用数据类型 String 对象 String对象是string原始类型的对象表示,String的valueOf()方法和 toString()方法都会返回String类型的原始值。 var oStringObject = new String("hello world"); alert(oStringObject.valueOf() == oStringObject.toString()); //输出 "true" String 对象具有属性length,它是字符串中的字符个数: var oStringObject = new String("hello world"); alert(oStringObject.length); //输出 "11" 21 2.2.2.2 原始数据类型和引用数据类型 l 方法 charAt() 和 charCodeAt() 访问的是字符串中的单个字符。这两个方法都有一个参数,即要操作的字符的位置。charAt() 方法返回的是包含指定位置处的字符的字符串,charCodeAt()方法返回字符代码。 l 方法concat(),用于把一个或多个字符串连接到 String 对象的原始值上。 l indexOf() 和 lastIndexOf() 方法返回的都是指定的子串在另一个字符串中的位置,找不到子串,返回 -1。indexOf() 是从字符串的开头(位置 0)开始检索字符串, lastIndexOf()是从字符串的结尾开始检索子串。 l localeCompare()方法对字符串进行排序比较。有一个参数,指定要进行比较的字符串。返回的是下列三个值之一: 如果 String 对象按照字母顺序排在参数中的字符串之前,返回负数;如果String对象等于参数中的字符串,返回 0; 如果 String 对象按照字母顺序排在参数中的字符串之后,返回正数。 l 有4种方法执行大小写转换:toLowerCase();toLocaleLowerCase();toUpperCase();toLocaleUpperCase(), 前两种方法用于把字符串转换成全小写,后两种方法用于把字符串转换成全大写。toLocaleLowerCase() 和 toLocaleUpperCase() 方法是基于特定的区域实现。 22 2.2.2.2 原始数据类型和引用数据类型 (3)7种数据类型的区别 ECMAScript包括基本数据类型和引用数据类型。基本数据类型指的是简单的数据段,引用数据类型指的是有多个值构成的对象。当变量赋值给一个变量时,解析器首先要确认这个值是基本类型值还是引用类型值。 原始数据类型包括:Undefined,Null,Boolean,Number,String,Symbol,引用数据类型包括对象、数组、函数等。 原始数据类型值,存储在栈(stack)中的数据段,它们的值直接存储在变量访问的位置。因为这些原始类型占据的空间是固定的,所以存储在较小的内存区域栈中,便于迅速查找变量的值。 引用数据值,存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。 23 2.2.2.2 原始数据类型和引用数据类型 存储位置不同 原始数据类型直接存储在栈(stack)中简单数据段,占据空间小,大小固定,属于被频繁使用的数据,所以存储在栈中; 引用数据类型直接存储在堆中,占据空间大,大小不固定,引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后,从堆中获得对象实体。 传值方式不同 按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本。这意味着函数形参的值如果被修改,实参也会被修改。同时两者指向相同的值。 24 2.2.2.2 原始数据类型和引用数据类型 基本数据类型按值传递,基本类型是不可变的,只有对象是可变的(mutable)。在JS中,任何看似对string值的”修改”操作,实际都是创建新的string值。 var person,name; person = 'kn'; name=person; person='黑白'; console.log(person, name, typeof person); //黑白 kn string person的改变没有改变name,说明 string 是按值传递的。赋值时创建一块新的内存空间。 引用类型按引用传递,引用类型的值是可变的,引用类型的值是保存在栈内存中,指向堆内存中的对象,JS和其他语言不同,不允许直接访问堆内存中的位置和操作堆内存空间,只能操作对象在栈内存中的引用地址。 var obj = {x : 0}; obj.x = 100; var o = obj; o.x = 1; console.log(obj.x)// 1, 被修改 o = {x:100}; //等同于重新赋值,重新开辟内存,不是修改 console.log(JSON.stringify(obj),JSON.stringify(o))//{"x":1} {"x":100} obj.x; // 1, 不会因o = {"x":100}改变 25 2.2.2.2 原始数据类型和引用数据类型 参数传递的不同 即实参复制给形参的过程不同,JS中所有函数的参数都是按值来传递的,主要原因是内存分配时的差别。 原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。 引用值:对象变量的值是这个对象在堆内存中的内存地址(必须铭记),因此它传递的值也就是这个内存地址,这就是为什么函数内部对这个参数的修改会体现在外部的原因,因为它们指向同一个对象。 总结 原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,它们只是拥有相同的值而已。 引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,它们中任何一个作出的改变都会反映在另一个身上。 复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个指针变量,指向这个对象。 26 2.2.2.3 typeof运算符 可以使用 typeof 操作符来检测变量的数据类型。 typeof "john" // 返回 string typeof 3.14 // 返回 number typeof false // 返回 Boolean typeof [1,2,3,4] //返回object,在JS中,数组是一种特殊对象类型。 typeof {name:'John', age:34} // 返回 object 使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "object"。JS引入了另一个Java运算符 instanceof 来解决这个问题。 instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。 var oStringObject = new String("hello world");//创建字符串对象 console.log(oStringObject instanceof String);//输出"true" 27 2.2.3 变量的值 在ECMAScript 中,变量可以存在两种类型的值,即原始值和引用值。原始值存储在栈(stack)中,即存储在变量访问的位置。引用值是存储在堆(heap)中的对象,存储在变量处的值是一个指针(point),指向存储对象的内存地址。 原始类型占据的空间是固定的,存储在较小的内存区域栈中。这样存储便于迅速查寻变量的值。如果一个值是引用类型的,那么它的存储空间将从堆中分配。由于引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。存贮在栈空间中的值是该对象存储在堆中的地址,因为地址的大小是固定的。 28 2.2.3 变量的值 变量的值 基本类型赋值使用直接量,引用类型值的添加属性或方法如下: var test = "hi", age = 25; //定义变量test和age var person = "xiaoming"; //定义一个基本类型值的变量 var person = new Object(); //定义一个引用类型值的变量 person.name = "xiaohong"; console.log(person.name); //输出"xiaoming" (1)变量先声明后使用 (2)变量的数据类型 变量的数据类型由值决定,如果变量值改变,变量的数据类型 也将改变。 (3)未初始化变量的值 当声明的变量未初始化时,该变量的默认值是undefined。 var num = 13; //声明变量num,赋给初始值13 num = 15; //15赋给变量num,变量的值改变了,切记 alert(num+15); //弹出25,此时num值为15 num = “xiaoming”; //num表示字符串了 const totle = 30; //声明了一个常量,赋值为30 totle=50; //类型错误:不能给一个常量重新赋值 (4)变量赋值演示案例 29 2.2.4 变量的作用域 l 变量有三种声明方式: l var声明一个变量,可赋一个初值; l let声明一个块作用域的局部变量,可赋一个初值; l const声明一个块作用域的只读常量,初始化后不能修改。 l 在JS中,变量分为全局变量和局部变量。全局变量在JS的任何地方都可以定义。局部变量只在函数体内定义。在声明局部变量时,必须要使用var关键字。 l var a = 1; //局部变量 l b = 1; //全局变量 l 作用域是可访问变量、对象、函数的集合。在 JS中, 对象和函数也是变量。JS变量生命周期在它声明时开始,局部变量在函数执行完毕后销毁,全局变量在页面关闭后销毁。看以下代码: l function test() { l var message=”hello”; l console.log(message); l } l test(); //输出hello l console.log(message); //输出message没有定义 30 2.2.4.1 三种作用域 全局变量拥有全局作用域,在 JS代码中的任何地方都可以定义、访问。在函数体内声明的变量只在函数体内有定义,是局部变量。函数参数是局部变量,只在函数体内有定义。在函数体内,局部变量的优先级高于同名的全局变量。 var scope = 'global' //声明全局变量,并赋初始值 function f () { var scope // 声明局部变量,覆盖同名的全局变量 console.log(scope) // 此时 scope 已声明但还未赋值,输出 undefined scope = 'local' // 赋值 console.log(scope) // 输出 local } 31 2.2.4.1 三种作用域 (1)全局作用域 var i = 1; //声明全局变量,这里var关键写也可以不写 function f(){ var i = 2; //声明局部变量,这里var关键字必须写 return i; //返回局部变量的值 } console.log(f()); //显示函数返回值的局部变量值2 console.log(i); //显示的全局变量的值1 变量在函数外定义,即为全局变量。全局变量的使用范围称为全局作用域: 网页中所有脚本和函数均可使用。 变量在函数内没有声明(没有使用var关键字),该变量也是全局变量。实例中 carName 虽然在函数内,却是一个全局变量。 function myFunction() { carName = "Volvo"; // 函数内没有申明,变量carName是全局变量 } 32 2.2.4.1 三种作用域 (2)函数级作用域 function functionName(parameters) { //执行的代码 } JS使用关键字function定义函数。函数可以通过声明定义,也可以是一个表达式。 变量在函数内声明(使用var关键字),即为局部变量,局部变量的使用范围称为局部作用域或函数级作用域。局部变量只能在函数内部访问。因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。 function doSomething() { var thing = "吃早餐"; //定义了局部变量 } console.log(thing); // uncaught ReferenceError: not defined 33 2.2.4.1 三种作用域 (2)函数级作用域 利用嵌套函数作用域可以访问,在外层函数中,嵌套一个内层函数,那么这个内层函数可以向上访问到外部作用域的变量。 内层函数可以访问到外层函数的变量,也可以通过返回内层函数来实现。 function outter() { var thing = "吃早餐"; function inner() { console.log(thing); } inner(); } outter(); // 吃早餐 function outter() { var thing = "吃晚餐"; function inner() { console.log(thing); } return inner; } var foo = outter(); foo(); // 吃晚餐 34 2.2.4.1 三种作用域 (3)块级作用域 使用 var 关键字声明的变量不具备块级作用域的特性,它在 {} 外依然能被访问到。 { var x = 2; } // 这里可以访问x变量 ES2015(ES6)新增加了let和const两个重要的关键字。let声明的变量只在let命令所在的代码块内有效。const声明一个只读的常量,声明后常量的值就不能改变。 在ES6之前,JS只有全局作用域与函数级作用域两种。ES6 新增了块级作用域。使用 let 或者 const 命令声明的变量只在其块级作用域内有定义。块级作用域的出现使一些场景变得更加合理。比如:用 var 命令声明的函数作用域内的局部变量会覆盖同名的全局变量,而用 let 命令声明则不会。 使用 let 关键字来实现块级作用域。let 声明的变量只在 let 命令所在的代码块{}内有效,在{}之外不能访问。 { let x = 2; } // 这里不能访问x变量 35 2.2.4.1 三种作用域 (3)块级作用域 for (var i = 0; i < 5; i++) { // } console.log(i) // 输出5 for (let i = 0; i < 5; i++) { // } console.log(i) // 输出Reference Error for (let i = 0; i < 5; i++) { let i = 'hello world' console.log(i) // 输出 hello world } 用var命令声明循环变量会泄露为全局变量,而用 let 命令声明则不会。另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。 36 2.2.4.1 三种作用域 (4)let、const和var作用域的区别 var i = 0 //声明全局变量i,也可以不用var var a = [1,2,3,4,5] for(var i = 0; i < 4;i++){ // console.log(a); } //循环执行结束,全局变量i值修改为4 console.log(a);//5 var i = 0 var a = [1,2,3,4,5] for(let i = 0; i<4;i++){ //console.log(a); } //循环执行结束,全局变量i值为0,没有修改 console.log(a);//1 使用var关键字声明的变量在任何地方都可以修改。在相同的作用域或块级作用域中,不能使用let关键字来重置var关键字声明的变量。const 用于声明一个或多个常量,声明时必须进行初始化,且初始化后值不可再修改,不存在变量提升机制。 let关键字定义的变量需要先声明再使用,不存在变量提升机制。let和const都是声明一个块级作用域的变量及常量,都不能和它所在作用域内的其他变量或函数拥有相同的名称,即不能重复申明,不易发生变量命名污染的问题,能规避冲突,使得代码简洁优雅。 37 2.2.4.1 三种作用域 (4)let、const和var作用域的区别 var x = 10; // 这里输出 x 为 10 { const x = 2; // 这里输出 x 为 2 } // 这里输出 x 为 10 let和const两者还有以下区别:const声明的常量必须初始化,而let声明的变量不用。const 定义常量的值不能通过再赋值修改,也不能再次声明。而 let 定义的变量值可以修改。 38 2.2.4.1 三种作用域 (4)let、const和var作用域的区别 var a = []; for (var i = 0; i < 10; i++) { a = function () { console.log(i); }; } a[5](); // 结果为10; var a = []; for (let i = 0; i < 10; i++) { a = function () { console.log(i); }; } a[5](); //结果为 5; 由于没有块级作用域,i 变量全局只有一个,当 for 循坏结束,变量 i 的值等于10。a[5]()对应的函数是输出i的值,而此时变量 i 的值已经是10,因此输出结果是10。同样a[0]()、a[1]()等也是一样结果。 数组内的索引为5,函数内的变量值为5,每次循环,会创建新的块级作用域,然后重新声明一个新的变量 i;JS 的解释引擎会记住上次循环的变量值,所以能够返回正确的结果,输出5。 39 2.2.4.1 三种作用域 (4)let、const和var作用域的区别 function test() { message='hi'; console.log(message); } console.log(message); //程序出错 function test() { message=”hello”; console.log(message); } test(); console.log(message); 未使用var操作符声明的变量message为全局变量,未调用test()方法,message就属于未定义状态。错误信息提示message没有定义。 未使用var操作符声明的变量message为全局变量,必须调用test()方法, message才会有效。程序输出2行”hello”。 40 2.2.4.2 变量提升(Hoisting机制) 变量提升,似乎意味着变量和函数的声明会移动到代码的最前面。但是,实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段放入内存时,提升到代码前面。 num = 6; num + 7; var num; var x=1; console.log(x + ” ” + y); //输出1 undefined var y=2; 由于变量提升,不会出错。 JS仅提升声明,不提升初始化值。如果你先使用的变量,再声明并初始化它,变量值是undefined。 2.2.4.2 变量提升(Hoisting机制) (1)提升优点 JS在执行任何代码段之前,将函数声明放入内存中进行提升,这就允许在声明该函数之前使用该函数。变量可以在声明之前进行初始化和使用。但是如果没有初始化,就不能使用它们。函数和变量相比,会被优先提升,即函数会被提升到更靠前的位置。 /*正确的方式:先声明函数,再调用*/ function aName(name){ console.log("这是 " + name); } aName("Tigger"); //输出"这是 Tigger" /*不推荐的方式:先调用函数,再声明*/ aName("Horse"); function aName(name){ console.log("这是 " + name); //输出"这是 Horse" } 在定义函数之前调用函数,仍然可以工作。 正常处理方式(先声明,后调用) 42 2.2.4.2 变量提升(Hoisting机制) (2)提升规则 var 声明的变量,提升时只声明,不赋值,默认为undefined(demo1中的变量a);不用关键字var,直接赋值的变量不能提升(demo1中的变量b); 函数提升会连带函数体一起提升,而函数不会被执行(deom2); /**demo1**/ console.log('a=',a); //a=undefined console.log('b=',b); // Uncaught ReferenceError: b is not defined var a=1; b=6; /**deom2**/ console.log('a=',a) // a=function a() {console.log("func a()")} function a() { console.log("func a()") } 43 2.2.4.2 变量提升(Hoisting机制) (2)提升规则 函数的优先级高于变量,函数声明提前到当前作用域最顶端(deom3); 预解析的顺序是从上到下执行,若变量或函数重名,提升时后面的定义会覆盖前面的定义(demo4); /**deom3**/ console.log('a=',a) // a=function a() {console.log("fun a")} var a=4 function a(){ console.log("fun a") } var a=5 console.log("a=",a) // a=5 /**deom4**/ console.log('a=',a) // a=undefined var a =2 console.log('a=',a) //a=2 var a =4 //修改了a console.log('a=',a) // a=4 44 2.2.4.2 变量提升(Hoisting机制) (2)提升规则 函数执行时,函数内部的变量声明和函数声明也按照以上规则进行提升;(deom6) 用函数表达式声明函数,按照声明变量规则进行提升(先后顺序);(deom5) /**deom6**/ console.log('b=',b) //输出function b(i)定义 var a=3 function b(i){ console.log('a=',a) var a=4 function a(){ console.log('fun a') } console.log('a=',a) } b() //调用函数,先输出function a()定义,然后输出4 /**deom5**/ console.log('a=',a) // a=undefined var a=function(){console.log('a1')} var a=5 console.log(a) var a=function(){console.log('a2')} console.log('a=',a) // a= function(){console.log('a2')} 2.3 const常量 常量表示一些固定不变的数据,使用const关键字创建常量,声明的同时必须赋值。为了区分变量与常量,一般变量名采用小写或驼峰标识,常量采用全大写的形式。 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。 const foo = {}; foo.prop = 123; // 为 foo 添加一个属性 // 将 foo 指向另一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only const a = []; a.push('Hello'); a.length = 0; a = ['Dave']; // 报错,常量a是一个数组,就不可以将另一个数组赋值给a 对象常量只是不允许修改引用地址,但是属性还是可以被修改、扩展和删除。 常量foo储存的是一个地址,指向一个对象。不可变的只是这个地址,但对象本身是可变的,所以依然可以为其添加新属性。 l 一个真正的对象常量,必须满足以下三点: l 对象的属性不得被扩展; l 对象的属性不得被删除; l 对象的属性不得被修改; var Obj = { name: 'jack' } Object.seal(Obj) Obj.age = 23 // 扩展属性 console.log(Obj.age) // undefined(说明扩展失败了) delete Obj.name // 删除属性 console.log(Obj.name) // 'jack'(说明删除失败了) var Obj = { name: 'jack' } Object.preventExtensions(Obj) Obj.age = 23 // 扩展属性 console.log(Obj.age) // undefined(说明扩展失败了) 防止对象属性被删除,Object.seal方法不仅可以保证对象的属性不被扩展,还能防止属性被删除。 防止对象属性被修改,Object.freeze,它可以实现对象既不可被扩展和删除,而且还不被修改 var Obj = { name: 'jack', extraInfo: { age: 23 } } Object.freeze(Obj) Obj.extraInfo.age = 80 console.log(Obj.extraInfo.age) // 80,还是修改了 var Obj = { name: 'jack' } Object.freeze(Obj) Obj.age = 23 // 扩展属性 console.log(Obj.age) // undefined(说明扩展失败了) delete Obj.name // 删除属性 console.log(Obj.name) // 'jack'(说明删除失败了) Obj.name = 'lucy' // 修改属性 console.log(Obj.name) // 'jack'(说明修改失败) Object.freeze虽然实现了真正的对象常量,但是它的一切操作只在顶级对象属性上生效。 防止对象属性被修改,Object.freeze,它可以实现对象既不可被扩展和删除,而且还不被修改。
|