一,JavaScript的书写格式
1.行内脚本:写在HTML标签内部,通过一个属性节点来添加,不推荐使用,例如“<div class=“one”onclick=“let a = 5; alert(a); console.log(a);”>hello world</div>”
2.内嵌(内联)脚本:写在一对<head>内/<body>的结束标签之前的一对<script>中,此种格式不能同时通过此<script>的src属性再引入其它的.js文件,否则所书写的JS代码就失效了,由于默认情况下web浏览器会从上至下的解析html网页,所以在通过所书写的JS代码操作界面上的标签时,当此<script>在一对<head>内时,JS代码必须写在入口函数“window.onload = function (){操作界面上标签的JS代码;};(//window.onload的含义为等到界面上所有的内容都加载完毕再执行{}中的JS代码块)”的函数作用域中,当此<script>在<body>的结束标签之前时,则无需入口函数,给<script>添加type=“text/html”可以将此<script>注释掉
3.外链(引入)脚本:写在一个单独的.js文件中,再引入进来,例如“<script src=“01-javascript书写格式.js”></script>”,同理,当将它添加到一对<head>之内时,在此.js文件中所书写的JS代码都必须放到入口函数中,当将它添加到<body>的结束标签之前时,则无需入口函数
二、JavaScript常见输出方式
1.通过浏览器弹窗来输出:格式为“alert(常/变量);(//只能传入一个实参,此弹窗只有确定,没有取消)”,“confirm(常/变量);(//只能传入一个实参,此弹窗既有确定,也有取消)”,“prompt(常/变量);(//此弹窗既有确定和取消,又有一个输入框,prompt方法(函数)的返回值就是在输入框中所输入的内容,默认为字符串)”,当弹出这三种弹窗时,都必须点击一下确定/取消让弹窗消失,才能继续执行之后的代码,由于在JS中是严格区分大小写的,所以“alert();”和“ALERT();”不是一回事,每一句JS代码的后面都需要添加一个英文的分号,不添加的话web浏览器也会自动帮我们添加,但是会消耗web浏览器一定的性能,并且有可能会出现错误
2.通过网页内容区域来输出:格式为“document.write(常/变量);”,当想要传入多个实参时,可以用逗号隔开
3.通过开发者工具控制台来输出:格式为“console.log(常/变量);(//普通输出)”,“console.warn(常/变量);(//警告输出)”,“console.error(常/变量);(//错误输出)”,“console.dir (常/变量);(//可以在控制台以目录结构的方式显示一个对象所有的属性和方法)”,当想要传入多个实参时,可以用逗号隔开
三、JavaScript常量
使用字面量所创建的基本数据/使用const限定符所定义的固定不变的数据就叫做常量,现实生活中人的性别其实就可以看做是常量,生下来是男孩一辈子都是男孩,生下来是女孩一辈子都是女孩,常量分为整型常量、实型常量、字符串常量、布尔常量、和自定义常量,整型常量其实就是整数,在JS中随便写一个整数都是整型常量,比如1/666/99,实型常量其实就是小数,在JS中随便写一个小数都是实型常量,比如3.14/6.66,字符串常量其实就是用单/双引号包裹起来的内容,无论包裹起来了多少个字符,在JS中都是字符串常量,比如‘a’/‘abc’/“1”/“知播渔教育”,字符串常量输出的时候会自动去掉引号,布尔常量其实就是真/假,在JS中布尔常量只有两个取值,true/false,自定义常量是在ES6中新增的,格式为“const 常量名称 = 常量取值;”,const属于限定符,只能定义常量,定义的时候必须初始化,在相同作用域内不可以重复定义同名常量,也不可以先使用后定义常量
四、JavaScript变量
可以被修改的数据就叫做变量,在现实生活中超市的储物格就是变量,在不同的时间段里面,储物格中存储的数据也不一样,在JS中可以通过定义变量的方式来创建储物格,也就是告诉web浏览器,我们需要一块内存空间,定义变量的格式为“var变量名称;”,使用变量就是向被分配的那块内存空间中存储数据和读取所存储的数据,存储数据的格式为“变量名称 = 需要被存储的数据;(//将等号右边需要被存储的数据放到等号左边变量被分配的那块存储空间中)”,读取存储在变量中的数据的格式为“var num;(//先定义一个变量) num = 123;(//向变量中存储数据) console.log(num);(//从变量中读取所存储的数据)”,“console.log();”的意思是console对象调用它的log方法,在JS中第一次给变量赋值就叫做变量的初始化,当想要修改变量中存储的数据时,只需要再次给变量赋值即可,当一个被定义的变量没有进行初始化时,此变量中存储的是undefined,也可以在定义变量的同时对变量进行初始化,例如“var value = 666;”,也可同时定义多个变量,格式为“var 变量名称1, 变量名称2, ...;”,当多个变量初始化的值都相同时,可以连写为“变量名称1 = 变量名称2 = 变量名称... = 初始化值;”,当多个变量初始化的值都不同时,可以连写为“var 变量名称1 = 取值1, 变量名称2 = 取值2, ...;”,在JS中变量之间是可以相互赋值的,例如“value = num;”,意思是将变量num中的值复制一份给变量value,使用var声明符在相同作用域内可以重复定义同名变量,后定义的会覆盖先定义的,并不会报错,例如“var num = 666; var num = 888;(//“var num = 666;”被覆盖掉)”,使用var声明符也可以对变量进行先访问后定义,并不会报错,但会返回undefined,是因为JS是一门解释型的语言,会边解析边执行,但web浏览器在解析JS代码之前还会进行一个预解析(预处理)的操作,也就是将当前作用域所有JS代码中变量和函数的定义放到当前作用域所有JS代码的最前面(即在当前作用域置顶,先预解析的在前,后预解析的在后),例如“console.log (num); var num = 123;”,预处理之后的代码为“var num; console.log (num);(//变量num的值是undefined) num = 123;”,ES6新增了let声明符,使用let声明符在相同作用域内不可以重复定义同名变量(只要出现了let就不行),但在不同作用域内可以,当由于在相同作用域内重复定义同名变量而报错时,即使之前正确的代码也不会执行,使用let声明符也不可以对变量进行先访问后定义,因为不会预解析,而且使用ES6中的let和class声明符在全局作用域中所定义的变量/函数,也不再是全局对象的属性/方法,当利用全局对象(例如window对象)访问/调用时,返回值是undefined
五、JavaScript关键字、保留字、和标识符
关键字就是被JS语言赋予了特殊含义的单词,关键字在开发工具(编辑器)中会显示特殊颜色,不能用作变量/函数名等,并且严格区分大小写,例如var是关键字而Var不是,在JS中所有的关键字都是小写的,关键字有哪些详见江哥的简书,保留字就是JS语言中预留的关键字,它们虽然现在没有作为关键字,但在以后的版本升级中有可能作为关键字,所以也不能用作变量/函数名等,标识符是指程序员在编写程序中自己起的名称(比如变量/函数名称等),只能由26个英文字母的大小写、10个阿拉伯数字0~9、下划线_、美元符号$组成,不能以数字开头,严格区分大小写,比如test和Test是两个不同的标识符,不可以使用关键字和保留字作为标识符,JS底层保存标识符时实际上是采用的Unicode编码,所以理论上讲,所有的utf-8中含有的内容都可以作为标识符(虽然中文也是合法的,但是一定不要用中文),标识符的命名要见名知意,也就是变量的名称要有意义,也可以使用驼峰命名法,即首字母小写,第二个单词的首字母大写,有利于提高阅读性,另外,在JS中,多行注释不可以嵌套多行注释
六、JavaScript数据类型
JS的数据类型包括基本和引用数据类型,基本数据类型中包括数值类型(number,包括整数、小数、和NaN)、字符串类型(string,即使用单/双引号包裹起来的内容)、布尔类型(boolean,即true和false)、symbol类型(详见jQuery.docx)、未定义类型(只有undefined这一个数据,“typeof undefined;”的返回值是“undefined”)、和空类型(只有null这一个数据,“typeof null;”的返回值是“object”),引用数据类型(靠空间地址来引用)中只有对象类型(包括对象、数组、和函数,在使用typeof操作符时,当所作用的是对象/数组时,返回值是“object”,当所作用的是函数时,返回值是“function”),使用typeof操作符可以返回所作用数据的数据类型字符串/创建所作用数据的类名字符串,格式为“typeof 常量/变量;”,“typeof undefined;(//返回值是“undefined”,当其所作用的变量没有被声明时,返回值也是“undefined”)”,“typeof [1, 2, 3];(//返回值是“object”)”,“typeof fn;(//返回值是“function”)”,“typeof null;(//返回值是“object”,特殊记一下,这个bug是第一版JS留下来的,原理是这样的,不同的对象在底层都表示为二进制,在JS中,当二进制的前三位都为0时,就会被判断为object类型,由于null的二进制表示是全0,所以,自然前三位也是0,这样一来,执行typeof时就会返回“object”)”,数据对象(当调用者是基本数据时,只能使用变量)调用它的toString方法可以返回此数据被转成的字符串,格式为“数据变量.toString ();(//注意,当调用者是基本数据时,只能使用变量来调用,undefined和null无法调用此方法,否则会报错)”,全局对象调用它的String方法可以返回传入数据被转成的字符串,格式为“String (常量/变量);”,任何数据与一个空字符串相加都可以将此数据转成字符串,格式为“常量/变量 + “”;”,其底层的本质就是利用全局对象调用它的String方法,将纯数字转成字符串之后是纯数字的字符串,将true/false/undefined/null/NaN转成字符串之后是其本身的字符串,将真数组转成字符串之后是其去掉中括号的字符串,例如“[1, 3, 5] + “”;(//返回值是“1, 3, 5”)”,将系统自带的伪数组转成字符串之后是“[object NodeList]”/“[object TouchList]”/...,将自定义的伪数组对象/普通对象转成字符串之后是“[object Object]”(日期对象除外),特殊记一下“({}).toString.apply (真数组对象);”的返回值是“[object Array]”,使用加/减运算符或利用全局对象调用它的Number方法都可以返回传入数据被转成的数值,格式为“Number (常量/变量);”,“+/- 常量/变量;(//减号会改变数值的正负性)”,加/减运算符其底层的本质就是利用全局对象调用它的Number方法,将纯数字的字符串转成数值之后是纯数字,将true转成数值之后是1,将false/null/空字符串/只有空格的字符串转成数值之后是0,将undefined/非纯数字的字符串/对象转成数值之后是NaN,全局对象调用它的parseInt/parseFloat方法可以在传入数据被转成字符串后,再从左至右提取数值,一旦遇到非数值就会立即停止,返回值是提取到的数值,当停止时没有提取到数值时,就返回NaN,但是parseInt方法只能提取整数,parseFloat方法则可以把小数部分也提取出来,格式为“parseInt/parseFloat (常量/变量);”,全局对象调用它的Boolean方法可以返回传入数据被转成的布尔(true/false),格式为“Boolean (常量/变量);”,将0/NaN/undefined/null/纯空字符串(引号中什么都没有,连空格都没有)转成布尔之后是false,其它的转成布尔都是true(包括引号中只有空格的字符串),全局对象调用它的encodeURIComponent/encodeURI方法可以返回传入字符串被转成的十六进制转义序列,传入字符串中的数字和ASCII字母不会发生任何变化,encodeURIComponent方法也不会对这些符号“-_.!~*‘()”进行编码,encodeURI方法也不会对这些符号“-_.!~*‘()”和这些符号“;/?:@&=+$,#”进行编码,例如“encodeURIComponent (“http://w3cschool.cc/my test.php?name=lnj&age=34”);(//返回值是“http%3A%2F%2Fw3cschool.cc%2Fmy%20test.php%3Fname%3Dlnj%26age%3D34”)”
七、JavaScript运算符
算术运算符分为“+”、“-”、“*”、“/”、和“%”(取余),并且都是左结合性(从左至右计算),“*”、“/”、和“%”的优先级要高于“+”和“-”,在进行加法运算时,当参与运算的数据有字符串时,都会默认转成字符串再相加,字符串相加的本质就是字符串的拼接,当参与运算的数据没有字符串时,都会默认先转成数值再相加,在进行减/乘/除运算时,都会默认先转成数值再运算,当任何数据与NaN进行加/减/乘/除运算时,返回值都是NaN,取余(%)运算的格式为“余数 = m % n;”,当m>n时,就正常取余,当m<n时,返回值是m,当n是0时,返回值是NaN,赋值运算符的作用就是将等号右边的值存储到等号左边的变量中,简单类型的赋值运算符为“=”,复杂类型的赋值运算符为“+=”、“-=”、“*=”、“/=”、和“%=”,赋值运算符是右结合性,即从右至左的计算,左边只能放变量,不能放常量,并且优先级低于算术运算符,例如“ let res = 1 + 1;(//由于赋值运算符的优先级低于算术运算符,所以会先计算1+1,然后再赋值给res)”,“let num1, num2; num1 = num2 = 3;(//由于赋值运算符的结合性是右结合性,所以会先将3赋值给num2,然后再将num2赋值给num1)”,“let res = 5; res += 5;(//等价于res = res + 5;) res -= 5;(//等价于res = res - 5;)”,自增运算符为“++”,可以快速的对一个变量中保存的数值进行+1操作,自减运算符为“--”,可以快速的对一个变量中保存的数值进行-1操作,当++/--运算符写在变量的前面时,表示变量先++/--,然后再参与其它的运算,当++/--运算符写在变量的后面时,表示变量先参与其它的运算,然后再++/--,例如“let num1 = 1; let num2 = ++num1 + 1;(//等价于“num1++; let num2 = num1 + 1;”) let num3 = num1++ + 1;(//等价于“let num3 = num1 + 1; num1++;”)”,++/--只能出现在变量的前/后面,不能出现在常量/整个表达式的前/后面,在JS中,用运算符连接在一起的有意义、有结果、有返回值的语句就叫做JS表达式,而只是单纯的阐述一种行为,并没有返回值的语句,就叫做JS语句(比如“let a = 5;”就是一个JS语句),例如“(1 + 1)++”和“++(a * 5)”就为错误的写法,++/--最好单独出现,不要出现在表达式之中,因为阅读性不好,例如“let res = a++ + b;(//最好改写成“let res = a + b; a++;”)”,当想要交换两个变量中保存的数据时,既可以使用算术运算符的方式,例如“let a = 10, b=5; a = a + b;(//变量a的值变为15;) b = a - b;(//变量b的值变为10;) a = a - b;(//变量a的值变为5;) console.log (a, b);”,也可以使用定义临时变量的方式(推荐,阅读性更好),例如“let a = 10, b=5; let temp;(//定义一个临时变量) temp = a;(//将变量a赋值给变量temp) a = b;(//将变量b赋值给变量a) b = temp;(//将变量temp赋值给变量b) console.log (a, b);”,关系运算符分为“>”、“<”、“>=”、“<=”、“==”(等等于)、“!=”(不等于)、“===”(全等于)、和“!==”(不全等于),当参与关系运算的数据都是字符串时,先将字符串中的每个字符都转成Unicode数值,再从左至右的依次进行关系运算,碰到相同的值就跳过,直到产生了结果就不再往下进行,返回值是true/false,当参与关系运算的数据不都是字符串时,先转成数值,再进行关系运算,返回值是true/false(当出现了NaN时,返回值是false),有几个特殊的比较结果,需要特别记一下,分别是“null == 0;(//false)”,“undefined == 0;(//false)”,“null == undefined;(//true,因为undefined是从null衍生出来的)”,“NaN == NaN;(//false)”,全局对象调用它的isNaN方法可以判断某一个数据是否是NaN,返回值是true/false,格式为“isNaN (常量/变量)”,“==”和“!=”只会判断取值,当被判断的两个值是基本数据类型时,“===”和“!==”会同时判断取值和数据类型,当被判断的两个值是引用数据类型时,“===”和“!==”会同时判断取值和空间地址(即它俩是否存储在同一块存储空间中),例如“let res = 123 === “123”;(//变量res的值是false)”,由于关系运算符都是左结合性,即从左至右的运算,所以不能利用关系运算符来判断区间,例如“let res = 10 > 5 > 3;(//等价于“let res = true > 3;”,最终等价于“let res = 1 > 3;”,所以变量res的值是false)”,“>”、“<”、“>=”、和“<=”的优先级高于“==”、“!=”、“===”、和“!==”,例如“let res = 10 == 10 > 0;(//等价于“let res = 10 == true;”,最终等价于“let res = 10 == 1;”,所以变量res的值是false)”,对于含有==/===的条件表达式,最好将常量写在前面,例如“let num = 10; if (num == 5) {console.log (“hello”);} else {console.log (“world”);}(//因为有时候我们容易将“==”误写成“=”而不自知,这样一来,这个条件表达式就变成true了,关键是还不会报错,所以我们就不知道错在哪了,为了防止这个问题,我们可以改写成“5 == num”,这样一来,一旦少写一个等号就会报错了)”,逻辑运算符分为逻辑与(&&)、逻辑或(||)、和逻辑非(!),结合性是左结合性(从左至右的运算),“&&”的优先级高于“||”,逻辑与(&&)的格式为“条件表达式A && 条件表达式B”,在整体充当条件表达式的“&&”中,全true则true,一false则false,在不是整体充当条件表达式的“&&”中,当条件表达式A是true时,返回值是表达式B所执行的结果,当条件表达式A是false时,返回值是表达式A所执行的结果,并且表达式B不会执行,例如“let num1 = 1; let num2 = undefined && (++num1 > 0);(//变量num2的值是undefined,当表达式B只是一个普通的JS语句时,必须用小括号包上,否则就会报错,例如“let c; 5 > 3 && (c = 105);”)”,逻辑或(||)的格式为“条件表达式A || 条件表达式B”,在整体充当条件表达式的“||”中,一true则true,全false则false,在不是整体充当条件表达式的“||”中,当条件表达式A是true时,返回值是表达式A所执行的结果,并且表达式B不会执行,当条件表达式A是false时,返回值是表达式B所执行的结果,逻辑非(!)的格式为“! 条件表达式”,当“!”用于条件判断时,true变false,false变true,逗号运算符一般用于简化代码,结合性是左结合性,即从左至右的运算,优先级是所有运算符中最低的,可以利用逗号运算符同时定义多个变量,例如“let a, b;”,也可以利用逗号运算符同时给多个变量赋值,例如“a = 10, b = 5;”,由于逗号运算符也是一个运算符,所以也是有运算结果的,运算结果就是最后一个表达式的结果,例如“let res = ((1 + 1), (2 + 2), (3 + 3) );(//变量res的值6)”,三目运算符又称之为条件运算符,格式为“条件表达式 ? 结果A : 结果B;”,当条件表达式是true时,返回值是A所执行的结果,当条件表达式是false时,返回值是B所执行的结果,“?”和“:”不能单独出现,要么一起出现,要么一起不出现,运算/操作符所作用于的实体就叫做操作数(它是表达式中的一个组成部分,它规定了指令中进行数字运算的量),所有运算/操作符的优先级从低到高的排序为“逗号运算符<赋值运算符<三目运算符<逻辑或<逻辑与<(等等于,不等于,全等于,不全等于)<(小于,小于等于,大于,大于等于,in,instanceof)<(加法,减法)<(乘法,除法,取模)<(前置自增,前置自减)<(后置自增,后置自减)<new (无参数列表)<(函数的调用,new (有参数列表),成员的访问“.”/“对象[key]”)<小括号”
八、JavaScript的流程控制
JS的流程控制包括选择和循环结构,选择结构包括if和switch语句,循环结构包括while、do while、和for循环,if语句中的条件表达式会默认转成布尔类型,if语句的第一种格式为“if (条件表达式) {当条件表达式是true时所执行的代码块;}”,当条件表达式是true时,就执行{}中所有的代码,并且只会执行一次,if语句的第二种格式为“if (条件表达式) {当条件表达式是true时所执行的代码块;} else {当条件表达式是false时所执行的代码块;}”,当条件表达式是true时,就执行if后面{}中的代码,当条件表达式是false时,就执行else后面{}中的代码,两个{}只有一个会执行,并且只会执行一次,当{}中需要执行的代码只有一句时,这个if语句也可以用三目运算表达式来表示,当有多句时,三目无效,if语句的第三种格式为“if (条件表达式A) {当A是true时所执行的代码块;} else if (条件表达式B) {当B是true时所执行的代码块;}... else {当前面所有条件表达式都是false时所执行的代码块;}”,当这样写时,这些所有代码就成为了一个整体,系统会从上至下的依次判断每一个条件表达式的true/false,哪一个条件表达式是true,就执行哪一个条件表达式后面{}中的代码,只会执行一次,并且其余的代码就都不会执行了,当前面所有的条件表达式都是false时,就会执行else后面{}中的代码,最后的“else {}”也是可以省略的,只不过省略之后,当前面所有的条件表达式都是false时,就没有可执行的代码了,if、else if、和else后面的大括号都可以省略,但是省略之后只有紧随其后的那句代码受其控制,例如“if (false) console.log (“hello”); console.log (“world”);(//只有“console.log (“hello”);”受到if控制)”,例如“if (false); {console.log (“hello”); console.log (“world”);}(//由于“;”也是一句代码(空语句),所以就相当于用一个空语句给隔开了,也就相当于if后没有了{},所以只有“;”受其控制)”,if语句也是可以嵌套使用的,例如“if (true) {if (false) {console.log (“hello1”);} else {console.log (“world1”);} } else {if (false) {console.log (“hello2”);} else {console.log (“world2”);} }(//最终会执行“console.log (“world1”);”)”,当if语句省略大括号时,else if和else会自动跟距离最近且没有被配对的if配对,例如“if (0) if (1) console.log (“A”); elseconsole.log (“B”); else if (1) console.log (“C”); elseconsole.log (“D”);(//紫色和紫色配成一对,蓝色和蓝色配成一对,配成一对的自动成为一个整体,最终会执行“console.log (“C”);”)”,当想要将变量a、b、c中的数值进行升序排序时,第一种方法是选择排序,其核心思想是使用正向思维从小向大排,即首先让变量a中的数值最小,然后再让变量b中的数值第二小,以此类推,具体流程是首先让变量a和变量b比,当a>b时,就交换变量a和变量b中的数值,然后再让变量a和变量c比,当a>c时,就交换变量a和变量c中的数值,这样一来,变量a中的数值就是最小的了,最后再让变量b和变量c比,当b>c时,就交换变量b和变量c中的数值,第二种方法是冒泡排序,其核心思想是使用逆向思维从大向小排,即首先通过模拟冒泡的方式让变量c中的数值最大,然后还是通过模拟冒泡的方式让变量b中的数值第二大,以此类推,具体流程是就像冒泡一样先让变量a和变量b比,当a>b时,就交换变量a和变量b中的数值,再让变量b和变量c比,当b>c时,就交换变量b和变量c中的数值,这样一来,变量c中的数值就是最大的了,最后再让变量a和变量b比,当a>b时,就交换变量a和变量b中的数值,switch语句的格式为“switch (表达式) {case 表达式A: 代码块A; break; case 表达式B: 代码块B; break; ... default: 代码块X; break;}”,会从上至下的依次判断每一个case操作符所作用的值是否跟小括号中的值全等,当全等时,就执行对应case的代码块,当前面所有的都不全等时,就会执行default的代码块,所有的代码块只有一个会执行,并且只会执行一次,一旦哪一个执行了,然后就break了(break操作符的作用是立即结束整个switch语句),也就是说整个switch语句就结束了,不会再继续向下执行了,所以一定不要忘记写“break;”,default不一定非要放到最后,但是无论放到什么位置,正常情况下都会最后执行,default也是可以省略的,只不过省略之后,当前面的case都不全等时,就没有可执行的代码块了,当对区间进行判断时,建议使用if语句,当对几个固定的值进行判断时,建议使用switch语句,但是能用if语句就用if语句,while循环的格式为“while (条件表达式) {当条件表达式是true时可重复多次执行的代码块;}”,完整流程是当条件表达式是true时,才会执行后面{}中的代码块,执行完之后,会再次判断条件表达式是否还是true,当还是true时,会再次执行,直到条件表达式是false为止,死循环就是条件表达式永远是true的循环,例如“while (true) {console.log (123);} ”,循环体就是循环结构条件表达式后面{}中的代码块,跟if语句一样,while循环后的{}可以省略,当省略时,只有紧随其后的那条语句受其控制,最简单的死循环的写法为“while (1);”,当想要计算正整数从1到5的“代数和”时,只需要不断的用“上一次的代数和”+“当前这个数”即可,而且每次加上的这个数都在自增1,代码为“let sum = 0;(//给“代数和”变量sum初始化) let num = 1;(//给“当前这个数”变量num初始化) while (num<=5) {sum = sum + num; num++;} console.log (sum);”,do while循环的格式为“do {当条件表达式是true时可重复多次执行的代码块;} while (条件表达式);”,无论条件表达式是true还是false,循环体都会先执行一次,然后当条件表达式是true时,循环体就可重复多次执行,直到条件表达式是false为止,当想要让用户输入密码(123456)时,当输入正确时,就弹窗“欢迎回来”,当输入错误时,就重新输入,代码为“let pwd = -1;(//先定义变量pwd并给它随便初始化一个值) do {pwd = prompt (“请输入密码”);} while (pwd !== “123456”); alert (“欢迎回来”);”,大部分情况下while和do while循环是可以互换的,当循环体中的代码块无论如何都需要先执行一次时,建议使用do while循环,其它的情况都建议使用while循环,for循环的第一种格式为“for (条件表达式的初始化; 条件表达式; 循环后增量表达式) {当条件表达式是true时可重复多次执行的代码块;}”,首先会执行“条件表达式的初始化”,并且只会执行一次,然后当“条件表达式”是true时,就执行“循环体”,最后再执行“循环后增量表达式”,直到条件表达式是false为止,例如“for (let num = 1; num <= 10; num++) {console.log (“发射子弹”+ num);}”,在for循环中,条件表达式的初始化、条件表达式、和循环后增量表达式都可以省略,而while循环是不能省略条件表达式的,例如“for (; ;) {console.log (“123”);}(//当条件表达式省略时就默认是true,死循环)”,第一种格式在循环结束之后,不可以让外界继续使用条件表达式中所定义的变量,for循环的第二种格式为“条件表达式的初始化; for (; 条件表达式; 循环后增量表达式) {当条件表达式是true时可重复多次执行的代码块;}”,第二种格式在循环结束之后,可以让外界继续使用条件表达式中所定义的变量,而while循环在循环结束之后,一定能让外界继续使用条件表达式中所定义的变量,所以for循环比while循环要更灵活,能用for循环就用for循环,break操作符除了可以用于选择结构的switch语句中以外也可以用于循环结构中,在循环结构的循环体中,break操作符的作用也是立即结束当前的循环,所以break操作符后面不能编写其它的代码,因为永远执行不到,即使是在循环嵌套的结构中,break操作符结束的也是当前所在的循环,对其它循环没有任何影响,例如“for (let i = 0; i < 5; i++) {console.log (“外循环”+ i); for (let j = 0; j < 5; j++) {console.log (“内循环”+ j); break;} }”,continue操作符只能用于循环结构中,作用是跳过本次执行,进入下一次执行,意思就是当本次循环体执行的代码执行了continue时,即使continue后面还有代码,也不再向后执行了,直接进入下一次循环体的执行,所以continue操作符后面也不能编写其它的代码,因为永远执行不到,即使是在for循环嵌套的结构中,continue操作符跳过的也是当前所在for循环的执行,对其它for循环没有任何影响,例如“for (let i = 0; i < 5; i++) {console.log (“外循环”+ i); for (let j = 0; j < 5; j++) {if (j === 1) {continue;} console.log (“内循环”+ j);} }”,在for循环嵌套的结构中,外层for循环控制的是共执行几个回合,内层for循环控制的是每个回合执行几次,例如“for (let i = 0; i < 3; i++) {for (let j = 0; j < 4; j++) {document.write (“*”);} document.write (“<br>”);}(//共执行3个回合,每个回合执行4次,执行完是一个三行四列的矩形)”,当想要将其变为倒直角三角形时,只需要将“let j = 0;”改为“let j = i;”/将“j < 4;”改为“j < 4 - i;”即可,当想要将其变为正直角三角形时,只需要将“j < 4;”改为“j <= i;”即可,当想要打印一个九九乘法表时,其代码为“for (let i = 1; i <= 9; i++) {for (let j = 1; j <= i; j++) {document.write (j + “*”+ i + “=”+ j*i);} document.write (“<br>”);}”
九、JavaScript数组
数组就是专门用于存储一组数据的,数组(Array)不属于基本数据类型,而属于引用数据类型里的对象类型,调用构造函数创建数组的格式为“let arr = new Array (7);(//返回一个长度为7并且所有元素全是undefined的数组,并保存到变量arr中)”,“let arr = new Array ();(//返回一个空数组,并保存到变量arr中)”,“let arr = new Array (“hello”, “world”, ...);(//返回一个元素为所传入内容的数组,并保存到变量arr中)”,向真/伪数组中存储数据的格式为“arr[0] = “hello”;”,从真/伪数组中返回数据的格式为“arr[0];(//返回此数组中第0个元素)”,例如“let arr = new Array (3);(//变量arr的值是[undefined, undefined, undefined]) arr[0] = “lnj”; arr[1] = “zs”; console.log (arr);(//变量arr的值是[“lnj”, “zs”, undefined])”,当访问了数组中不存在的索引时,不会报错,返回值是undefined,当数组的存储空间不够时,会自动扩容,比如一个数组的长度为3,我们也可以向第四块存储空间中存一些数据,此数组会自动扩容一块存储空间,并且长度变为4,内存中每一块存储空间都是有编号的,系统可以通过索引找到对应的空间,这就叫做哈希映射,但给数组中每个索引所分配的存储空间不一定是挨在一起的,所以查找效率很低,各大浏览器就对存储空间的分配进行了优化,由于数组中可以存储不同类型的数据,所以当存储的是相同类型的数据时,就会分配连续的存储空间,当存储的是不同类型的数据时,就不会分配连续的存储空间,还可以使用字面量中括号创建数组,相当于构造函数的简写,格式为“let arr = [];(//创建一个空数组,并保存到变量arr中)”,“let arr = [“hello”, “world”, ...];(//创建一个带数据的数组,并保存到变量arr中)”,数组的遍历就是依次访问数组中所存储的所有元素(所谓的遍历其实就是将所有的按照顺序逐个过一遍筛子),例如“for (let i = 0; i < arr.length; i++) {console.log (arr[i]);}”,数组对象访问它的length属性可以返回此数组的长度值,数组解构赋值是ES6中新增的一种赋值方式,就是遍历等号右边的数组,把所访问到的元素依次赋值给等号左边所定义的变量,等号左右两边的格式必须一模一样才能完全解构,即把等号右边数组中的每一个元素都赋值给等号左边所定义的变量,例如“let [a, b, c] = [1, 3, 5];(//完全解构)”,“let [a, b, [c, d] ] = [1, 3, [2, 4] ];(//完全解构)”,而等号左边所定义变量的个数跟等号右边数组中元素的个数可以不一样,例如“let [a, b] = [1, 3, 5];(//变量a的值是1,变量b的值是3)”,“let [a, b, c] = [1];(//变量a的值是1,变量b和c的值是undefined)”,当等号左边所定义变量的个数比等号右边数组中元素的个数少时,也可以使用ES6中新增的扩展运算符“...”来将剩余的元素打包到一个数组中并赋值给等号左边所定义的最后一个变量,例如“let [a, ...b] = [1, 3, 5];(//扩展运算符只能作用到最后一个变量上,变量b的值是[3, 5])”,当等号左边所定义变量的个数比等号右边数组中元素的个数多时,可以给多出的变量设置默认值,例如“let [a, b = 666, c = 888] = [1];”,但是所设置的默认值也可能被等号右边数组中的元素覆盖掉,例如“let [a, b = 666, c = 888] = [1, 3, 5];(//变量b和c的值会变成3和5)”,也可以把等号右边的字符串看作是一个数组,并解构赋值给等号左边所定义的变量,只有字符串可以,数值不可以,因为这时字符串被转成了一个类似数组的对象(伪数组),例如“let [a, b, c, d, e] = “我是中国人”;(//变量a、b、c、d、e的值分别是“我”、“是”、“中”、“国”、“人”)”,数组对象调用它的unshift/push方法可以返回在此数组的开头/结尾处新增所传入的一/多个元素之后的总长度值,例如“let arr = [“a”, “b”, “c”]; let res = arr.push (“d”, “e”);(//变量res的值是5)”,数组对象调用它的shift/pop方法可以返回此数组的最前/后面的一个元素被删除之后被删除的元素(不用传参),例如“let arr = [“a”, “b”, “c”]; let res = arr.shift ();(//变量res的值是“a”)”,数组对象调用它的splice方法既可以返回此数组被从参数1索引处向后删除参数2个元素之后被删除的元素所组成的新数组,也可以返回此数组被从参数1索引处修改参数2个元素为余下参数内容之后被修改的元素所组成的新数组,例如“let arr = [“a”, “b”, “c”]; let res = arr.splice (1, 2);(//变量res的值是[“b”, “c”],当不传入参数2时,就删除到末尾)”,“let arr = [“a”, “b”, “c”]; let res = arr.splice (1, 2, “d”, “e”);(//变量res的值是[“b”, “c”])”,数组对象访问它的指定索引可以返回/修改此数组中指定索引处的元素,格式为“console.log (arr[0]);(//返回)”,“arr[0] = “hello”;(//修改)”,清空一个数组的格式为“arr = [];”,“arr.length = 0;”,“arr.splice (0, arr.length);”,数组对象调用它的toString/join方法可以返回此数组被转成的字符串,格式为“let arr = [1, 2, 3, 4, 5]; let str = arr.toString ();(//变量str的值是“1, 2, 3, 4, 5”)”,数组对象调用它的join方法还可以返回此数组被转成所传入连接符形式的字符串,格式为“let arr = [1, 2, 3, 4, 5]; let str = arr.join (“+”);(//变量str的值是“1+2+3+4+5”,当传入一个空字符串时,代表去掉此数组的逗号并直接拼接,当不传入任何参数时,相当于传入了“,”,当某个数组中只有一个元素时,无论传不传参数,或传入啥参数,都直接返回此元素本身的字符串)”,使用扩展运算符“...”或数组对象调用它的concat方法都可以返回两个数组被拼接在一起的新数组(concat方法不会改变调用者中元素的个数),格式为“let arr1 = [1, 3, 5]; let arr2 = [2, 4, 6]; let res = arr1.concat (arr2);(//变量res的值是[1, 3, 5, 2, 4, 6],另外,实参还可以传入元素,起到push方法的作用,例如“[1, 2].concat(3);(//返回值是[1, 2, 3])”)”,“[...arr1, ...arr2, ...];(//此种方式是使用扩展运算符对数组进行解耦,可以拼接无数个数组,也就是将二维数组变成一维数组,其实扩展运算符也可以对对象进行解耦,格式为“{...obj1, ...obj2}”)”,当把两个数组相加时,返回值是它们被转成字符串之后再拼接的新字符串,例如“let res = arr1 + arr2;(//res是“1, 3, 52, 4, 6”)”,数组对象调用它的reverse方法可以返回此数组中元素顺序被颠倒之后的新数组,并且原有数组的元素顺序也会被颠倒,因为数组是引用数据类型,例如“let arr = [1, 2, 3, 4, 5]; let res = arr.reverse ();(//变量res的值是[5, 4, 3, 2, 1],并且原有数组arr也会变为这个)”,数组对象调用它的slice方法可以返回在此数组中所截取的传入索引范围所指代的新数组(不会影响到调用者),例如“let arr = [1, 2, 3, 4, 5]; let res = arr.slice (1, 3);(//变量res的值是[2, 3],参数1代表起始索引,是闭区间,参数2代表结束索引,是开区间,当不写结束索引时,就截取到末尾,当参数2传入-1时,就一直截取到此数组的倒数第二位,是包括倒数第二位的,当不传参时,就将此数组中的所有元素都放到一个新的数组中原样返回)”,数组对象调用它的indexOf/lastIndexOf方法可以在此数组中正/逆向从参数2索引处开始查找参数1元素,返回值是参数1元素在此数组中的索引值(一旦找到就会立即停止查找,没找到就返回-1),例如“let arr = [1, 2, 3, 4, 5, 3]; let res = arr.indexOf (3, 4);(//变量res的值是5,当参数2不写时,就默认从开头/结尾处开始查找)”,数组对象调用它的includes方法(ES6新增)可以判断此数组中是否包含所传入的元素,返回值是true/false,格式为“arr.includes (“hello”);”,数组对象调用它的fill方法可以返回此数组中所有元素都被设为所传入元素之后的新数组,格式为“arr.fill (“hello”);”,将一个数组中所有的数值(元素)进行升序排序的方式有计数排序、选择排序、和冒泡排序,计数排序的核心思想就是将待排序数组中的所有数值全部充当一个额外新数组中的索引值,具体流程是根据待排序数组中的最大数值再new Array一个“长度≧此最大数值+1”的额外新数组,并将里面的所有元素都fill成undefined,使用for循环遍历待排序数组,并将此数组中的所有数值都充当额外新数组的索引值,并给这些索引值处的元素随便做个记号,比如重新赋值一个“a”,最后再使用for循环遍历此额外新数组,当哪个索引值处的元素跟“a”全等时,就将此索引值打印出来即可,此方法的缺陷是一旦出现负数则无法排序,选择排序用for循环嵌套的“倒直角三角形”中的第一种形式(即let j = i;),当对n个数值进行排序时,就让外层for循环共比较n-1个回合,内层for循环第一个回合比较n-1次,然后逐个回合递减一次,内层for循环的循环体用“arr[i]”与“arr[j + 1]”比较即可,冒泡排序用for循环嵌套的“倒直角三角形”中的第二种形式(即j < (n-1) - i;),只不过内循环的循环体用“arr[j]”与“arr[j + 1]”比较即可,详见“69-JavaScript-数组练习4.html”,当一个父数组内部的每一个元素都是一个子数组时,这个父数组就叫做二维数组,二维数组内部的子数组就叫做一维数组,返回一维数组中某个元素的格式为“数组对象[二维数组索引][一维数组索引];”,当定义二维数组时,其内部有多少个一维数组,就写上多少个中括号即可,例如“let arr = [[], [], ...];”,使用两层for循环嵌套可以将每个一维数组中的数据都打印出来,例如“let arr = [[1, 3], [2, 4] ]; for (let i = 0; i < arr.length; i++) {let subArray = arr[i]; for (let j = 0; j < subArray.length; j++) {console.log (subArray[j]);} }”
十、JavaScript函数
函数是专门用来封装一段可执行的代码块的,在哪里调用此函数就在哪里开启一个函数作用域,然后在此函数作用域中执行此代码块,并且每调用一次都会开启一个新的函数作用域,使用函数的好处是冗余代码变少了,当需求变更时,需要修改的代码也变少了,定义函数的格式为“function 函数名称 (形参列表) {可执行的代码; return 表达式;}”,return操作符所作用的表达式的值就叫做函数的返回值,就是当{}中的代码执行完毕后系统返回的值,调用函数时所传入的常量/变量叫做实参,例如“function getSum (a, b) {let res = a + b; return res;} let num1 = 10; let num2 = 20; let result = getSum (num1, num2);(//变量result的值是30)”,一个函数可以有一/多个形参,也可以没有形参,可以写return,也可以不写return,当所定义函数的作用域中没写return/return后什么也没写时,则此函数的返回值是undefined,也就是说一切函数都有返回值,return操作符跟break操作符相似,作用是立即结束此return操作符所在函数作用域代码块的本次执行,所以“return;”的下一行不能编写任何代码,因为永远执行不到,当函数形参的个数比实参多时,则没有接收到数据的形参中所保存的默认值是undefined,当函数形参的个数比实参少时,则多出的实参函数无法接收到,函数的数据类型是引用数据类型里的对象类型,既然函数也是一种数据,所以也可以将其保存到一个变量中,并且可以利用此变量名调用此函数,格式为“let test = function () {代码块;}; test ();(//这种写法与“function test () {代码块;} test ();”是完全等同的,只不过前者是ES6新增的格式,后者是ES6之前的老格式)”,在“console.log ();”中,因为也是通过“()”来调用的,所以log也是一个函数(方法),在调用log函数(方法)时之所以可以传入一/多个实参,是因为在log函数(方法)的函数作用域中有一个默认的隐式参数arguments,其实所有的函数/方法的函数作用域中都有一个默认的隐式参数arguments(箭头函数除外),无论此函数/方法有没有设置形参,隐式参数arguments指代的都是所有传递给此函数/方法的实参所组成的伪数组,例如“function num () {for (let i = 0; i < arguments.length; i++) {console.log (arguments[i]);} num (10, 20, 30);(//调用的结果是执行“console.log (10); console.log (20); console.log (30);”)”,扩展运算符“...”也可以用在函数的形参列表中,可以将传递给函数的实参打包到一个数组中,但只能写在形参列表的最后,例如“function num (a, ...values) {console.log (values);} num (10, 20, 30);(//调用的结果是执行“console.log ([20, 30]);”,即使此数组只接收到一个实参,也会将其打包到数组中)”,在ES6之前,可以使用“逻辑或”来给形参设置默认值,格式为“function test (a, b) {a = a || 1; b = b || 2;}(//当调用此函数没给它传递实参时,由于形参a和b的默认值是undefined(是false),所以“逻辑或”这个运算符就会将它俩的默认值修改为1和2)”,从ES6开始,可以直接在形参后面使用“=”设置默认值,并且一句“函数的调用”也可以充当此默认值,格式为“function test1 (a = 1, b = test2 () ) {代码块;}”,函数可以充当其他函数的实参,格式为“let test1 = function () {alert (123);}; function test2 (a) {a ();} test2 (test1);(//调用的结果是执行“alert (123);”)”,函数也可以充当其他函数的返回值,并且在JS中函数是可以嵌套定义的,格式为“function test1 () {let test2 = function () {alert (123);}; return test2;} let test3 = test1 (); test3 ();(//调用的结果是执行“alert (123);”)”,匿名函数就是没有名称的函数,当只定义不使用时就会报错,可以充当函数的实参使用,格式为“function test1 (a) {a ();} test1 (function () {alert (123);} );(//调用的结果是执行“alert (123);”)”,也可以充当函数的返回值使用,格式为“function test1 () {return function () {alert (123);} } let test2 = test1 (); test2 ();(//调用的结果是执行“alert (123);”)”,还可以作为一个立即执行的函数使用,但必须使用小括号将此匿名函数包裹起来形成一个整体,才可以立即执行(具名函数也可以这样立即执行),格式为“(function () {alert (123);} ) ();”,箭头函数是ES6中新增的一种定义匿名函数的格式,就是为了简化定义函数的代码,格式为“let test = (形参列表) => {代码块;};”,当只有一个形参时,“()”可以省略,但当没有形参时,“()”不可以省略,当{}中只有一句代码时,{}和“;”也可以省略,当仅有的这句代码含有return操作符时,return也可以省略,但当return的是一个字面量对象时,省略return之后此对象需要使用小括号包起来,箭头函数自身作用域中的this指针,指代的不是调用者,而是此箭头函数所在作用域的this指针,并且无法利用箭头函数对象调用它的bind/call/apply方法来修改,箭头函数的函数作用域中是没有隐式参数arguments的,在函数的作用域中调用自己的函数就叫做递归函数,在一定程度上可以实现循环的功能,但是每次调用自己都会在内存中被分配一块新的存储空间,所以性能不是很好,当想要让用户输入密码(123456)时,当输入正确时,就弹窗“欢迎回来”,当输入错误时,就重新输入,代码为“function login () {let pwd = prompt (“请输入密码”); if (pwd !== “123456”) {login ();} alert (“欢迎回来”);} login ();”,跟其它代码的执行原理一样,“函数的调用”也相当于一句代码的执行,一旦这句代码所指代的代码块执行完毕,“执行进度”就会自动跑到这句代码的末尾处(即分号的后面),当上边示例的密码连续输错了两次时,就相当于连续两次在内部执行“login ();”,当第三次输入正确时,在点击完“欢迎回来”弹窗的确定按钮之后,就会额外再弹出两次此弹窗,就是因为“执行进度”又连续两次跑到了内部“login ();”的末尾处,充当实参的函数就叫做回调函数,由于在ES6之前没有块级作用域,所以只要在函数作用域之外就叫做全局作用域,从ES6开始,必须在函数和块级作用域之外才叫做全局作用域,定义在全局作用域的变量/函数叫做全局变量/函数,其有效范围是从定义变量/函数的这一行一直到此html文件(文档)的末尾,函数的{}里面叫做函数作用域,选择和循环结构的{}里面叫做块级作用域(ES6新增),定义在函数和块级作用域中的变量/函数叫做局部变量/函数,其有效范围是从定义变量/函数的这一行一直到此大括号的末尾,由于只有let声明符才识别块级作用域,所以使用var声明符在块级作用域中所定义的变量等同于全局变量,在函数/块级作用域中定义变量时,当省略let/var声明符时,就默认为全局变量,全局作用域又叫做0级作用域,当前{}中作用域的级别等于其父级作用域的级别+1,以此类推,当前作用域的父级作用域,的父级作用域,的父级作用域...以此类推,一直到全局作用域,这样的一个直系链条就叫做作用域链,在JS中,系统会优先访问/调用自己作用域中的变量/函数,当所定义函数的作用域中缺少需要访问/调用的某个变量/函数时,首先系统沿着作用域链必须能查找到所缺少的那个变量/函数,然后在调用此所定义的函数时还必须能成功的访问/调用到所缺少的那个变量/函数,这两个条件必须同时满足才行,缺一不可,否则在调用此所定义的函数时就会报错,免用var/let声明符,直接使用function函数声明符所定义的函数(ES6之前的老格式)可以先调用后定义,因为函数可以预解析,使用var/let声明符所定义的函数不可以先调用后定义(会报错),因为函数不会预解析,但是使用var声明符所定义的函数只有其变量会预解析,但仍然会报错,例如“say (); var say = function () {console.log (“hello itzb”);}”,预解析之后会变成“var say; say ();(//报错) say = function () {console.log (“hello itzb”);}”,当使用var声明符在相同作用域内重复定义同名的变量和函数时,会优先预解析函数,但在实际开发中千万不要这么干,由于ES6之前不存在块级作用域,所以ES6之前在选择和循环结构的{}中直接使用function函数声明符所定义的函数即视为全局函数,并且还可以预解析,但是只有低级web浏览器里的解释器才不识别块级作用域(即按照ES6之前的旧规执行),而高级web浏览器里的解释器则会识别块级作用域(即按照ES6的新规执行),例如“if (true) {function demo () {代码1;} } else {function demo () {代码2;} } demo ();(//由于这俩同名函数都是直接使用function函数声明符所定义的,明显为ES6之前的老格式,预解析之后会发生覆盖,所以低版本IE浏览器调用的结果是执行代码2,而谷歌浏览器调用的结果是执行代码1)”
十一、JavaScript的面向对象
面向对象是软件开发方法,是一种对现实世界抽象的理解,是计算机编程技术发展到一定阶段后的产物,面向对象是将功能封装进对象,强调具备了功能的对象,关注的是解决问题需要哪些对象,而面向过程强调的是功能行为,关注的是解决问题需要哪些步骤,但随着需求的更改,功能的增加,发现需要面对每一个步骤非常麻烦,这时就开始思索,能不能把这些步骤和功能再进行封装,封装时根据不同的功能,进行不同的封装,功能类似的封装在一起,这样结构就清晰多了,用的时候,找到对应的类就可以了,面向对象思想是一种符合人们思考习惯的思想,可以将复杂的事情简单化,将程序员从执行者转换成了指挥者,完成需求时先要去找具有所需功能的对象来用,当该对象不存在时,就创建一个具有所需功能的对象,这样可以简化开发并提高复用,JS中有一个系统提供的构造函数Object,创建对象的第一种方法是构造函数法,可以先new Object创建一个Object实例对象,然后再利用此实例对象动态的添加各种属性/方法,格式为“let obj = new Object (); obj.name = “lnj”; obj.say = function () {console.log (“hello world”);};”,第二种方法是字面量法,可以先使用字面量{}来创建一个对象,然后再利用此对象动态的添加各种属性/方法,格式为“let obj = {}; obj.name = “lnj”; obj.say = function () {console.log (“hello world”);};”,也可以在字面量{}中直接添加各种属性/方法,格式为“let obj = {name: “lnj”, say: function () {console.log (“hello world”);} };(//即使属性/方法名称带引号,利用“.”访问/调用的时候也可以不带引号)”,“let obj = {name: “lnj”, say () {console.log (“hello world”);} };”,方法其实就是一个函数,但是不能叫函数,只能叫方法,因为不能单独通过方法名和小括号来调用,只能通过“对象.方法名称 ();”来调用,无论是函数还是方法,其函数作用域中都有一个隐式参数this指针,this指针指代的就是调用者(即谁调用当前的这个函数/方法,this指针就是谁)/它的实例对象,普通函数作用域中的this指的是window对象,因为调用函数的标准写法是“window.函数名称 ();”,只不过window可以省略而已,每个全局变量/函数都默认是window对象的属性/方法,我们可以单独新建一个.js文件并引入进此.html文件,在那个.js文件里把封装好的函数放到一个立即执行的匿名函数的作用域中,此时这个封装好的函数不是全局函数,而是局部函数(因为其在1级作用域中),只有在此匿名函数的作用域(1级作用域)中手动把此函数赋值给window对象的同名方法,即手动将它变为全局函数,这样才不影响其在此.html文件(文档)全局范围内的调用,并且当在此匿名函数的作用域中定义了变量时,也不会污染全局的命名空间,方法的函数作用域中的this指针指代的是调用它的对象,构造函数作用域中的this指针指代的是它的实例对象,工厂函数就是专门用于定义类的函数,格式为“function person (name, age) {let per = new Object (); per.name = name; per.age = age; per.say = function () {console.log (“hello world”);}; return per;}”,有了此工厂函数之后,可以调用它来创建对象,格式为“let per1 = person (“lnj”, 34); let per2 = person (“zs”, 18);”,构造函数跟工厂函数一样,都是专门用于定义类的,本质上是工厂函数的简写(省略第一行和最后一行代码,共节省两行代码),函数名称的首字母最好大写,比较好辨认,在创建实例对象时,只能使用new操作符来调用,并且当不传参时,还可以省略后面的小括号,例如“function Person (name, age) {this.name = name; this.age = age; this.say = function () {console.log (“hello world”);};} let obj1 = new Person (“lnj”, 34); let obj2 = new Person (“zs”, 44);”,当new某个类时,{}中的代码便执行了起来,在{}中系统会自动new Object来创建一个实例对象,并把此实例对象赋值给this指针,结尾处还会自动添加一句“return this;”,由于每一个对象/变量/属性名称都会在运存中被分配一块存储空间(每块存储空间都是有自己的空间地址的,空间地址的格式为“0ffc1”),所以属性名称以及方法所对应的具名函数(例如“function say () {console.log (“hello world”);}”)这些小空间在类这个大空间的内部,就类似于文件夹套文件夹,而当我们每次new这个类创建实例对象时,都会按这种模式被分配一些空间,当某个方法的取值是固定不变的时,例如“say: function () {console.log (“hello world”);}”,new这个类创建几个实例对象就会使一模一样的say方法占用几份存储空间,这样一来就会造成很多无端的空间浪费,特别占运存,第一种解决方式是在类的外边把say方法的取值单独定义一个具名函数,然后在类的里边再赋值给say方法即可,此时say这块存储空间中保存的就是那个具名函数的空间地址(比如0ffc1,之所以叫做引用数据类型,是因为靠空间地址来引用,浅拷贝),但是此种方式会降低阅读性,并且会污染全局的命名空间,第二种解决方式就是再创建一个自定义对象,把这个say方法添加给这个自定义对象,然后再把这个自定义对象的say方法赋值给类里边的say方法即可,最终极的解决方式是利用prototype属性,每个类/函数对象都有一个默认的prototype属性(静态属性),由于此属性的取值是一个对象,所以此属性属于对象类型的属性,类/函数对象的prototype值就叫做此类/函数对象的原型对象(prototype对象),我们可以在类的外边自定义它的prototype对象,然后把say方法放到{}里边即可,例如“Person.prototype = {say: function () {console.log (“hello world”);} };”,由于每个prototype对象都有一个默认的constructor属性,此属性的取值是原来的类/函数对象,但是当我们自定义一个prototype对象时,势必就会把它原来默认的prototype对象覆盖掉,就相当于在prototype对象中没有constructor属性了,进而它就会沿着它的原型链去查找,所以为了不破坏原关系,就需要在自定义对象里面手动添加上一个键值对“constructor: 类”即可,使用new操作符来调用类/函数所创建(返回)的对象就叫做实例对象(每new一次都会创建一个全新的实例对象),任何实例对象、prototype对象、和自定义对象都有一个默认的__proto__(原型)属性,任何实例对象的__proto__值都是创建它的类/函数对象的prototype对象,而任何prototype和自定义对象的__proto__值都是Object类对象的prototype对象(由于Array.prototype.__proto__的取值是Object.prototype,而Array.prototype的取值又是arr.__proto__,所以arr.__proto__.__proto__的取值就是Object.prototype,这就是为什么“[1, 2, 3] instanceof Object;”会返回true的原因),但Object类对象的prototype对象的__proto__值是null(空),由于类也是函数,而函数的数据类型是对象,所以任何函数都是一个对象,是new Function(祖先函数类)所创建的实例对象,所以任何函数对象的__proto__值都是Function类对象的prototype对象,由于Function类(祖先函数类)也是函数,所以它也是一个对象,是new它自身所创建的实例对象,所以Function类对象的__proto__值也是Function类对象的prototype对象,一个对象的__proto__值(原型)的__proto__值(原型)的__proto__值(原型)...这条链就叫做原型链,一个对象会优先访问/调用自己的属性/方法,当它自己没有时,才会沿着它的原型链去查找,当还是没有时,此属性/方法的返回值就是undefined,所以,当在一个类中所定义的实例属性/方法跟它prototype对象中所定义的重名时,new此类对象所创建的实例对象就会以此类中所定义的为准,当给一个对象所不存在的属性/方法赋值时,此对象不会沿着它的原型链去查找,而是会给自己新增一个此属性/方法,由于一个对象的属性/方法的取值可以被任意修改,不是很安全,所以我们可以将取值进行封装,就是隐藏实现的细节,仅对外公开返回和修改此取值的接口(方法),例如“function Person () {this.name = “lnj”; let age = 34; this.setAge = function (myAge) {if (myAge >= 0) {age = myAge;} }; this.getAge = function () {return age;} } let per = new Person ();”,对于实例对象per而言,当想要返回其age值时,只需要调用它的getAge方法即可,当想要修改其age值时,只需要调用它的setAge方法并传入一个实参即可,此实参必须大于等于0,否则age值不会被修改,没有被封装的实例属性就叫做公有属性,被封装的实例属性就叫做私有属性,当在实例方法名称的前面加上下划线“_”时,就代表告诉其他的程序员这是一个私有方法,意思就是供其它方法在内部去调用的,不要在外部去调用,仅仅是告诉别人而已,并不是真正的私有方法,在JS中,可以利用实例对象访问/调用的属性/方法叫做实例属性/方法,可以利用类对象访问/调用的属性/方法叫做静态属性/方法,当类和类之间的关系是“is a”的关系时,就可以使用继承来减少代码的冗余度,比如学生is a人,即学生是一个人,比如有Person和Student两个类,Person是父类,Student是子类,在父类中已经定义的实例属性/方法在子类中就不用再定义了,第一种继承的方法是将父类的实例对象赋值给子类对象的prototype对象,例如“Student.prototype = new Person (); Student.prototype.constructor = Student;(//为了不破坏原关系)”,这样一来,子类的实例对象既可以访问/调用其父类中所定义的实例属性/方法,也可以访问/调用其父类对象的prototype对象中所定义的属性/方法,但是其弊端是,当在父类中存在形参时,则无法在子类中也设置这些形参,所以此种方法不可取,函数/方法对象调用它的bind方法可以返回一个this指针被指向参数1并且形参被修改为余下参数的新函数/方法(不会立即调用所返回的新函数/方法),函数/方法对象调用它的call/apply方法可以立即调用this指针被指向参数1并被传入余下参数的新函数/方法(只不过调用apply方法时必须通过数组/伪数组的方式进行传参),返回值是undefined,例如“let obj = {name: “zs”}; function test (a, b) {console.log (a, b); console.log (this);} let fn = test.bind (obj, 10, 20);(//变量fn的值是“function () {console.log (10, 20); console.log (obj);}”) test.call (obj, 10, 20); test.apply (obj, [10, 20]);(//后两个调用的结果都是执行“console.log (10, 20); console.log (obj);”,只不过调用apply方法时必须通过数组/伪数组的方式传递)”,push方法调用它的apply方法可以实现真伪数组的互相转换,当真转伪及系统自带的伪转真/普通伪时,可以支持任何浏览器,但当自定义的伪转真时,只支持IE9及以上的高级浏览器,而利用slice方法调用它的apply/call方法来实现伪转真可以支持任何浏览器(推荐),例如“var arr = [1, 3, 5, 7, 9]; var obj = {}; [].push.apply (obj, arr);(//此时变量obj的值是真数组arr被转成的伪数组{0: 1, 1: 3, 2: 5, 3: 7, 4: 9, length: 5})”,“var res = document.querySelectorAll (“div”);(//系统自带的伪数组) var arr = []; [].push.apply (arr, res);(//此时变量arr的值是系统自带的伪数组res被转成的真数组)”,“var obj = {0: “lnj”, 1: 33, length: 2}; var arr = [].slice.call (obj);(//此时变量arr的值是自定义的伪数组obj被转成的真数组,支持任何浏览器)”,当想要将一种自定义的伪数组转成另外一种自定义的伪数组时,只需要先转成真数组,然后再转成伪数组就可以了,例如jQuery.js的源码中“else if (njQuery.isArray (selector) ) {var arr = [].slice.call (selector);(//当传入一个真/伪数组时,先将所传入的真/伪数组统一转成真数组) [].push.apply (this, arr);(//再将此真数组中的元素push到空njQuery实例对象上,即真转伪)}”,第二种继承的方法是在子类的函数作用域中添加上一句“Person.call (this, 父形参1, 父形参2, ...);(//当没有使用new操作符时,就相当于将Person函数作用域中的代码原封不动的执行一遍)”,这样一来就把父类中所定义的实例属性/方法复制粘贴到子类中来了,并且在子类的形参列表中也可以正常填写这些“父形参”了,但是要想使子类的实例对象可以访问/调用其父类对象的prototype对象中所定义的属性/方法,就必须将父类对象的prototype对象赋值给其子类对象的prototype对象,例如“Student.prototype = Person.prototype;(//浅拷贝) Student.prototype.constructor = Student;(//为了不破坏原关系)”,但是当我们以后给子类对象的prototype对象动态的添加属性/方法时,也就相当于添加给了其父类对象的prototype对象,这样一来就污染了其父类对象的prototype对象,而且虽然第二句代码是为了不破坏子类的原关系,但是却起到了将其父类对象的prototype对象的constructor值改为了Student的作用,这样一来,就意外的破坏了其父类的原关系,所以这也是不可取的,最终极的继承方法就是前两种方法的结合版,该用call还用call,但要把父类的实例对象赋值给其子类对象的prototype对象,这样一来,子类的实例对象就可以沿着原型链访问/调用其父类对象的prototype对象中所定义的属性/方法了,以后用这种方法即可,一般编译型语言都是强类型语言,要求变量的使用要严格符合定义,无论普通变量还是函数名称,例如定义“int num;”,num中将来就只能够存储整型数据,一般解释型语言都是弱类型语言,不会要求变量的使用要严格符合定义,例如定义“let num;”,num中可以存储任何类型的数据,多态指的是强类型语言上的状态,就是一个变量的多种状态,new两个不同的类所创建的实例对象必然不是同一个类型的,是无法充当同一个函数的实参的,但当让这两个类都继承同一个父类时,这两个实例对象就可以充当同一个函数的实参了,由于JS是弱类型的语言,所以不用关注多态,从ES6开始,系统提供了一个class声明符,这个声明符也是专门用于定义类的,格式为“class 类名 {name = “lnj”; age = 34; say = function () {console.log (“hello world”);};}(//类名的首字母最好大写,不支持先调用后定义)”,但是这种添加实例属性/方法的写法并不是ES6正式版规则中的写法,除了谷歌浏览器以外,大部分的浏览器都不支持,所以就需要在类的函数作用域中添加constructor方法(构造方法),当new这个类的时候,系统会自动调用constructor方法(构造方法),格式为“class Person {constructor (name, age) {this.name = name; this.age = age; this.say = function () {console.log (“hello world”);};} }”,static操作符是用来添加静态属性和静态方法的,格式为“class Person {static name = “lnj”; static say () {console.log (“hello world”);} }”,但是这种添加静态属性的写法并不是ES6正式版规则中的写法,除了谷歌浏览器以外,大部分的浏览器都不支持,在ES6正式版的规则中,使用static操作符只能添加静态方法,不能添加静态属性,当想要添加静态属性时,只能在类的外部,通过“Person.name = “lnj”;”的方式动态的来添加,当使用class声明符定义一个类时,就不能自定义此类对象的prototype对象,当想要给此类对象的prototype对象添加属性时,只能在类的外部,通过“Person.prototype.name = “lnj”;”的方式动态的来添加,当想要给此类对象的prototype对象添加方法时,既可以在此类的外部动态的来添加,还可以在此类的内部通过“class Person {say () {console.log (“hello world”);} }”的方式来添加,ES6新增的继承的格式为“class 子类名 extends 父类名 {constructor (父形参1, 父形参2, name, age) {super (父形参1, 父形参2); this.name = name; this.age = age;} }”,super方法所起的作用就相当于ES6之前的call方法,直/间接利用类对象访问它的name属性可以返回此类的类名字符串,格式为“实例对象.constructor.name;(//间接,实例对象.constructor返回的是类对象)”,由于构造函数就是工厂函数的简写,而在工厂函数的作用域中每个实例对象都是new Object所创建的,所以“typeof 实例对象;”的返回值是“object”,使用instanceof操作符或类对象的prototype对象调用它的isPrototypeOf方法都可以判断某个实例对象是否是new某个类/其子类所创建的(换句话说就是判断某个类对象的prototype对象是否存在于某个实例对象的原型链上),返回值是true/false(当将实例对象换成基本数据时,返回值是false),格式为“per instanceof Person;”,“Person.prototype.isPrototypeOf (per);”,“[1, 2, 3] instanceof Object;(//返回值是true)”,使用in操作符或对象调用它的hasOwnProperty方法都可以判断某个对象是否拥有某个属性/方法,返回值是true/false,格式为““name”in obj;(//还可以判断在某个对象的原型链上是否拥有某个属性/方法)”,“obj.hasOwnProperty (“name”);(//只能判断某个对象是否拥有某个自有属性/方法)”,使用中括号包裹可以代替“.”来返回/设置一个对象属性/方法的取值,“.”只适用于字符串类型的属性/方法名称(比如“obj.1 = “hello world”;”会报错),而“中括号包裹”适用于所有基本数据类型的属性/方法名称,格式为“obj[“name”] = “lnj”;(//“name”也可以使用一个变量来代替) obj[“say”] = function () {console.log (“hello world”);}; obj[“say”] (); obj[1] = “lyz”; obj[true] = “jiangge”;”,使用delete操作符可以删除所作用对象的某个属性/方法,格式为“delete obj[“name”];”,“delete obj[“say”];”,对象的遍历可以依次将某个对象的所有属性/方法赋值给一个变量,可以使用高级for循环中的for in循环来遍历一个对象的所有属性和方法,格式为“for (let key in obj) {console.log (key);}”,“let key in obj”的意思是将此对象obj的所有属性/方法的名称字符串依次赋值给key这个变量(当将obj换成一个数组时,则是将此数组的所有索引值依次赋值给key这个变量,但是for in循环是无序的,所以不推荐用来遍历数组),当想要返回此对象的所有属性/方法的取值时,只需将循环体中的“key”换成“obj[key]”,由于key是变量,所以可以省略引号,一定不能换成“obj.key”,否则key就不是变量了,就变成了一个属性/方法的名称了,当只想遍历一个对象的属性而不想遍历此对象的方法时,只需在循环体的开头加上一句“if (obj[key] instanceof Function) {continue;}”即可,另外,使用for in循环还可以循环到一个实例对象的原型上的可枚举属性,而使用Object.keys();则获取不到,对象解构赋值(ES6新增)跟数组解构赋值的符号是不一样的,数组解构赋值使用中括号,对象解构赋值使用大括号,数组解构赋值等号左边的变量名称可以随便起,但是对象解构赋值等号左边的变量名称必须跟等号右边对象的属性/方法的名称一致,才能解构出取值,例如“let {age} = {name: “lnj”, age: 34};(//变量age的值是34)”,可以把字符串解构赋值给对象,只有字符串可以,数值不可以,这是因为字符串被系统转成了一个伪数组对象,例如“let {length : len} = “hello”;(//变量len的值是5)”,数组解构赋值的实际应用为“let arr = [1, 3]; function sum ([a, b]) {return a + b;} let res = sum (arr);(//变量res的值是4)”,对象解构赋值的实际应用为“let obj = {name: “lnj”, age: 34} function say ({name, age}) {console.log (name, age);} say (obj);(//调用的结果为执行“console.log (“lnj”, 34);”)”,运行内存中分为栈区和堆区,栈区由编译器自动分配释放,主要存放一些基本类型的变量和对象的引用,其优势是存取速度比堆要快,并且栈内的数据可以共享,但缺点是存在栈中的数据大小与生存期必须是确定的,容量比较小,缺乏灵活性,堆区由程序员分配释放,主要存放引用数据(对象),它是运行时动态分配内存的,因此存取速度较慢,但容量较大,当把基本数据赋值给变量时,是把这个数据保存在了变量中,当把引用数据赋值给变量时,只是把它在内存中的空间地址保存在了变量中,所以,在把一个变量赋值给另一个变量时,当拷贝的是数据时,就叫做深拷贝,当拷贝的是空间地址时,就叫做浅拷贝,例如“let num1 = 123; let num2 = num1; num2 = 666;(//深拷贝,虽然变量num2的值被修改为了666,但是对变量num1丝毫没有影响,变量num1的值还是123)”,例如“class Person {name = “lnj”; age = 34;} let p1 = new Person (); let p2 = p1; p2.name = “zs”;(//变量p1和p2的name值都被修改为了“zs”,由于只要是一个变量/对象都会在内存中被分配一块新的存储空间,所以变量p1、new Person ()、和变量p2在内存中是三块不同的存储空间,变量p1和p2中保存的只是实例对象“new Person ()”的空间地址而已,当实例对象的属性/方法值被修改后,由于变量p1和p2中所保存的空间地址不变,所以就相当于变量p1和p2的属性/方法值都被修改了)”,当想要将一个对象赋值给另一个对象时,使用for in循环/Object类对象调用它的assign方法可以间接实现深拷贝,例如“let p2 = new Object (); for (let key in p1) {p2[key] = p1[key];}”,“let p2 = new Object (); Object.assign (p2, p1);(//可以将后一个对象的属性和方法添加给前一个对象)”,但是以上两种方法的前提是对象p1的所有属性值都是基本数据,所以,最终极的方法的格式为“class Person {name = “lnj”; cat = {age: 3}; scores = [1, 3, 5];} let p1 = new Person (); let p2 = new Object (); function depCopy (p2, p1) {for (let key in p1) {if (p1[key] instanceof Object) {let subTarget = new p1[key].constructor; p2[key] = subTarget; depCopy (p2[key], p1[key]);} else {p2[key] = p1[key];} } } depCopy (p2, p1);”,使用for of循环(ES6新增)/数组对象调用它的forEach方法可以遍历此数组(只能遍历真数组/系统自带的伪数组,无法遍历自定义的伪数组对象),格式为“for (let value of arr) {console.log (value);}(//将数组arr中的元素依次赋值给变量value)”,“arr.forEach (function (currentValue, currentIndex, currentArray) {console.log (currentValue, currentIndex, currentArray);} );(//可以在遍历此数组arr的同时执行所传入的回调函数)”,系统内部forEach方法的取值是“Array.prototype.forEach = function (fn) {for (let i = 0; i < this.length; i++) {fn (this[i], i, this);} };”,所以,回调函数的形参currentValue所接收到的是此数组被遍历的元素,形参currentIndex所接收到的是此数组被遍历的索引值,形参currentArray所接收到的是此数组本身,数组对象调用它的findIndex方法可以在遍历此数组的同时执行所传入的回调函数,当所传入回调函数的返回值是true时,就停止遍历并返回满足条件那个元素的索引值,当遍历完成后所传入回调函数的返回值都是false时,就返回-1(findIndex方法是indexOf方法的定制版),例如“let arr = [3, 2, 6, 7, 6]; let index = arr.findIndex (function (currentValue, currentIndex, currentArray) {return currentValue === 6} ); console.log (index);”,系统内部findIndex方法的取值是“Array.prototype.findIndex = function (fn) {for (let i = 0; i < this.length; i++) {let result = fn (this[i], i, this); if (result) {return i;} } return -1;};”,所以,回调函数的三个形参所接收到的数据同上,数组对象调用它的find方法可以在遍历此数组的同时执行所传入的回调函数,当所传入回调函数的返回值是true时,就停止遍历并返回满足条件的那个元素,当遍历完成后所传入回调函数的返回值都是false时,就返回undefined,例如“let value = arr.find (function (currentValue, currentIndex, currentArray) {return currentValue === 6} ); console.log (value);”,系统内部find方法的取值是“Array.prototype.find = function (fn) {for (let i = 0; i < this.length; i++) {let result = fn (this[i], i, this); if (result !== undefined) {return this[i];} } return undefined;};”,所以,回调函数的三个形参所接收到的数据同上,数组对象调用它的filter方法可以在遍历此数组的同时执行所传入的回调函数,返回值是此数组中所有返回值是true的元素所组成的新数组,当遍历完成后所传入回调函数的返回值都是false时,返回值是一个空数组,例如“let arr = [1, 2, 3, 4, 5]; let newArray = arr.filter (function (currentValue, currentIndex, currentArray) {return currentValue % 2 === 0} );(//变量newArray的值是[2, 4])”,系统内部filter方法的取值是“Array.prototype.filter = function (fn) {let newArray = []; for (let i = 0; i < this.length; i++) {let result = fn (this[i], i, this); if (result) {newArray.push (this[i]);} } return newArray;};”,所以,回调函数的三个形参所接收到的数据同上,数组对象调用它的map方法可以在遍历此数组的同时执行所传入的回调函数,返回值是所传入回调函数的返回值所组成的新数组,例如“let newArray = arr.map (function (currentValue, currentIndex, currentArray) {if (currentValue % 2 === 0) {return currentValue;} } );(//变量newArray的值是[undefined, 2, undefined, 4, undefined])”,系统内部map方法的取值是“Array.prototype.map = function (fn) {let newArray = new Array (this.length); newArray.fill (undefined); for (let i = 0; i < this.length; i++) {let result = fn (this[i], i, this); if (result !== undefined) {newArray[i] = result;} } return newArray;};”,所以,回调函数的三个形参所接收到的数据同上,数组对象调用它的some/every方法可以在遍历此数组的同时执行所传入的回调函数,当所传入回调函数的返回值是true/false时,就停止遍历并返回true/false,当遍历完成后所传入回调函数的返回值都是false/true时,就返回false/true,当此数组为空数组时,直接返回false/true,数组对象调用它的splice方法或使用delete操作符都可以删除此数组中的元素,但splice方法删除某个元素后,后面的元素会向前占位,数组的长度也会变短,而delete操作符则会用“empty”来占位,数组的长度不会变短,所以,在遍历数组的同时,当想要调用splice方法来删除此数组中所有的元素时,只能从后向前删,格式为“let arr = [1, 2, 3, 4, 5]; for (let i = arr.length - 1; i >= 0; i--) {arr.splice (i, 1);}”,当想要使用delete操作符来删除此数组中所有的元素时,格式为“for (let i = 0; i < arr.length; i++) {delete arr[i];}”,当想要给数组中的数值排序时,除了计数、选择、和冒泡排序之外,还可以利用数组对象调用它的sort方法来给此数组中的数据排序,返回值是经过排序之后的新数组,并且原有数组的数据顺序也会被重排,因为数组是引用数据类型,升序排序的格式为“arr.sort ();”,“arr.sort (function (a, b) {return a - b;};”,“arr.sort ((a, b) => {return a.value - b.value;});”,“arr.sort (function (a, b) {if (a > b) {return 正数;} else if (a < b) {return 负数;} else {return 0;} } );(//“正数”和“负数”任意写一个数即可)”,降序排序的格式为“arr.sort (function (a, b) {return b - a;};”,“arr.sort ((a, b) => {return b.value - a.value;});”,或者参照升序排序的那种格式把它的前一个数改为“负数”,后一个数改为“正数”即可,当此数组中的元素是字符串时,则是按照此字符串所对应的Unicode数值进行排序,在JS中,字符串可以看做是一个特殊的数组,并可以被本地对象String包装成对象类型,所以字符串对象有很多跟数组对象一样的属性和方法,利用一个字符串直接访问/调用即可,不用非得保存到一个变量中,字符串对象访问它的length属性可以返回此字符串的长度值,格式为“str.length;”,字符串对象调用它的charAt方法或访问它的索引可以返回此字符串中某个索引处的字符,格式为“str.charAt (索引);”,“str[索引];(//只有高级浏览器才支持)”,字符串对象调用它的indexOf/lastIndexOf方法可以返回传入字符串在此字符串中的索引值(没有就返回-1),使用方法跟数组中的一模一样,字符串对象调用它的includes方法(ES6新增)可以判断此字符串中是否包含所传入的字符,返回值是true/false,使用方法跟数组中的一模一样,字符串对象调用它的concat方法或直接让两个字符串相加都可以返回两个字符串被拼接在一起的新字符串,更推荐后者,concat的使用方法跟数组中的一模一样,字符串对象调用它的substr/slice方法可以返回在此字符串中所截取的传入索引范围所指代的新字符串,格式为“str.substr (起始索引, 个数);(//参数2代表截取的个数,当不写时就截取到末尾)”,而slice的使用方法跟数组中的一模一样,另外,也可以把slice换成substring(推荐,功能都是一样的),字符串对象调用它的split方法可以返回此字符串被从传入连接符处切割成的多个字符串所组成的数组,格式为“str.split (“此字符串中的连接符”);(//当不传参/传入的参数在调用者中没有时,返回值是一个只有调用者这一个元素的数组)”,例如“let arr = “1, 2, 3”.split (“,”);(//变量arr的值是[“1”, “2”, “3”])”,字符串对象调用它的startsWith/endsWith方法(ES6新增)可以判断在此字符串的开头/结尾处是否为所传入的字符串,返回值是true/false,例如“let str = “www.it666.com”; let result = str.startsWith (“www”);(//变量result的值是true)”,模板字符串的符号为``(ES6新增),它可以当作普通字符串使用,也可以用来定义多行字符串,还可以在字符串中用“${变量}”来插入变量,其中的大括号叫做“插值语法”,在里面可以书写任何合法的JS表达式,例如“let name = “lnj”; let age = 34; let str = “我的名字是”+ name + “,我的年龄是”+ age;(//取值可以改写成`我的名字是${name},我的年龄是${age}`)”,使用字面量所创建的基本数据/使用const限定符所定义的固定不变的数据就叫做常量,常量是固定不变的,即使对它进行修改/拼接也都是额外再创建一个新常量,字符串对象调用它的replace方法可以返回此字符串中首个参数1字符串被替换为参数2字符串之后的新字符串,例如“let str = “abc”; let newStr = str.replace (“b”, “m”);(//变量newStr的值是“amc”,参数1也可以传入一个数值/正则表达式对象)”,字符串对象调用它的toUpperCase/toLowerCase方法可以返回此字符串中所有字母都被转成大/小写之后的新字符串,例如““AbC”.toLowerCase ();”,字符串对象调用它的match方法可以返回在此字符串中跟传入字符串相匹配的首个字符串所组成的真数组,未匹配到就返回null,格式为““abc123”.match (“abc”);(//参数也可以传入一个数值/正则表达式对象)”,字符串对象调用它的search方法可以返回此字符串中跟传入字符串相匹配的首个字符串的起始位置的索引值,未匹配到就返回-1,参数也可以传入一个数值/正则表达式对象,字符串对象调用它的trim方法可以返回此字符串首行被去除两端空格之后的新字符串(IE9以下低级浏览器不支持),字符串对象调用它的padStart/padEnd方法(ES7新增)可以返回在此字符串的开头/结尾处用参数2字符串将它补全成参数1总长度的新字符串,例如““8”.padStart (3, “0”);(//返回值是“008”)”,基本数据类型是无法访问任何属性和调用任何方法的,以前之所以能够访问属性和调用方法,是因为在运行的时候,系统自动用自带的构造函数(即本地对象)将基本数据类型包装成了对象类型,例如“let str = “www.it666.com”; str = new String (str);(//这句为系统自动添加,将字符串包装成对象) console.log (str.length); str.split (“.”);(//返回值是[“www”, “it666”, “com”])”,基本数据类型的包装类型常见的有String、Number、和Boolean,创建Number(数值)对象的格式为“var myNum = new Number (5);(//变量myNum的值为对象类型)”,“var myNum = Number (5);(//虽然变量myNum的值为数值类型,但是仍然可以调用系统自带的方法)”,数值对象调用它的toFixed方法可以返回此数值被保留“传入数字”位小数之后的字符串,例如“2.68.toFixed (1);(//返回值是“2.7”)”,JS中提供了三种系统自带的对象,包括宿主、本地、和内置对象,JS的运行环境(载体)就叫做宿主,当JS在web浏览器上运行时,宿主就是web浏览器,当JS在NodeJS上运行时,宿主就是NodeJS,宿主所提供的对象就叫做宿主对象,包括web浏览器和NodeJS宿主对象,web浏览器宿主对象包括window和document对象等,所有的DOM和BOM对象都属于web浏览器宿主对象,ECMAScript自带的类(构造函数)就叫做本地对象(在web浏览器和NodeJS上都有),包括Boolean、Number、String、Array、Function、Object、Date、和RegExp等,在任何的编程语言中,字母都是有其所对应的Unicode数值的,利用String类对象调用它的fromCharCode方法(静态方法)可以返回传入十进制Unicode数值被转成的字符串(字母/符号),字符串对象调用它的charCodeAt方法可以返回此字符串中第传入索引个字符的十进制Unicode数值,new Date所创建的实例对象就叫做日期对象,new Date既可以返回当前的日期对象(不传参),还可以返回所传入的日期对象(可以传入一个日期字符串/当前日期距离1970年1月1日(即世界标准时间)的毫秒值即时间戳/若干个表示年月日时分秒的数字参数),例如“let date1 = new Date (“2019-11-11 09:08:07”);(//变量date1的值是“Mon Nov 11 2019 09:08:07 GMT+0800 (中国标准时间)”)”,“let date2 = new Date (2019, 10, 11, 9, 8, 7);(//此种格式系统会自动增加一个月)”,Date类对象调用它的now方法或日期对象调用它的valueOf/getTime方法都可以返回当前日期距离1970年1月1日(即世界标准时间)的毫秒值即时间戳,两个日期对象相减可以得出两个日期之间相差多少毫秒,其底层的本质也是调用了valueOf方法,日期对象调用它的getFullYear/getMonth/getDate/getHours/getMinutes/getSeconds方法可以返回当前日期的年/月/日/时/分/秒的数值,但返回的月份会比当前的实际月份少一个月,当想要把当前日期格式化为中国的日期格式时,代码为“let date = new Date (); function formartDate (date) {return `${date.getFullYear ()}-${date.getMonth () + 1}-${date.getDate ()} ${date.getHours ()}:${date.getMinutes ()}:${date.getSeconds ()}`;} let res = formartDate (date);”,日期对象调用它的getDate/setDate方法可以返回/设置此日期中“日”的数值,例如“date.setDate (17);”,日期对象调用它的toUTCString/toGMTString方法可以返回此日期根据格林威治时间(GMT)被转成的字符串,在new Set(ES6新增)时传入一个数组可以创建一个没有重复元素的数据集合对象,可以利用这一唯一特性进行数组的去重工作,例如“let set5 = new Set ([1, 2, 2, 3, 4, 3, 5]);(//变量set5的值就是一个数据集合对象)”,数据集合对象访问它的size属性可以返回此数据集合对象的长度值,例如“set5.size;(//返回值是5)”,数据集合对象调用它的add方法可以在此数据集合对象的尾部添加所传入的元素,返回值是调用者,便于链式编程,数据集合对象调用它的has方法可以检测此数据集合对象中是否包含所传入的元素,返回值是true/false,数据集合对象调用它的delete方法可以移除此数据集合对象中所传入的元素,数据集合对象调用它的clear方法可以移除此数据集合对象中的所有元素,ECMAScript自带的对象就叫做内置对象(在web浏览器和NodeJS上都有),包括全局(在web浏览器上,全局对象指的就是window对象,在NodeJS上,全局对象指的就是global对象)、Math、和JSON对象,可以利用内置对象来调用它里面自带的方法,Math对象调用它的floor方法可以返回传入数值被向下取整之后的新数值(就是直接砍掉所有的小数位),例如“let num = 3.9; let value = Math.floor (num);(//变量value的值是3)”,Math对象调用它的ceil方法可以返回传入数值被向上取整之后的新数值(只要有小数位就会砍掉,并给整数位+1),Math对象调用它的round方法可以返回传入数值的小数位被四舍五入之后的新整数值(比如3.5会被转成4,而-3.5会被转成-3),Math对象调用它的abs方法可以返回传入数值被取绝对值之后的新数值,Math对象调用它的random方法可以返回一个0~1之间的随机数(前闭后开,包括0但不包括1),例如“let value = Math.random ();(//变量value是一个0~1之间的随机数)”,当想要获取一个a和b之间的随机整数时(包括a和b),格式为“function getRandomIntInclusive (min, max) {min = Math.ceil (min); max = Math.floor (max); return Math.floor (Math.random () * (max - min + 1) ) + min;} let value = getRandomIntInclusive(1, 10);(//或者直接用变量value接收getRandomIntInclusive函数的返回值,把min和max改为1和10即可)”,Math对象调用它的min/max方法可以返回传入的数值参数列表中的最小/大数值,例如“Math.max (...[1, 2, 3]);(//扩展运算符也可以这么用)”,Math对象调用它的sqrt方法可以返回传入数值的平方根值,Math对象访问它的pi属性可以返回圆周率值,JSON(JavaScript Object Notation)就是JS对象简谱,是一种轻量级的数据交换格式,它是基于ECMAScript(欧洲计算机协会制定的JS规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据,JSON其实就是字符串版本的JS对象/数组,由于其数据类型是字符串,所以最外层必须加引号(当写到一个.json文件中时,最外层的引号可以省略),并且JSON中的key只能是字符串,当value是基本数据时,直接使用此基本数据即可,当value是JS对象/数组时,必须将此JS对象/数组转成JSON,由于父JSON的最外层已经加引号了,所以其内部子JSON的最外层就不用再加引号了,例如“let json1 = `{“lnj”: {“age”: 34} }`;let json2 = `[{“name”: “lnj”}, {“age”: 34}]`; let json3 = `{“person”: [{“name”: “lnj”}, {“name”: “zs”}] }`;”,JSON对象调用它的stringify方法可以返回传入JS对象/数组被转成(序列化成)的JSON(不支持IE9以下的浏览器),格式为“JSON.stringify (obj);”,JSON对象调用它的parse方法可以返回传入JSON被转成(反序列化成)的JS对象/数组(不支持IE9以下的浏览器),格式为“JSON.parse (json);”,在IE9以下的浏览器中,需要引入一个json2.js插件才能正常使用stringify和parse方法,在GitHub上(例如github.com/douglascrockford/JSON-js)下载此插件就可以了,全局对象调用它的eval方法可以返回传入标准/非标准JSON被转成的JS对象(非标准的JSON指的是key没有加引号,传入的参数需用小括号括上,但当传入的是JSON数组时,加不加小括号均可),例如“var obj = eval (“(”+ json + “)”);”,访问网址“pv.sohu.com/cityjson”可以显示自己当前在哪个城市,“<script src=“http://pv.sohu.com/cityjson”></script>”就相当于“<script>var returnCitySN = {“cip”: “125.211.136.146”, “cid”: “230100”, “cname”: “黑龙江省哈尔滨市”};</script>”,我们可以将此<script>放在我们自己的<script>之前,这样一来,在我们自己的js文件中就可以访问变量returnCitySN了,JS模板引擎有百度的、腾讯的、淘宝的等等,腾讯的JS模板引擎是现在企业开发中用得最多的,网址是github.com/aui/art-template,下载并解压缩,然后进入example文件夹,就会看到可以在NodeJS中使用,也可以在web浏览器中使用,可以先学会web-native-syntax模板以创建此html页面所有相似的区块,进入web-native-syntax文件夹,basic.html文件就是一个示例程序,双击打开它,然后查看网页源代码,就会看到具体如何使用的示例,首先在一对<head>中有一个js文件,我们需要将这个js文件引入到我们自己的html文件当中,然后向下看,共有两对<script>,第一对<script>是一个带有id名称的JS模板,必须仿照此模板的格式将某一个区块的HTML代码写入其中,在此JS模板中可以嵌入JS代码,在所嵌入的JS代码中也可以再嵌入HTML代码,在所嵌入的HTML代码中还可以再嵌入JS代码,第二对<script>是JS代码,也必须仿照这种格式来使用,Object类对象调用它的keys方法可以返回传入对象中所有可枚举属性的名称字符串所组成的数组,Object类对象调用它的values方法可以返回传入对象中所有可枚举属性的值所组成的数组,Object类对象调用它的getOwnPropertyNames方法可以返回传入对象中所有自有属性的名称字符串所组成的数组(包括不可枚举属性但不包括Symbol值作为名称的属性),在JS中,对象的属性分为可枚举和不可枚举之分,枚举是指对象中的属性是否可以被遍历出来,再简单点说就是属性是否可以被列举出来,对象的每一个属性都有一个描述对象,用来描述和控制该属性的行为,利用Object类对象调用它的getOwnPropertyDescriptor/getOwnPropertyDescriptors方法前者可以返回参数1对象的参数2自有属性的数据/存取描述对象,后者可以返回传入对象的所有自有属性的数据/存取描述对象所组成的对象(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性),例如“Object.getOwnPropertyDescriptor (obj, “name”);”,数据描述对象中包含四个属性,分别是configurable属性,表示能否使用delete操作符删除此属性/能否修改此属性的特性/能否把此属性修改为访问器属性,当此属性是直接通过字面量定义的时,默认值是true,当此属性是通过defineProperty/defineProperties方法定义的时,默认值是false,enumerable属性,表示此属性是否可枚举,即是否可以通过for in循环/Object.keys();获取到此属性,当此属性是直接通过字面量定义的时,默认值是true,当此属性是通过defineProperty/defineProperties方法定义的时,默认值是false,writable属性,表示能否修改此属性的值,当此属性是直接通过字面量定义的时,默认值是true,当此属性是通过defineProperty/defineProperties方法定义的时,默认值是false,value属性,表示此属性对应的值,默认值是undefined,存取描述对象也包含四个字段,其中前两个跟数据描述对象一样,后两个一个是get方法,即访问此属性时所调用的方法,返回值就是此属性的值,一个是set方法,即给此属性设置值时所调用的方法,set方法接收到的唯一参数是设置给此属性的取值,get和set方法默认是没有的(undefined),只能通过Object.defineProperty();/Object.defineProperties();来定义,Object类对象调用它的defineProperty/defineProperties方法前者可以给参数1对象的参数2属性定义参数3数据/存取描述对象,后者可以给参数1对象的多个属性以参数2大对象的形式定义多个数据/存取描述对象,Array类对象调用它的isArray方法可以判断传入的值是否是一个真数组,返回值是true/false