第十五章正则表达式
最后更新于:2022-04-01 04:59:14
#第十五章正则表达式
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
>假设用户需要在HTML表单中填写姓名、地址、出生日期等。那么在将表单提交到服务器进一步处理前,JavaScript程序会检查表单以确认用户确实输入了信息并且这些信息是符合要求的。
###1.什么是正则表达式
>正则表达式(regular expression)是一个描述字符模式的对象。ECMAScript的RegExp类表示正则表达式,而String和RegExp都定义了使用正则表达式进行强大的模式匹配和文本检索与替换的函数。
>正则表达式主要用来验证客户端的输入数据。用户填写完表单单击按钮之后,表单就会被发送到服务器,在服务器端通常会用PHP、ASP.NET 等服务器脚本对其进行进一步处理。因为客户端验证,可以节约大量的服务器端的系统资源,并且提供更好的用户体验。
###2.创建正则表达式
>创建正则表达式和创建字符串类似,创建正则表达式提供了两种方法,一种是采用new运算符,另一个是采用字面量方式。
**1.两种创建方式**
~~~
var box = new RegExp('box'); //第一个参数字符串
var box = new RegExp('box', 'ig'); //第二个参数可选模式修饰符
~~~
**模式修饰符的可选参数**
| 参 数 | 含 义 |
| -- | -- |
| i | 忽略大小写 |
| g | 全局匹配 |
| m | 多行匹配 |
~~~
var box = /box/; //直接用两个反斜杠
var box = /box/ig; //在第二个斜杠后面加上模式修饰符
~~~
**2.测试正则表达式**
>RegExp 对象包含两个方法:test()和exec(),功能基本相似,用于测试字符串匹配。test()方法在字符串中查找是否存在指定的正则表达式并返回布尔值,如果存在则返回true,不存在则返回false。exec()方法也用于在字符串中查找指定正则表达式,如果exec()方法执行成功,则返回包含该查找字符串的相关信息数组。如果执行失败,则返回null。
**RegExp 对象的方法**
| 方 法 | 功 能 |
| -- | -- |
| test | 在字符串中测试模式匹配,返回true 或false |
| exec | 在字符串中执行匹配搜索,返回结果数组 |
~~~
/*使用new 运算符的test 方法示例*/
var pattern = new RegExp('box', 'i'); //创建正则模式,不区分大小写
var str = 'This is a Box!'; //创建要比对的字符串
alert(pattern.test(str)); //通过test()方法验证是否匹配
/*使用字面量方式的test 方法示例*/
var pattern = /box/i; //创建正则模式,不区分大小写
var str = 'This is a Box!';
alert(pattern.test(str));
/*使用一条语句实现正则匹配*/
alert(/box/i.test('This is a Box!')); //模式和字符串替换掉了两个变量
/*使用exec 返回匹配数组*/
var pattern = /box/i;
var str = 'This is a Box!';
alert(pattern.exec(str)); //匹配了返回数组,否则返回null
~~~
*PS:exec 方法还有其他具体应用,我们在获取控制学完后再看。*
**3.使用字符串的正则表达式方法**
>除了test()和exec()方法,String对象也提供了4个使用正则表达式的方法。
**String 对象中的正则表达式方法**
| 方 法 | 含 义 |
| -- | -- |
| match(pattern) | 返回pattern 中的子串或null |
| replace(pattern, replacement) | 用replacement 替换pattern |
| search(pattern) | 返回字符串中pattern 开始位置 |
| split(pattern) | 返回字符串按指定pattern 拆分的数组 |
~~~
/*使用match 方法获取获取匹配数组*/
var pattern = /box/ig; //全局搜索
var str = 'This is a Box!,That is a Box too';
alert(str.match(pattern)); //匹配到两个Box,Box
alert(str.match(pattern).length); //获取数组的长度
/*使用search 来查找匹配数据*/
var pattern = /box/ig;
var str = 'This is a Box!,That is a Box too';
alert(str.search(pattern)); //查找到返回位置,否则返回-1
~~~
*PS:因为search 方法查找到即返回,也就是说无需g 全局*
~~~
/*使用replace 替换匹配到的数据*/
var pattern = /box/ig;
var str = 'This is a Box!,That is a Box too';
alert(str.replace(pattern, 'Tom')); //将Box 替换成了Tom
/*使用split 拆分成字符串数组*/
var pattern = / /ig;
var str = 'This is a Box!,That is a Box too';
alert(str.split(pattern)); //将空格拆开分组成数组
~~~
**RegExp对象的静态属性**
| 属 性 | 短 名 | 含 义 |
| -- | -- | -- |
| input| $_| 当前被匹配的字符串|
|lastMatch| $& |最后一个匹配字符串|
|lastParen| $+ |最后一对圆括号内的匹配子串|
|leftContext| $` |最后一次匹配前的子串|
|multiline| $*| 用于指定是否所有的表达式都用于多行的布尔值|
|rightContext| $'| 在上次匹配之后的子串 |
*/*使用静态属性*/*
*var pattern = /(g)oogle/;*
*var str = 'This is google!';*
*pattern.test(str); //执行一下*
*alert(RegExp.input); //This is google!*
*alert(RegExp.leftContext); //This is*
*alert(RegExp.rightContext); //!*
*alert(RegExp.lastMatch); //google*
*alert(RegExp.lastParen); //g*
*alert(RegExp.multiline); //false*
*PS:Opera 不支持input、lastMatch、lastParen 和multiline属性。IE 不支持multiline 属性。*
>所有的属性可以使用短名来操作
RegExp.input 可以改写成RegExp['$_'],依次类推。但RegExp.input 比较特殊,它还可以写成RegExp.$_。
**RegExp对象的实例属性**
|属性|含义|
| -- | -- |
|global| Boolean 值,表示g 是否已设置|
|ignoreCase| Boolean 值,表示i 是否已设置|
|lastIndex| 整数,代表下次匹配将从哪里字符位置开始|
|multiline| Boolean 值,表示m 是否已设置|
|Source |正则表达式的源字符串形式|
~~~
/*使用实例属性*/
var pattern = /google/ig;
alert(pattern.global); //true,是否全局了
alert(pattern.ignoreCase); //true,是否忽略大小写
alert(pattern.multiline); //false,是否支持换行
alert(pattern.lastIndex); //0,下次的匹配位置
alert(pattern.source); //google,正则表达式的源字符串
var pattern = /google/g;
var str = 'google google google';
pattern.test(str); //google,匹配第一次
alert(pattern.lastIndex); //6,第二次匹配的位
~~~
*PS:以上基本没什么用。并且lastIndex 在获取下次匹配位置上IE 和其他浏览器有偏差,主要表现在非全局匹配上。lastIndex还支持手动设置,直接赋值操作*。
###3.获取控制
>正则表达式元字符是包含特殊含义的字符。它们有一些特殊功能,可以控制匹配模式的方式。反斜杠后的元字符将失去其特殊含义。
**字符类:单个字符和数字**
|元字符/元符号|匹配情况|
| -- | -- |
|. |匹配除换行符外的任意字符|
|[a-z0-9]| 匹配括号中的字符集中的任意字符|
|[^a-z0-9] |匹配任意不在括号中的字符集中的字符|
|\d |匹配数字|
|\D |匹配非数字,同[^0-9]相同|
|\w |匹配字母和数字及_|
|\W |匹配非字母和数字及_|
**字符类:空白字符**
|元字符/元符号|匹配情况|
| -- | -- |
|\0| 匹配null 字符|
|\b| 匹配空格字符|
|\f| 匹配进纸字符|
|\n| 匹配换行符|
|\r| 匹配回车字符|
|\t| 匹配制表符|
|\s| 匹配空白字符、空格、制表符和换行符|
|\S| 匹配非空白字符|
**字符类:锚字符**
|元字符/元符号|匹配情况|
| -- | -- |
|^ |行首匹配|
|$ |行尾匹配|
|\A |只有匹配字符串开始处|
|\b |匹配单词边界,词在[]内时无效|
|\B |匹配非单词边界|
|\G |匹配当前搜索的开始位置|
|\Z |匹配字符串结束处或行尾|
|\z |只匹配字符串结束处|
**字符类:重复字符**
|元字符/元符号|匹配情况|
| -- | -- |
|x? |匹配0 个或1 个x|
|x* |匹配0 个或任意多个x|
|x+ |匹配至少一个x|
|(xyz)+ |匹配至少一个(xyz)|
|x{m,n} |匹配最少m 个、最多n 个x|
**字符类:替代字符**
|元字符/元符号|匹配情况|
| -- | -- |
|this\where\logo| 匹配this 或where 或logo 中任意一个|
**字符类:记录字符**
|元字符/元符号|匹配情况|
| -- | -- |
|(string)| 用于反向引用的分组|
|\1 或$1| 匹配第一个分组中的内容|
|\2 或$2| 匹配第二个分组中的内容|
|\3 或$3| 匹配第三个分组中的内容|
~~~
/*使用点元字符*/
var pattern = /g..gle/; //.匹配一个任意字符
var str = 'google';
alert(pattern.test(str));
/*重复匹配*/
var pattern = /g.*gle/; //.匹配0 个一个或多个
var str = 'google'; //*,?,+,{n,m}
alert(pattern.test(str));
/*使用字符类匹配*/
var pattern = /g[a-zA-Z_]*gle/; //[a-z]*表示任意个a-z 中的字符
var str = 'google';
alert(pattern.test(str));
var pattern = /g[^0-9]*gle/; //[^0-9]*表示任意个非0-9 的字符
var str = 'google';
alert(pattern.test(str));
var pattern = /[a-z][A-Z]+/; //[A-Z]+表示A-Z 一次或多次
var str = 'gOOGLE';
alert(pattern.test(str));
/*使用元符号匹配*/
var pattern = /g\w*gle/; //\w*匹配任意多个所有字母数字_
var str = 'google';
alert(pattern.test(str));
var pattern = /google\d*/; //\d*匹配任意多个数字
var str = 'google444';
alert(pattern.test(str));
var pattern = /\D{7,}/; //\D{7,}匹配至少7 个非数字
var str = 'google8';
alert(pattern.test(str));
/*使用锚元字符匹配*/
var pattern = /^google$/; //^从开头匹配,$从结尾开始匹配
var str = 'google';
alert(pattern.test(str));
var pattern = /goo\sgle/; //\s 可以匹配到空格
var str = 'goo gle';
alert(pattern.test(str));
var pattern = /google\b/; //\b 可以匹配是否到了边界
var str = 'google';
alert(pattern.test(str));
/*使用或模式匹配*/
var pattern = /google|baidu|bing/; //匹配三种其中一种字符串
var str = 'google';
alert(pattern.test(str));
/*使用分组模式匹配*/
var pattern = /(google){4,8}/; //匹配分组里的字符串4-8 次
var str = 'googlegoogle';
alert(pattern.test(str));
var pattern = /8(.*)8/; //获取8..8 之间的任意字符
var str = 'This is 8google8';
str.match(pattern);
alert(RegExp.$1); //得到第一个分组里的字符串内容
var pattern = /8(.*)8/;
var str = 'This is 8google8';
var result = str.replace(pattern,'<strong>$1</strong>'); //得到替换的字符串输出
document.write(result);
var pattern = /(.*)\s(.*)/;
var str = 'google baidu';
var result = str.replace(pattern, '$2 $1'); //将两个分组的值替换输出
document.write(result);
~~~
|贪婪|惰性|
| -- | -- |
|+ |+?|
|? |??|
|* |*?|
|{n} |{n}?|
|{n,} |{n,}?|
|{n,m} |{n,m}?|
~~~
/*关于贪婪和惰性*/
var pattern = /[a-z]+?/; //?号关闭了贪婪匹配,只替换了第一个
var str = 'abcdefjhijklmnopqrstuvwxyz';
var result = str.replace(pattern, 'xxx');
alert(result);
var pattern = /8(.+?)8/g; //禁止了贪婪,开启的全局
var str = 'This is 8google8, That is 8google8, There is 8google8';
var result = str.replace(pattern,'<strong>$1</strong>');
document.write(result);
var pattern = /8([^8]*)8/g; //另一种禁止贪婪
var str = 'This is 8google8, That is 8google8, There is 8google8';
var result = str.replace(pattern,'<strong>$1</strong>');
document.write(result);
/*使用exec 返回数组*/
var pattern = /^[a-z]+\s[0-9]{4}$/i;
var str = 'google 2012';
alert(pattern.exec(str)); //返回整个字符串
var pattern = /^[a-z]+/i; //只匹配字母
var str = 'google 2012';
alert(pattern.exec(str)); //返回google
var pattern = /^([a-z]+)\s([0-9]{4})$/i; //使用分组
var str = 'google 2012';
alert(pattern.exec(str)[0]); //google 2012
alert(pattern.exec(str)[1]); //google
alert(pattern.exec(str)[2]); //2012
/*捕获性分组和非捕获性分组*/
var pattern = /(\d+)([a-z])/; //捕获性分组
var str = '123abc';
alert(pattern.exec(str));
var pattern = /(\d+)(?:[a-z])/; //非捕获性分组
var str = '123abc';
alert(pattern.exec(str));
/*使用分组嵌套*/
var pattern = /(A?(B?(C?)))/; //从外往内获取
var str = 'ABC';
alert(pattern.exec(str));
/*使用前瞻捕获*/
var pattern = /(goo(?=gle))/; //goo 后面必须跟着gle 才能捕获
var str = 'google';
alert(pattern.exec(str));
/*使用特殊字符匹配*/
var pattern = /\.\[\/b\]/; //特殊字符,用\符号转义即可
var str = '.[/b]';
alert(pattern.test(str));
/*使用换行模式*/
var pattern = /^\d+/mg; //启用了换行模式
var str = '1.baidu\n2.google\n3.bing';
var result = str.replace(pattern, '#');
alert(result);
~~~
###4.常用的正则
1.检查邮政编码
~~~
var pattern = /[1-9][0-9]{5}/; //共6 位数字,第一位不能为0
var str = '224000';
alert(pattern.test(str));
~~~
2.检查文件压缩包
~~~
var pattern = /[\w]+\.zip|rar|gz/; //\w 表示所有数字和字母加下划线
var str = '123.zip'; //\.表示匹配.,后面是一个选择
alert(pattern.test(str));
~~~
3.删除多余空格
~~~
var pattern = /\s/g; //g 必须全局,才能全部匹配
var str = '111 222 333';
var result = str.replace(pattern,''); //把空格匹配成无空格
alert(result);
~~~
4.删除首尾空格
~~~
var pattern = /^\s+/; //强制首
var str = ' goo gle ';
var result = str.replace(pattern, '');
pattern = /\s+$/; //强制尾
result = result.replace(pattern, '');
alert('|' + result + '|');
var pattern = /^\s*(.+?)\s*$/; //使用了非贪婪捕获
var str = ' google ';
alert('|' + pattern.exec(str)[1] + '|');
var pattern = /^\s*(.+?)\s*$/;
var str = ' google ';
alert('|' + str.replace(pattern, '$1') + '|');//使用了分组获取
~~~
5.简单的电子邮件验证
~~~
var pattern = /^([a-zA-Z0-9_\.\-]+)@([a-zA-Z0-9_\.\-]+)\.([a-zA-Z]{2,4})$/;
var str = 'yc60.com@gmail.com';
alert(pattern.test(str));
var pattern = /^([\w\.\-]+)@([\w\.\-]+)\.([\w]{2,4})$/;
var str = 'yc60.com@gmail.com';
alert(pattern.test(str));
~~~
*PS:以上是简单电子邮件验证,复杂的要比这个复杂很多,大家可以搜一下。*
第十四章匿名函数与闭包
最后更新于:2022-04-01 04:59:12
#第十四章匿名函数与闭包
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数。声明:本节内容需要有面向对象和少量设计模式基础,否则无法听懂.
###1.>匿名函数
~~~
//普通函数
function box() { //函数名是box
return 'Zhang';
}
~~~
~~~
//匿名函数
function () { //匿名函数,会报错
return 'Zhang';
}
~~~
~~~
//通过表达式自我执行
(function box() { //封装成表达式
alert('Zhang');
})(); //()表示执行函数,并且传参
~~~
~~~
//把匿名函数赋值给变量
var box = function () { //将匿名函数赋给变量
return 'Zhang';
};
alert(box()); //调用方式和函数调用相似
~~~
~~~
//函数里的匿名函数
function box () {
return function () { //函数里的匿名函数,产生闭包
return 'Zhang';
}
}
alert(box()()); //调用匿名函数
~~~
###2.>闭包
>闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。
~~~
//通过闭包可以返回局部变量
function box() {
var user = 'Zhang';
return function () { //通过匿名函数返回box()局部变量
return user;
};
}
alert(box()()); //通过box()()来直接调用匿名函数返回值
var b = box();
alert(b()); //另一种调用匿名函数返回值
~~~
>使用闭包有一个优点,也是它的缺点:就是可以把局部变量驻留在内存中,可以避免使用全局变量。(全局变量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难,所以推荐使用私有的,封装的局部变量)。
~~~
//通过全局变量来累加
var age = 100; //全局变量
function box() {
age ++; //模块级可以调用全局变量,进行累加
}
box(); //执行函数,累加了
alert(age); //输出全局变量
~~~
~~~
//通过局部变量无法实现累加
function box() {
var age = 100;
age ++; //累加
return age;
}
alert(box()); //101
alert(box()); //101,无法实现,因为又被初始化了
~~~
~~~
//通过闭包可以实现局部变量的累加
function box() {
var age = 100;
return function () {
age ++;
return age;
}
}
var b = box(); //获得函数
alert(b()); //调用匿名函数
alert(b()); //第二次调用匿名函数,实现累加
~~~
*PS:由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。*
>作用域链的机制导致一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值。
~~~
//循环里包含匿名函数
function box() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function () {
return i;
};
}
return arr;
}
var b = box(); //得到函数数组
alert(b.length); //得到函数集合长度
for (var i = 0; i < b.length; i++) {
alert(b[i]()); //输出每个函数的值,都是最后一个值
}
~~~
>上面的例子输出的结果都是5,也就是循环后得到的最大的i 值。因为b[i]调用的是匿名函数,匿名函数并没有自我执行,等到调用的时候,box()已执行完毕,i 早已变成5,所以最终的结果就是5 个5。
~~~
//循环里包含匿名函数-改1,自我执行匿名函数
function box() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = (function (num) { //自我执行
return num;
})(i); //并且传参
}
return arr;
}
var b = box();
for (var i = 0; i < b.length; i++) {
alert(b[i]); //这里返回的是数组,直接打印即可
}
~~~
>改1 中,我们让匿名函数进行自我执行,导致最终返回给a[i]的是数组而不是函数了。最终导致b[0]-b[4]中保留了0,1,2,3,4 的值。
~~~
//循环里包含匿名函数-改2,匿名函数下再做个匿名函数
function box() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = (function (num) {
return function () { //直接返回值,改2 变成返回函数
return num; //原理和改1 一样
}
})(i);
}
return arr;
}
var b = box();
for (var i = 0; i < b.length; i++) {
alert(b[i]()); //这里通过b[i]()函数调用即可
}
~~~
>改1 和改2 中,我们通过匿名函数自我执行,立即把结果赋值给a[i]。每一个i,是调用方通过按值传递的,所以最终返回的都是指定的递增的i。而不是box()函数里的i。
>关于this 对象在闭包中使用this对象也可能会导致一些问题,this对象是在运行时基于函数的执行环境绑定的,如果this在全局范围就是window,如果在对象内部就指向这个对象。而闭包却在运行时指向window 的,因为闭包并不属于这个对象的属性或方法。
~~~
var user = 'The Window';
var obj = {
user : 'The Object',
getUserFunction : function () {
return function () { //闭包不属于obj,里面的this 指向window
return this.user;
};
}
};
alert(obj.getUserFunction()()); //The window
//可以强制指向某个对象
alert(obj.getUserFunction().call(obj)); //The Object
//也可以从上一个作用域中得到对象
getUserFunction : function () {
var that = this; //从对象的方法里得对象
return function () {
return that.user;
};
}
~~~
**内存泄漏**
>由于IE 的JScript 对象和DOM对象使用不同的垃圾收集方式,因此闭包在IE中会导致一些问题。就是内存泄漏的问题,也就是无法销毁驻留在内存中的元素。以下代码有两个知识点还没有学习到,一个是DOM,一个是事件。
~~~
function box() {
var oDiv = document.getElementById('oDiv'); //oDiv 用完之后一直驻留在内存
oDiv.onclick = function () {
alert(oDiv.innerHTML); //这里用oDiv 导致内存泄漏
};
}
box();
~~~
>那么在最后应该将oDiv 解除引用来避免内存泄漏。
~~~
function box() {
var oDiv = document.getElementById('oDiv');
var text = oDiv.innerHTML;
oDiv.onclick = function () {
alert(text);
};
oDiv = null; //解除引用
}
~~~
*PS:如果并没有使用解除引用,那么需要等到浏览器关闭才得以释放。*
**模仿块级作用域**
>JavaScript 没有块级作用域的概念。
~~~
function box(count) {
for (var i=0; i<count; i++) {}
alert(i); //i 不会因为离开了for 块就失效
}
box(2);
function box(count) {
for (var i=0; i<count; i++) {}
var i; //就算重新声明,也不会前面的值
alert(i);
}
box(2);
~~~
>以上两个例子,说明JavaScript 没有块级语句的作用域,if () {} for () {}等没有作用域,如果有,出了这个范围i 就应该被销毁了。就算重新声明同一个变量也不会改变它的值。JavaScript 不会提醒你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不见(如果初始化了,当然还会执行的)。使用模仿块级作用域可避免这个问题。
~~~
//模仿块级作用域(私有作用域)
(function () {
//这里是块级作用域
})();
//使用块级作用域(私有作用域)改写
function box(count) {
(function () {
for (var i = 0; i<count; i++) {}
})();
alert(i); //报错,无法访问
}
box(2);
~~~
>使用了块级作用域(私有作用域)后,匿名函数中定义的任何变量,都会在执行结束时被销毁。这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽可能少向全局作用域中添加变量和函数。在大型项目中,多人开发的时候,过多的全局变量和函数很容易导致命名冲突,引起灾难性的后果。如果采用块级作用域(私有作用域),每个开发者既可以使用自己的变量,又不必担心搞乱全局作用域。
~~~
(function () {
var box = [1,2,3,4];
alert(box); //box 出来就不认识了
})();
~~~
>在全局作用域中使用块级作用域可以减少闭包占用的内存问题,因为没有指向匿名函数
的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
**私有变量**
>JavaScript 没有私有属性的概念;所有的对象属性都是公有的。不过,却有一个私有变
量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问
这些变量。
~~~
function box() {
var age = 100; //私有变量,外部无法访问
}
~~~
>而通过函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。而
利用这一点,可以创建用于访问私有变量的公有方法。
~~~
function Box() {
var age = 100; //私有变量
function run() { //私有函数
return '运行中...';
}
this.get = function () { //对外公共的特权方法
return age + run();
};
}
var box = new Box();
alert(box.get());
~~~
>可以通过构造方法传参来访问私有变量。
~~~
function Person(value) {
var user = value; //这句其实可以省略
this.getUser = function () {
return user;
};
this.setUser = function (value) {
user = value;
};
}
~~~
>但是对象的方法,在多次调用的时候,会多次创建。可以使用静态私有变量来避免这个问题。
**静态私有变量**
>通过块级作用域(私有作用域)中定义私有变量或函数,同样可以创建对外公共的特权方
法。
~~~
(function () {
var age = 100;
function run() {
return '运行中...';
}
Box = function () {}; //构造方法
Box.prototype.go = function () { //原型方法
return age + run();
};
})();
var box = new Box();
alert(box.go());
~~~
>上面的对象声明,采用的是Box = function () {} 而不是function Box() {} 因为如果用后面这种,就变成私有函数了,无法在全局访问到了,所以使用了前面这种。
~~~
(function () {
var user = '';
Person = function (value) {
user = value;
};
Person.prototype.getUser = function () {
return user;
};
Person.prototype.setUser = function (value) {
user = value;
}
})();
~~~
>使用了prototype 导致方法共享了,而user 也就变成静态属性了。(所谓静态属性,即共享于不同对象中的属性)。
**模块模式**
>之前采用的都是构造函数的方式来创建私有变量和特权方法。那么对象字面量方式就采用模块模式来创建。
~~~
var box = { //字面量对象,也是单例对象
age : 100, //这是公有属性,将要改成私有
run : function () { //这时公有函数,将要改成私有
return '运行中...';
};
};
私有化变量和函数:
var box = function () {
var age = 100;
function run() {
return '运行中...';
}
return { //直接返回对象
go : function () {
return age + run();
}
};
}();
~~~
>上面的直接返回对象的例子,也可以这么写:
~~~
var box = function () {
var age = 100;
function run() {
return '运行中...';
}
var obj = { //创建字面量对象
go : function () {
return age + run();
}
};
return obj; //返回这个对象
}();
~~~
>字面量的对象声明,其实在设计模式中可以看作是一种单例模式,所谓单例模式,就是永远保持对象的一个实例。
>增强的模块模式,这种模式适合返回自定义对象,也就是构造函数。
~~~
function Desk() {};
var box = function () {
var age = 100;
function run() {
return '运行中...';
}
var desk = new Desk(); //可以实例化特定的对象
desk.go = function () {
return age + run();
};
return desk;
}();
alert(box.go());
~~~
第十三章面向对象与原型
最后更新于:2022-04-01 04:59:09
#第十三章面向对象与原型
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
ECMAScript 有两种开发模式:1.函数式(过程化),2.面向对象(OOP)。面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是,ECMAScript没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
###1.>创建对象
>创建一个对象,然后给这个对象新建属性和方法。
~~~
var box = new Object(); //创建一个Object 对象
box.name = 'Zhang'; //创建一个name 属性并赋值
box.age = 100; //创建一个age 属性并赋值
box.run = function () { //创建一个run()方法并返回值
return this.name + this.age + '运行中...';
};
alert(box.run()); //输出属性和方法的值
~~~
>上面创建了一个对象,并且创建属性和方法,在run()方法里的this,就是代表box对象本身。这种是JavaScript 创建对象最基本的方法,但有个缺点,想创建一个类似的对象,就
会产生大量的代码。
~~~
var box2 = box; //得到box 的引用
box2.name = 'Zero'; //直接改变了name 属性
alert(box2.run()); //用box.run()发现name 也改变了
var box2 = new Object();
box2.name = 'Zero';
box2.age = 200;
box2.run = function () {
return this.name + this.age + '运行中...';
};
alert(box2.run()); //这样才避免和box 混淆,从而保持独立
~~~
>为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法
就是为了解决实例化对象产生大量重复的问题。
~~~
function createObject(name, age) { //集中实例化的函数
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
var box1 = createObject('Zhang', 100); //第一个实例
var box2 = createObject('Zero', 200); //第二个实例
alert(box1.run());
alert(box2.run()); //保持独立
~~~
>工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法
搞清楚他们到底是哪个对象的实例。
alert(typeof box1); //Object
alert(box1 instanceof Object); //true
ECMAScript 中可以采用构造函数(构造方法)可用来创建特定的对象。类型于Object 对
象。
~~~
function Box(name, age) { //构造函数模式
this.name = name;
this.age = age;
this.run = function () {
return this.name + this.age + '运行中...';
};
}
var box1 = new Box('Zhang', 100); //new Box()即可
var box2 = new Box('Zero', 200);
alert(box1.run());
alert(box1 instanceof Box); //很清晰的识别他从属于Box
~~~
使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题是,这里并没有new Object(),为什么可以实例化Box(),这个是哪里来的呢?
使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
1.构造函数方法没有显示的创建对象(new Object());
2.直接将属性和方法赋值给this 对象;
3.没有renturn 语句。
构造函数的方法有一些规范:
1.函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和
普通函数);
2.通过构造函数创建对象,必须使用new 运算符。
既然通过构造函数可以创建对象,那么这个对象是哪里来的,new Object()在什么地方
执行了?执行的过程如下:
1.当使用了构造函数,并且new 构造函数(),那么就后台执行了new Object();
2.将构造函数的作用域给新对象,(即new Object()创建出的对象),而函数体内的this 就
代表new Object()出来的对象。
3.执行构造函数内的代码;
4.返回新对象(后台直接返回)。
关于this 的使用,this 其实就是代表当前作用域对象的引用。如果在全局范围this 就代
表window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。
~~~
var box = 2;
alert(this.box); //全局,代表window
~~~
构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用new 运算符来调用,否则就是普通函数。
~~~
var box = new Box('Zhang', 100); //构造模式调用
alert(box.run());
Box('Zhang', 20); //普通模式调用,无效
var o = new Object();
Box.call(o, 'Zero', 200) //对象冒充调用
alert(o.run());
~~~
探讨构造函数内部的方法(或函数)的问题,首先看下两个实例化后的属性或方法是否相等。
~~~
var box1 = new Box('Zhang', 100); //传递一致
var box2 = new Box('Zhang', 100); //同上
alert(box1.name == box2.name); //true,属性的值相等
alert(box1.run == box2.run); //false,方法其实也是一种引用地址
alert(box1.run() == box2.run()); //true,方法的值相等,因为传参一致
~~~
>可以把构造函数里的方法(或函数)用 new Function()方法来代替,得到一样的效果,更加证明,他们最终判断的是引用地址,唯一性。
~~~
function Box(name, age) { //new Function()唯一性
this.name = name;
this.age = age;
this.run = new Function("return this.name + this.age + '运行中...'");
}
~~~
>我们可以通过构造函数外面绑定同一个函数的方法来保证引用地址的一致性,但这种做法没什么必要,只是加深学习了解:
~~~
function Box(name, age) {
this.name = name;
this.age = age;
this.run = run;
}
function run() { //通过外面调用,保证引用地址一致
return this.name + this.age + '运行中...';
}
~~~
>虽然使用了全局的函数run()来解决了保证引用地址一致的问题,但这种方式又带来了一个新的问题,全局中的this 在对象调用的时候是Box 本身,而当作普通函数调用的时候,this 又代表window。
###2.>原型
>我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype通过调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
~~~
function Box() {} //声明一个构造函数
Box.prototype.name = 'Zhang'; //在原型里添加属性
Box.prototype.age = 100;
Box.prototype.run = function () { //在原型里添加方法
return this.name + this.age + '运行中...';
};
~~~
比较一下原型内的方法地址是否一致:
~~~
var box1 = new Box();
var box2 = new Box();
alert(box1.run == box2.run); //true,方法的引用地址保持一致
~~~
为了更进一步了解构造函数的声明方式和原型模式的声明方式,我们通过图示来了解一下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-10_566915c0b338f.png)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-10_566915c0dbb1a.png)
在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。__proto__
属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor。
通过这两个属性,就可以访问到原型里的属性和方法了。
PS:IE 浏览器在脚本访问__proto__会不能识别,火狐和谷歌浏览器及其他某些浏览器
均能识别。虽然可以输出,但无法获取内部信息。
`alert(box1.__proto__); //[object Object]`
判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。
`alert(Box.prototype.isPrototypeOf(box)); //只要实例化对象,即都会指向`
原型模式的执行流程:
1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原
型中的值。
~~~
var box1 = new Box();
alert(box1.name); //Zhang,原型里的值
box1.name = 'Zero';
alert(box.1name); //Zero,就近原则,
var box2 = new Box();
alert(box2.name); //Zhang,原型里的值,没有被box1 修改
~~~
如果想要box1 也能在后面继续访问到原型里的值,可以把构造函数里的属性删除即可,
具体如下:
~~~
delete box1.name; //删除属性
alert(box1.name);
~~~
如何判断属性是在构造函数的实例里,还是在原型里?可以使用hasOwnProperty()函数
来验证:
`alert(box.hasOwnProperty('name')); //实例里有返回true,否则返回false`
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-10_566915c147d43.png)
**in**操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原
型中。
`alert('name' in box); //true,存在实例中或原型中`
我们可以通过**hasOwnProperty()**方法检测属性是否存在实例中,也可以通过**in**来判断
实例或原型中是否存在属性。那么结合这两种方法,可以判断原型中是否存在属性。
~~~
function isProperty(object, property) { //判断原型中是否存在属性
return !object.hasOwnProperty(property) && (property in object);
}
var box = new Box();
alert(isProperty(box, 'name')) //true,如果原型有
~~~
为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使
用字面量的方式:
~~~
function Box() {};
Box.prototype = { //使用字面量的方式
name : 'Zhang',
age : 100,
run : function () {
return this.name + this.age + '运行中...';
}
};
~~~
使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区
别,字面量创建的方式使用constructor 属性不会指向实例,而会指向Object,构造函数创建
的方式则相反。
~~~
var box = new Box();
alert(box instanceof Box);
alert(box instanceof Object);
alert(box.constructor == Box); //字面量方式,返回false,否则,true
alert(box.constructor == Object); //字面量方式,返回true,否则,false
~~~
如果想让字面量方式的constructor 指向实例对象,那么可以这么做:
~~~
Box.prototype = {
constructor : Box, //直接强制指向即可
};
~~~
PS:字面量方式为什么constructor 会指向Object?因为Box.prototype={};这种写法其实
就是创建了一个新对象。而每创建一个函数,就会同时创建它prototype,这个对象也会自
动获取constructor 属性。所以,新对象的constructor 重写了Box 原来的constructor,因此会
指向新对象,那个新对象没有指定构造函数,那么就默认为Object。
原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。
~~~
function Box() {};
Box.prototype = { //原型被重写了
constructor : Box,
name : 'Zhang',
age : 100,
run : function () {
return this.name + this.age + '运行中...';
}
};
Box.prototype = {
age = 200
};
var box = new Box(); //在这里声明
alert(box.run()); //box 只是最初声明的原型
~~~
原型对象不仅仅可以在自定义对象的情况下使用,而ECMAScript 内置的引用类型都可
以使用这种方式,并且内置的引用类型本身也使用了原型。
~~~
alert(Array.prototype.sort); //sort 就是Array 类型的原型方法
alert(String.prototype.substring); //substring 就是String 类型的原型方法
String.prototype.addstring = function () { //给String 类型添加一个方法
return this + ',被添加了!'; //this 代表调用的字符串
};
alert('Zhang'.addstring()); //使用这个方法
~~~
*PS:尽管给原生的内置引用类型添加方法使用起来特别方便,但我们不推荐使用这种*
*方法。因为它可能会导致命名冲突,不利于代码维护。*
>原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:
~~~
function Box() {};
Box.prototype = {
constructor : Box,
name : 'Zhang',
age : 100,
family : ['父亲', '母亲', '妹妹'], //添加了一个数组属性
run : function () {
return this.name + this.age + this.family;
}
};
var box1 = new Box();
box1.family.push('哥哥'); //在实例中添加'哥哥'
alert(box1.run());
var box2 = new Box();
alert(box2.run()); //共享带来的麻烦,也有'哥哥'了
~~~
*PS:数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化出的数据需要*
*保留自己的特性,而不能共享*。
>为了解决构造传参和共享问题,可以组合构造函数+原型模式:
~~~
function Box(name, age) { //不共享的使用构造函数
this.name = name;
this.age = age;
this. family = ['父亲', '母亲', '妹妹'];
};
Box.prototype = { //共享的使用原型模式
constructor : Box,
run : function () {
return this.name + this.age + this.family;
}
};
~~~
*PS:这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。*
>原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在声明一个对象时,构造函数+原型部分让人感觉又很怪异,最好就是把构造函数和原型封装到一起。为了解决这个问题,我们可以使用动态原型模式。
~~~
function Box(name ,age) { //将所有信息封装到函数体内
this.name = name;
this.age = age;
if (typeof this.run != 'function') { //仅在第一次调用的初始化
Box.prototype.run = function () {
return this.name + this.age + '运行中...';
};
}s
}
var box = new Box('Zhang', 100);
alert(box.run());
~~~
>当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样及得到了封装,又实现了原型方法共享,并且属性都保持独立。
~~~
if (typeof this.run != 'function') {
alert('第一次初始化'); //测试用
Box.prototype.run = function () {
return this.name + this.age + '运行中...';
};
}
var box = new Box('Zhang', 100); //第一次创建对象
alert(box.run()); //第一次调用
alert(box.run()); //第二次调用
var box2 = new Box('Zero', 200); //第二次创建对象
alert(box2.run());
alert(box2.run());
~~~
*PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系。*
以上讲解了各种方式对象创建的方法,如果这几种方式都不能满足需求,可以使用一开始那种模式:寄生构造函数。
function Box(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
>寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接String.prototype.addstring,可以通过寄生构造的方式添加。
~~~
function myString(string) {
var str = new String(string);
str.addstring = function () {
return this + ',被添加了!';
};
return str;
}
var box = new myString('Zhang'); //比直接在引用原型添加要繁琐好多
alert(box.addstring());
~~~
>在一些安全的环境中,比如禁止使用this 和new,这里的this 是构造函数里不使用this,这里的new 是在外部实例化构造函数时不使用new。这种创建方式叫做稳妥构造函数。
~~~
function Box(name , age) {
var obj = new Object();
obj.run = function () {
return name + age + '运行中...'; //直接打印参数即可
};
return obj;
}
var box = Box('Zhang', 100); //直接调用函数
alert(box.run());
~~~
*PS:稳妥构造函数和寄生类似。*
###3.>继承
>继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript 只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。
~~~
function Box() { //Box 构造
this.name = 'Zhang';
}
function Desk() { //Desk 构造
this.age = 100;
}
Desk.prototype = new Box(); //Desc 继承了Box,通过原型,形成链条
var desk = new Desk();
alert(desk.age);
alert(desk.name); //得到被继承的属性
function Table() { //Table 构造
this.level = 'AAAAA';
}
Table.prototype = new Desk(); //继续原型链继承
var table = new Table();
alert(table.name); //继承了Box 和Desk
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-10_566915c178535.png)
如果要实例化table,那么Desk 实例中有age=100,原型中增加相同的属性age=200,最后结果是多少呢?
`Desk.prototype.age = 200; //实例和原型中均包含age`
*PS:以上原型链继承还缺少一环,那就是Obejct,所有的构造函数都继承自Obejct。而继承Object 是自动完成的,并不需要程序员手动继承。*
经过继承后的实例,他们的从属关系会怎样呢?
~~~
alert(table instanceof Object); //true
alert(desk instanceof Table); //false,desk 是table 的超类
alert(table instanceof Desk); //true
alert(table instanceof Box); //true
~~~
在JavaScript 里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的函数称为子类型(子类,派生类)。继承也有之前问题,比如字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。
~~~
function Box(age) {
this.name = ['Zhang', 'Zero', 'Hello']
this.age = age;
}
function Desk(age) {
Box.call(this, age); //对象冒充,给超类型传参
}
var desk = new Desk(200);
alert(desk.age);
alert(desk.name);
desk.name.push('AAA'); //添加的新数据,只给desk
alert(desk.name);
~~~
借用构造函数虽然解决了刚才两种问题,但没有原型,复用则无从谈起。所以,我们需
要原型链+借用构造函数的模式,这种模式成为组合继承。
~~~
function Box(age) {
this.name = ['Zhang', 'Zero', 'Hello']
this.age = age;
}
Box.prototype.run = function () {
return this.name + this.age;
};
function Desk(age) {
Box.call(this, age); //对象冒充
}
Desk.prototype = new Box(); //原型链继承
var desk = new Desk(100);
alert(desk.run());
~~~
>还有一种继承模式叫做:原型式继承;这种继承借助原型并基于已有的对象创建新对象,同时还不必因此创建自定义类型。
~~~
function obj(o) { //传递一个字面量函数
function F() {} //创建一个构造函数
F.prototype = o; //把字面量函数赋值给构造函数的原型
return new F(); //最终返回出实例化的构造函数
}
var box = { //字面量对象
name : 'Zhang',
arr : ['哥哥','妹妹','姐姐']
};
var box1 = obj(box); //传递
alert(box1.name);
box1.name = 'Zero';
alert(box1.name);
alert(box1.arr);
box1.arr.push('父母');
alert(box1.arr);
var box2 = obj(box); //传递
alert(box2.name);
alert(box2.arr); //引用类型共享了
~~~
>寄生式继承把原型式+工厂模式结合而来,目的是为了封装创建对象的过程。
~~~
function create(o) { //封装创建过程
var f= obj(o);
f.run = function () {
return this.arr; //同样,会共享引用
};
return f;
}
~~~
>组合式继承是JavaScript 最常用的继承模式;但,组合式继承也有一点小问题,就是超类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的内部。
~~~
function Box(name) {
this.name = name;
this.arr = ['哥哥','妹妹','父母'];
}
Box.prototype.run = function () {
return this.name;
};
function Desk(name, age) {
Box.call(this, name); //第二次调用Box
this.age = age;
}
Desk.prototype = new Box(); //第一次调用Box
~~~
以上代码是之前的组合继承,那么寄生组合继承,解决了两次调用的问题。
~~~
function obj(o) {
function F() {}
F.prototype = o;
return new F();
}
function create(box, desk) {
var f = obj(box.prototype);
f.constructor = desk;
desk.prototype = f;
}
function Box(name) {
this.name = name;
this.arr = ['哥哥','妹妹','父母'];
}
Box.prototype.run = function () {
return this.name;
};
function Desk(name, age) {
Box.call(this, name);
this.age = age;
}
inPrototype(Box, Desk); //通过这里实现继承
var desk = new Desk('Zhang',100);
desk.arr.push('姐姐');
alert(desk.arr);
alert(desk.run()); //只共享了方法
var desk2 = new Desk('Zero', 200);
alert(desk2.arr); //引用问题解决
~~~
第十二章内置对象
最后更新于:2022-04-01 04:59:07
#第十二章内置对象
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>ECMA-262 对内置对象的定义是:“由ECMAScript实现提供的、不依赖宿主环境的对象,这些对象在ECMAScript程序执行之前就已经存在了。”意思就是说,开发人员不必显示地实例化内置对象;因为它们已经实例化了。ECMA-262 只定义了两个内置对象:Global
和Math。
###1.>Global 对象
>Global(全局)对象是ECMAScript中一个特别的对象,因为这个对象是不存在的。在ECMAScript中不属于任何其他对象的属性和方法,都属于它的属性和方法。所以,事实上,并不存在全局变量和全局函数;所有在全局作用域定义的变量和函数,都是Global对象的属性和方法。
*PS:因为ECMAScript没有定义怎么调用Global对象,所以,Global.属性或者Global.方法()都是无效的。(Web浏览器将Global作为window 对象的一部分加以实现)*
>Global 对象有一些内置的属性和方法:
1.URI 编码方法
>URI 编码可以对链接进行编码,以便发送给浏览器。它们采用特殊的UTF-8 编码替换所有无效字符,从而让浏览器能够接受和理解。encodeURI()不会对本身属于URI的特殊字符进行编码,例如冒号、正斜杠、问号和#号;而encodeURIComponent()则会对它发现的任何非标准字符进行编码
~~~
var box = '//Zhang 张';
alert(encodeURI(box)); //只编码了中文
var box = '//Zhang 张';
alert(encodeURIComponent(box)); //特殊字符和中文编码了
~~~
*PS:因为encodeURIComponent()编码比encodeURI()编码来的更加彻底,一般来说encodeURIComponent()使用频率要高一些。使用了URI 编码过后,还可以进行解码,通过decodeURI()和decodeURIComponent()来进行解码*
~~~
var box = '//Zhang 张';
alert(decodeURI(encodeURI(box))); //还原
var box = '//Zhang 张';
alert(decodeURIComponent(encodeURIComponent(box))); //还原
~~~
*PS:URI 方法如上所述的四种,用于代替已经被ECMA-262 第3 版废弃的escape()和unescape()方法。URI方法能够编码所有的Unicode 字符,而原来的只能正确地编码ASCII字符。所以建议不要再使用escape()和unescape()方法。*
2.eval()方法
>eval()方法主要担当一个字符串解析器的作用,他只接受一个参数,而这个参数就是要执行的JavaScript 代码的字符串。
~~~
eval('var box = 100'); //解析了字符串代码
alert(box);
eval('alert(100)'); //同上
eval('function box() {return 123}'); //函数也可以
alert(box());
~~~
>eval()方法的功能非常强大,但也非常危险。因此使用的时候必须极为谨慎。特别是在用户输入数据的情况下,非常有可能导致程序的安全性,比如代码注入等等。
3.Global 对象属性
>Global 对象包含了一些属性:undefined、NaN、Object、Array、Function 等等。
`alert(Array); //返回构造函数`
4.window 对象
>之前已经说明,Global没有办法直接访问,而Web浏览器可以使用window 对象来实现一全局访问。
`alert(window.Array); //同上`
###2.>Math 对象
>ECMAScript 还为保存数学公式和信息提供了一个对象,即Math 对象。与我们在JavaScript直接编写计算功能相比,Math对象提供的计算功能执行起来要快得多。
1.Math 对象的属性
>Math 对象包含的属性大都是数学计算中可能会用到的一些特殊值。
| 属性|说明 |
| -- | -- |
| Math.E| 自然对数的底数,即常量e 的值 |
| Math.LN10| 10 的自然对数 |
| Math.LN2| 2 的自然对数 |
| Math.LOG2E| 以2 为底e 的对数 |
| Math.LOG10E| 以10 为底e 的对数 |
| Math.PI| Π的值 |
| Math.SQRT1_2| 1/2 的平方根 |
| Math.SQRT2| 2 的平方根 |
~~~
alert(Math.E); //
alert(Math.LN10);
alert(Math.LN2);
alert(Math.LOG2E);
alert(Math.LOG10E);
alert(Math.PI);
alert(Math.SQRT1_2);
alert(Math.SQRT2); //
~~~
2.min()和max()方法
>Math.min()用于确定一组数值中的最小值。Math.max()用于确定一组数值中的最大值。
~~~
alert(Math.min(2,4,3,6,3,8,0,1,3)); //最小值
alert(Math.max(4,7,8,3,1,9,6,0,3,2)); //最大值
~~~
3.舍入方法
>Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;
Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;
Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数;
~~~
alert(Math.ceil(25.9)); //26
alert(Math.ceil(25.5)); //26
alert(Math.ceil(25.1)); //26
alert(Math.floor(25.9)); //25
alert(Math.floor(25.5)); //25
alert(Math.floor(25.1)); //25
alert(Math.round(25.9)); //26
alert(Math.round(25.5)); //26
alert(Math.round(25.1)); //25
~~~
4.random()方法
>Math.random()方法返回介于0 到1 之间一个随机数,不包括0 和1。如果想大于这个范围的话,可以套用一下公式:
>值= Math.floor(Math.random() * 总数+ 第一个值
~~~
alert(Math.floor(Math.random()*10+1));//随机产生1-10之间的任意数
for (var i = 0; i<10;i ++) {
document.write(Math.floor(Math.random() * 10 + 5)); //5-14 之间的任意数
document.write('<br />');
}
~~~
>为了更加方便的传递想要范围,可以写成函数:
~~~
function selectFrom(lower, upper) {
var sum = upper - lower + 1; //总数-第一个数+1
return Math.floor(Math.random() * sum + lower);
}
for (var i=0 ;i<10;i++) {
document.write(selectFrom(5,10)); //直接传递范围即可
document.write('<br />');
}
~~~
5.其他方法
| 方法|说明 |
| -- | -- |
| Math.abs(num)| 返回num 的绝对值 |
| Math.exp(num)| 返回Math.E 的num 次幂|
| Math.log(num)| 返回num 的自然对数|
| Math.pow(num,power)| 返回num 的power 次幂|
| Math.sqrt(num)| 返回num 的平方根 |
| Math.acos(x)| 返回x 的反余弦值 |
| Math.asin(x)| 返回x 的反正弦值 |
| Math.atan(x)| 返回x 的反正切值 |
| Math.atan2(y,x)| 返回y/x 的反正切值|
| Math.cos(x)| 返回x 的余弦值 |
| Math.sin(x)| 返回x 的正弦值 |
| Math.tan(x)| 返回x 的正切值 |
第十一章基本包装类型
最后更新于:2022-04-01 04:59:05
#第十一章基本包装类型
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>为了便于操作基本类型值,ECMAScript提供了3个特殊的引用类型:Boolean、Number和String。这些类型与其他引用类型相似,但同时也具有与各自的基本类型相应的特殊行为。实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而能够调用一些方法来操作这些数据。
###1.>基本包装类型概述
~~~
var box = 'Mr. Zhang'; //定义一个字符串
var box2 = box.substring(2); //截掉字符串前两位
alert(box2); //输出新字符串
~~~
>变量box 是一个字符串类型,而box.substring(2)又说明它是一个对象(PS:只有对象才会调用方法),最后把处理结果赋值给box2。'Mr. Lee'是一个字符串类型的值,按道理它不应该是对象,不应该会有自己的方法,比如:
`alert('Mr. Zhang'.substring(2)); //直接通过值来调用方法`
1.字面量写法:
~~~
var box = 'Mr. Zhang'; //字面量
box.name = 'Zhang'; //无效属性
box.age = function () { //无效方法
return 100;
};
alert(box); //Mr. Zhang
alert(box.substring(2)); //. Zhang
alert(typeof box); //string
alert(box.name); //undefined
alert(box.age()); //错误
~~~
2.new 运算符写法:
~~~
var box = new String('Mr. Zhang'); //new 运算符
box.name = 'Zhang'; //有效属性
box.age = function () { //有效方法
return 100;
};
alert(box); //Mr. Zhang
alert(box.substring(2)); //. Zhang
alert(typeof box); //object
alert(box.name); //Zhang
alert(box.age()); //100
~~~
>以上字面量声明和new运算符声明很好的展示了他们之间的区别。但有一定还是可以肯定的,那就是不管字面量形式还是new运算符形式,都可以使用它的内置方法。并且Boolean 和Number 特性与String 相同,三种类型可以成为基本包装类型。
*PS:在使用new 运算符创建以上三种类型的对象时,可以给自己添加属性和方法,但我们建议不要这样使用,因为这样会导致根本分不清到底是基本类型值还是引用类型值*。
###2.>Boolean 类型
>Boolean 类型没有特定的属性或者方法。
###3.>Number 类型
>Number 类型有一些静态属性(直接通过Number调用的属性,而无须new 运算符)和方法。
**Number 静态属性**
| 属性 |描述 |
| -- | -- |
| MAX_VALUE| 表示最大数 |
| MIN_VALUE| 表示最小值 |
| NaN| 非数值 |
| NEGATIVE_INFINITY| 负无穷大,溢出返回该值 |
| POSITIVE_INFINITY| 无穷大,溢出返回该值 |
| prototype| 原型,用于增加新属性和方法 |
**Number 对象的方法**
| 方法|描述 |
| -- | -- |
| toString()| 将数值转化为字符串,并且可以转换进制 |
| toLocaleString()| 根据本地数字格式转换为字符串 |
| toFixed()| 将数字保留小数点后指定位数并转化为字符串 |
| toExponential()|将数字以指数形式表示,保留小数点后指定位数并转化为字符串 |
| toPrecision()|指数形式或点形式表述数,保留小数点后面指定位数并转化为字符串 |
~~~
var box = 1000.789;
alert(box.toString()); //转换为字符串,传参可以转换进制
alert(box.toLocaleString()); //本地形式,1,000.789
alert(box.toFixed(2)); //小数点保留,1000.78
alert(box.toExponential()); //指数形式,传参会保留小数点
alert(box.toPrecision(3)); //指数或点形式,传参保留小数点
~~~
###4.>String 类型
>String 类型包含了三个属性和大量的可用内置方法。
**String对象属性**
| 属性|描述|
| -- | -- |
| length| 返回字符串的字符长度 |
| constructor| 返回创建String 对象的函数 |
| prototype| 通过添加属性和方法扩展字符串定义 |
>String 也包含对象的通用方法,比如valueOf()、toLocaleString()和toString()方法,但这些方法都返回字符串的基本值。
**字符方法**
| 方法|描述 |
| -- | -- |
| charAt(n)| 返回指定索引位置的字符 |
| charCodeAt(n)| 以Unicode 编码形式返回指定索引位置的字符|
~~~
var box = 'Mr.Zhang';
alert(box.charAt(1)); //r
alert(box.charCodeAt(1)); //114
alert(box[1]); //r,通过数组方式截取
~~~
*PS:box[1]在IE 浏览器会显示undefined,所以使用时要慎重。*
**字符串操作方法**
| 方法|描述 |
| -- | -- |
| concat(str1...str2)| 将字符串参数串联到调用该方法的字符串|
| slice(n,m)| 返回字符串n 到m 之间位置的字符串 |
| substring(n,m)| 同上 |
| substr(n,m)| 返回字符串n 开始的m 个字符串 |
~~~
var box = 'Mr.Zhang';
alert(box.concat(' is ', ' Teacher ', '!')); //Mr.Zhang is Teacher !
alert(box.slice(3)); //Zhang
alert(box.slice(3,5)); //Zh
alert(box.substring(3)); //Zhang
alert(box.substring(3,5)); //Zh
alert(box.substr(3)); //Zhang
alert(box.substr(3,5)); //Zhang
var box = 'Mr.Zhang';
alert(box.slice(-3)); //ang,6+(-3)=3 位开始
alert(box.substring(-3)); //Mr.Zhang 负数返回全部
alert(box.substr(-3)); //ang,6+(-3)=3 位开始
var box = 'Mr.Zhang';
alert(box.slice(3, -1)); //Zhan 6+(-1)=5, (3,5)
alert(box.substring(3,-1));//Mr.第二参为负,直接转0,并且方法会把较小的数字提前,(0,3)
alert(box.substr(3, -1)); //'' 第二参数为负,直接转0 ,(3,0)
~~~
*PS:IE 的JavaScript实现在处理向substr()方法传递负值的情况下存在问题,它会返回原始字符串,使用时要切记。*
**字符串位置方法**
| 方法|描述 |
| -- | -- |
| indexOf(str, n)|从n开始搜索的第一个str,并将搜索的索引值返回|
| lastIndexOf(str,n)|从n开始搜索的最后一个str,并将搜索的索引值返回 |
~~~
alert(box.indexOf('Z')); //3
alert(box.indexOf('Z', 5)); //10
alert(box.lastIndexOf('Z')); //10
alert(box.lastIndexOf('Z', 5)); //3,从指定的位置向前搜索
~~~
*PS:如果没有找到想要的字符串,则返回-1。*
>示例:找出全部的L
~~~
var box = 'Mr.Zhang is Zhang'; //包含两个Z 的字符串
var boxarr = []; //存放Z 位置的数组
var pos = box.indexOf('Z'); //先获取第一个Z 的位置
while (pos > -1) { //如果位置大于-1,说明还存在Z
boxarr.push(pos); //添加到数组
pos = box.indexOf('Z', pos + 1); //从新赋值pos 目前的位置
}
alert(boxarr); //输出
~~~
**大小写转换方法**
| 方法|描述 |
| -- | -- |
| toLowerCase(str)| 将字符串全部转换为小写 |
| toUpperCase(str)| 将字符串全部转换为大写 |
|toLocaleLowerCase(str)|将字符串全部转换为小写,并且本地化 |
|toLocaleupperCase(str)|将字符串全部转换为大写,并且本地化 |
~~~
var box = 'Mr.Zhang is Zhang';
alert(box.toLowerCase()); //全部小写
alert(box.toUpperCase()); //全部大写
alert(box.toLocaleLowerCase()); //
alert(box.toLocaleUpperCase()); //
~~~
*PS:只有几种语言(如土耳其语)具有地方特有的大小写本地性,一般来说,是否本地化效果都是一致的。*
**字符串的模式匹配方法**
| 方法 |描述 |
| -- | -- |
| match(pattern) | 返回pattern 中的子串或null |
| replace(pattern, replacement)| 用replacement 替换pattern|
| search(pattern) | 返回字符串中pattern 开始位置 |
| split(pattern) | 返回字符串按指定pattern 拆分的数组 |
>正则表达式在字符串中的应用,在前面的章节已经详细探讨过,这里就不再赘述了。以上中match()、replace()、serach()、split()在普通字符串中也可以使用。
~~~
var box = 'Mr.Zhang is Zhang';
alert(box.match('Z')); //找到Z,返回L 否则返回null
alert(box.search('Z')); //找到Z 的位置,和indexOf 类型
alert(box.replace('Z', 'Q')); //把Z 替换成Q
alert(box.split(' ')); //以空格分割成字符串
~~~
**其他方法**
| 方法|描述 |
| -- | -- |
| fromCharCode(ascii)| 静态方法,输出Ascii 码对应值 |
| localeCompare(str1,str2)| 比较两个字符串,并返回相应的值|
`alert(String.fromCharCode(76)); //L,输出Ascii 码对应值`
>localeCompare(str1,str2)方法详解:比较两个字符串并返回以下值中的一个:
1.如果字符串在字母表中应该排在字符串参数之前,则返回一个负数。(多数-1)
2.如果字符串等于字符串参数,则返回0。
3.如果字符串在自附表中应该排在字符串参数之后,则返回一个正数。(多数1)
~~~
var box = 'Zhang';
alert(box.localeCompare('apple')); //1
alert(box.localeCompare('Zhang')); //0
alert(box.localeCompare('zoo')); //-1
~~~
第十章变量、作用域及内存
最后更新于:2022-04-01 04:59:02
#第十章变量、作用域及内存
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>JavaScript 的变量与其他语言的变量有很大区别。JavaScript变量是松散型的(不强制类型)本质,决定了它只是在特定时间用于保存特定值的一个名字而已。由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变。
###1.>变量及作用域
1.基本类型和引用类型的值
>ECMAScript 变量可能包含两种不同的数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值则是指那些保存在堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
>将一个值赋给变量时,解析器必须确定这个值是基本类型值,还是引用类型值。基本类型值有以下几种:Undefined、Null、Boolean、Number 和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。
*PS:在某些语言中,字符串以对象的形式来表示,因此被认为是引用类型。ECMAScript放弃这一传统。*
>如果赋值的是引用类型的值,则必须在堆内存中为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址大小的固定的,因此可以将内存地址保存在栈内存中。这样,当查询引用类型的变量时,先从栈中读取内存地址,然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-27_562f33aeeb68f.png)
2.动态属性
>定义基本类型值和引用类型值的方式是相似的:创建一个变量并为该变量赋值。但是,当这个值保存到变量中以后,对不同类型值可以执行的操作则大相径庭。
~~~
var box = new Object(); //创建引用类型
box.name = 'Zhang'; //新增一个属性
alert(box.name); //输出
~~~
>如果是基本类型的值添加属性的话,就会出现问题了。
~~~
var box = 'Zhang'; //创建一个基本类型
box.age = 26; //给基本类型添加属性
alert(box.age); //undefined
~~~
3.复制变量值
>在变量复制方面,基本类型和引用类型也有所不同。基本类型复制的是值本身,而引用类型复制的是地址。
~~~
var box = 'Zhang'; //在栈内存生成一个box 'Zhang'
var box2 = box; //在栈内存再生成一个box2 'Zhang'
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-27_562f33af08c85.png)
>box2 是虽然是box1的一个副本,但从图示可以看出,它是完全独立的。也就是说,两个变量分别操作时互不影响。
~~~
var box = new Object(); //创建一个引用类型
box.name = 'Zhang'; //新增一个属性
var box2 = box; //把引用地址赋值给box2
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-27_562f33af17d58.png)
>在引用类型中,box2其实就是box,因为他们指向的是同一个对象。如果这个对象中的name属性被修改了,box2.name和box.name输出的值都会被相应修改掉了。
4.传递参数
>ECMAScript 中所有函数的参数都是按值传递的,言下之意就是说,参数不会按引用传递,虽然变量有基本类型和引用类型之分。
~~~
function box(num) { //按值传递,传递的参数是基本类型
num += 10; //这里的num 是局部变量,全局无效
return num;
}
var num = 50;
var result = box(num);
alert(result); //60
alert(num); //50
~~~
*PS:以上的代码中,传递的参数是一个基本类型的值。而函数里的num 是一个局部变量,和外面的num 没有任何联系。*
>下面给出一个参数作为引用类型的例子。
~~~
function box(obj) { //按值传递,传递的参数是引用类型
obj.name = 'Zhang';
}
var p = new Object();
box(p);
alert(p.name);
~~~
*PS:如果存在按引用传递的话,那么函数里的那个变量将会是全局变量,在外部也可以访问。比如PHP中,必须在参数前面加上&符号表示按引用传递。而ECMAScript 没有这些,只能是局部变量。可以在PHP 中了解一下。*
*PS:所以按引用传递和传递引用类型是两个不同的概念。*
~~~
function box(obj) {
obj.name = 'Zhang';
var obj = new Object(); //函数内部又创建了一个对象
obj.name = 'Mr.'; //并没有替换掉原来的obj
}
~~~
>最后得出结论,ECMAScript函数的参数都将是局部变量,也就是说,没有按引用传递。
5.检测类型
>要检测一个变量的类型,我们可以通过typeof运算符来判别。诸如:
~~~
var box = 'Zhang';
alert(typeof box); //string
~~~
>虽然typeof 运算符在检查基本数据类型的时候非常好用,但检测引用类型的时候,它就不是那么好用了。通常,我们并不想知道它是不是对象,而是想知道它到底是什么类型的对象。因为数组也是object,null 也是Object 等等。这时我们应该采用instanceof 运算符来查看。
~~~
var box = [1,2,3];
alert(box instanceof Array); //是否是数组
var box2 = {};
alert(box2 instanceof Object); //是否是对象
var box3 = /g/;
alert(box3 instanceof RegExp); //是否是正则表达式
var box4 = new String('Zhang');
alert(box4 instanceof String); //是否是字符串对象
~~~
*PS:当使用instanceof 检查基本类型的值时,它会返回false。*
5.执行环境及作用域
>执行环境是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。全局执行环境是最外围的执行环境。在Web浏览器中,全局执行环境被认为是window对象。因此所有的全局变量和函数都是作为window对象的属性和方法创建的。
~~~
var box = 'blue'; //声明一个全局变量
function setBox() {
alert(box); //全局变量可以在函数里访问
}
setBox(); //执行函数
~~~
>全局的变量和函数,都是window 对象的属性和方法。
~~~
var box = 'blue';
function setBox() {
alert(window.box); //全局变量即window 的属性
}
window.setBox(); //全局函数即window 的方法
~~~
*PS:当执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。如果是全局环境下,需要程序执行完毕,或者网页被关闭才会销毁。*
*PS:每个执行环境都有一个与之关联的变量对象,就好比全局的window 可以调用变量和属性一样。局部的环境也有一个类似window的变量对象,环境中定义的所有变量和函数都保存在这个对象中。(我们无法访问这个变量对象,但解析器会处理数据时后台使用它)*
>函数里的局部作用域里的变量替换全局变量,但作用域仅限在函数体内这个局部环境。
~~~
var box = 'blue';
function setBox() {
var box = 'red'; //这里是局部变量,出来就不认识了
alert(box);
}
setBox();
alert(box);
~~~
>通过传参,可以替换函数体内的局部变量,但作用域仅限在函数体内这个局部环境。
~~~
var box = 'blue';
function setBox(box) { //通过传参,替换了全局变量
alert(box);
}
setBox('red');
alert(box);
~~~
>函数体内还包含着函数,只有这个函数才可以访问内一层的函数。
~~~
var box = 'blue';
function setBox() {
function setColor() {
var b = 'orange';
alert(box);
alert(b);
}
setColor(); //setColor()的执行环境在setBox()内
}
setBox();
~~~
*PS:每个函数被调用时都会创建自己的执行环境。当执行到这个函数时,函数的环境就会被推到环境栈中去执行,而执行后又在环境栈中弹出(退出),把控制权交给上一级的执行环境。*
*PS:当代码在一个环境中执行时,就会形成一种叫做作用域链的东西。它的用途是保证对执行环境中有访问权限的变量和函数进行有序访问。作用域链的前端,就是执行环境的变量对象。*
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-27_562f33af22ea1.png)
6.没有块级作用域
块级作用域表示诸如if语句等有花括号封闭的代码块,所以,支持条件判断来定义变量。
~~~
if (true) { //if 语句代码块没有局部作用域
var box = 'Zhang';
}
alert(box);
~~~
>for 循环语句也是如此
~~~
for (var i = 0; i < 10; i ++) { //没有局部作用域
var box = 'Zhang';
}
alert(i);
alert(box);
~~~
>var 关键字在函数里的区别
~~~
function box(num1, num2) {
var sum = num1 + num2; //如果去掉var 就是全局变量了
return sum;
}
alert(box(10,10));
alert(sum); //报错
~~~
*PS:非常不建议不使用var就初始化变量,因为这种方法会导致各种意外发生。所以初始化变量的时候一定要加上var。一般确定变量都是通过搜索来确定该标识符实际代表什么。*
~~~
var box = 'blue';
function getBox() {
return box; //代表全局box
} //如果加上函数体内加上var box = 'red'
alert(getBox()); //那么最后返回值就是red
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-27_562f33af3dc2e.png)
*PS:变量查询中,访问局部变量要比全局变量更快,因为不需要向上搜索作用域链。*
###2.>内存问题
>JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。其他语言比如C和C++,必须手工跟踪内存使用情况,适时的释放,否则会造成很多问题。而JavaScript 则不需要这样,它会自行管理内存分配及无用内存的回收。
>JavaScript 最常用的垃圾收集方式是标记清除。垃圾收集器会在运行的时候给存储在内存中的变量加上标记。然后,它会去掉环境中正在使用变量的标记,而没有被去掉标记的变量将被视为准备删除的变量。最后,垃圾收集器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。
>垃圾收集器是周期性运行的,这样会导致整个程序的性能问题。比如IE7 以前的版本,它的垃圾收集器是根据内存分配量运行的,比如256 个变量就开始运行垃圾收集器,这样,就不得不频繁地运行,从而降低的性能。
>一般来说,确保占用最少的内存可以让页面获得更好的性能。那么优化内存的最佳方案,就是一旦数据不再有用,那么将其设置为null 来释放引用,这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象。
~~~
var o = {
name : 'Zhang'
};
o = null; //解除对象引用,等待垃圾收集器回收感
~~~
第九章Function类型
最后更新于:2022-04-01 04:59:00
#第九章Function类型
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>在ECMAScript 中,Function(函数)类型实际上是对象。每个函数都是Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针。
###1.>函数的声明方式
1.普通的函数声明
~~~
function box(num1, num2) {
return num1+ num2;
}
~~~
2.使用变量初始化函数
~~~
var box= function(num1, num2) {
return num1 + num2;
};
~~~
3.使用Function 构造函数
`var box= new Function('num1', 'num2' ,'return num1 + num2');`
*PS:第三种方式我们不推荐,因为这种语法会导致解析两次代码(第一次解析常规ECMAScript代码,第二次是解析传入构造函数中的字符串),从而影响性能。但我们可以通过这种语法来理解"函数是对象,函数名是指针"的概念。*
###2.>作为值的函数
>ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。
~~~
function box(sumFunction, num) {
return sumFunction(num); //someFunction
}
function sum(num) {
return num + 10;
}
var result = box(sum, 10); //传递函数到另一个函数里
~~~
###3.>函数的内部属性
>在函数内部,有两个特殊的对象:arguments 和this。arguments 是一个类数组对象,包含着传入函数中的所有参数,主要用途是保存函数参数。但这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments 对象的函数。
~~~
function box(num) {
if (num <= 1) {
return 1;
} else {
return num * box(num-1); //一个简单的的递归
}
}
~~~
>对于阶乘函数一般要用到递归算法,所以函数内部一定会调用自身;如果函数名不改变是没有问题的,但一旦改变函数名,内部的自身调用需要逐一修改。为了解决这个问题,我们可以使用arguments.callee 来代替。
~~~
function box(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num-1);//使用callee 来执行自身
}
}
~~~
>函数内部另一个特殊对象是this,其行为与Java和C#中的this大致相似。换句话说,this引用的是函数据以执行操作的对象,或者说函数调用语句所处的那个作用域。
*PS:当在全局作用域中调用函数时,this 对象引用的就是window。*
~~~
//便于理解的改写例子
window.color = '红色的'; //全局的,或者var color = '红色的';也行
alert(this.color); //打印全局的color
var box = {
color : '蓝色的', //局部的color
sayColor : function () {
alert(this.color); //此时的this 只能box 里的color
}
};
box.sayColor(); //打印局部的color
alert(this.color); //还是全局的
//引用教材的原版例子
window.color = '红色的'; //或者var color = '红色的';也行
var box = {
color : '蓝色的'
};
function sayColor() {
alert(this.color); //这里第一次在外面,第二次在box 里面
}
getColor();
box.sayColor = sayColor; //把函数复制到box 对象里,成为了方法
box.sayColor();
~~~
###4.>函数属性和方法
>ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length和prototype。其中,length属性表示函数希望接收的命名参数的个数。
~~~
function box(name, age) {
alert(name + age);
}
alert(box.length); //2
~~~
*PS:对于prototype属性,它是保存所有实例方法的真正所在,也就是原型。这个属性,我们将在面向对象一章详细介绍。而prototype 下有两个方法:apply()和call(),每个函数都包含这两个非继承而来的方法。这两个方法的用途都在特定的作用域中调用函数,实际上等于设置函数体内this 对象的值*。
~~~
function box(num1, num2) {
return num1 + num2; //原函数
}
function sayBox(num1, num2) {
return box.apply(this,[num1,num2]);//this表示作用域,这里是window
} //[]表示box 所需要的参数
function sayBox2(num1, num2) {
return box.apply(this, arguments); //arguments 对象表示box 所需要的参数
}
alert(sayBox(10,10)); //20
alert(sayBox2(10,10)); //20
~~~
call()方法于apply()方法相同,他们的区别仅仅在于接收参数的方式不同。对于call()方
法而言,第一个参数是作用域,没有变化,变化只是其余的参数都是直接传递给函数的。
~~~
function box(num1, num2) {
return num1 + num2;
}
function callBox(num1, num2) {
return box.call(this, num1,num2);//和apply区别在于后面的传参
}
alert(callBox(10,10));
~~~
事实上,传递参数并不是apply()和call()方法真正的用武之地;它们经常使用的地方是
能够扩展函数赖以运行的作用域。
~~~
var color = '红色的'; //或者window.color = '红色的';也行
var box = {
color : '蓝色的'
};
function sayColor() {
alert(this.color);
}
sayColor(); //作用域在window
sayColor.call(this); //作用域在window
sayColor.call(window); //作用域在window
sayColor.call(box); //作用域在box,对象冒充
~~~
>这个例子是之前作用域理解的例子修改而成,我们可以发现当我们使用call(box)方法的时候,sayColor()方法的运行环境已经变成了box 对象里了。
使用call()或者apply()来扩充作用域的最大好处,就是对象不需要与方法发生任何耦合关系(耦合,就是互相关联的意思,扩展和维护会发生连锁反应)。也就是说,box对象和sayColor()方法之间不会有多余的关联操作,比如box.sayColor = sayColor;
第八章对象和数组
最后更新于:2022-04-01 04:58:58
#第八章对象与数组
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>什么是对象,其实就是一种类型,即引用类型。而对象的值就是引用类型的实例。在ECMAScript中引用类型是一种数据结构,用于将数据和功能组织在一起。它也常被称做为类,但ECMAScript中却没有这种东西。虽然ECMAScript是一门面向对象的语言,却不具备传统面向对象语言所支持的类和接口等基本结构。
###1.>Object 类型
到目前为止,我们使用的引用类型最多的可能就是Object 类型了。虽然Object 的实例
不具备多少功能,但对于在应用程序中的存储和传输数据而言,它确实是非常理想的选择。
创建Object 类型有两种。一种是使用new 运算符,一种是字面量表示法。
1.使用new 运算符创建Object
var box = new Object(); //new 方式
box.name = '无脑码农'; //创建属性字段
box.age = 30; //创建属性字段
2.new 关键字可以省略
var box = Object(); //省略了new 关键字
3.使用字面量方式创建Object
var box = { //字面量方式
name : '无脑码农', //创建属性字段
age : 30
};
4.属性字段也可以使用字符串星矢
var box = {
'name' : '无脑码农', //也可以用字符串形式
'age' : 30
};
5.使用字面量及传统复制方式
var box = {}; //字面量方式声明空的对象
box.name = '无脑码农'; //点符号给属性复制
box.age = 30;
6.两种属性输出方式
alert(box.age); //点表示法输出
alert(box['age']); //中括号表示法输出,注意引号
PS:在使用字面量声明Object 对象时,不会调用Object()构造函数(Firefox 除外)。
7.给对象创建方法
var box = {
run : function () { //对象中的方法
return '运行';
}
}
alert(box.run()); //调用对象中的方法
8.使用delete 删除对象属性
delete box.name; //删除属性
在实际开发过程中,一般我们更加喜欢字面量的声明方式。因为它清晰,语法代码少,
而且还给人一种封装的感觉。字面量也是向函数传递大量可选参数的首选方式。
function box(obj) { //参数是一个对象
if (obj.name != undefined) alert(obj.name); //判断属性是否存在
if (obj.age != undefined) alert(obj.age);
}
box({ //调用函数传递一个对象
name : '无脑码农',
age : 30
});
###2.>Array 类型
除了Object 类型之外,Array 类型是ECMAScript 最常用的类型。而且ECMAScript 中
的Array 类型和其他语言中的数组有着很大的区别。虽然数组都是有序排列,但ECMAScript
中的数组每个元素可以保存任何类型。ECMAScript 中数组的大小也是可以调整的。
创建Array 类型有两种方式:第一种是new 运算符,第二种是字面量。
1.使用new 关键字创建数组
var box = new Array(); //创建了一个数组
var box = new Array(10); //创建一个包含10 个元素的数组
var box = new Array('无脑码农',30,'讲师','北京'); //创建一个数组并分配好了元素
2.以上三种方法,可以省略new 关键字。
var box = Array(); //省略了new 关键字
3 使用字面量方式创建数组
var box = []; //创建一个空的数组
var box = ['无脑码农',30,'讲师','北京'];//创建包含元素的数组
var box = [1,2,]; //禁止这么做,IE 会识别3 个元素
var box = [,,,,,]; //同样,IE 的会有识别问题
PS:和Object 一样,字面量的写法不会调用Array()构造函数。(Firefox 除外)。
4.使用索引下标来读取数组的值
alert(box[2]); //获取第三个元素
box[2] = '员工'; //修改第三个元素
box[4] = '游戏开发'; //增加第五个元素
5.使用length 属性获取数组元素量
alert(box.length) //获取元素个数
box.length = 10; //强制元素个数
box[box.length] = 'JS 技术'; //通过length 给数组增加一个元素
6.创建一个稍微复杂一点的数组
var box = [
{ //第一个元素是一个对象
name : '无脑码农',
age : 30,
run : function () {
return 'run 了';
}
},
['沈大海','无脑码农',new Object()],//第二个元素是数组
'北京', //第三个元素是字符串
25+25, //第四个元素是数值
new Array(1,2,3) //第五个元素是数组
];
alert(box);
PS:数组最多可包含4294967295 个元素,超出即会发生异常。
###3.>对象中的方法
对象或数组都具有toLocaleString()、toString()和valueOf()方法。其中toString()和valueOf()
无论重写了谁,都会返回相同的值。数组会讲每个值进行字符串形式的拼接,以逗号隔开。
var box = ['无脑码农',30,'游戏开发']; //字面量数组
alert(box); //隐式调用了toString()
alert(box.toString()); //和valueOf()返回一致
alert(box.toLocaleString()); //返回值和上面两种一致
默认情况下,数组字符串都会以逗号隔开。如果使用join()方法,则可以使用不同的分
隔符来构建这个字符串。
var box = ['无脑码农', 30, '游戏开发'];
alert(box.join('|')); //无脑码农|30|游戏开发
栈方法
ECMAScript 数组提供了一种让数组的行为类似于其他数据结构的方法。也就是说,可
以让数组像栈一样,可以限制插入和删除项的数据结构。栈是一种数据结构(后进先出),也
就是说最新添加的元素最早被移除。而栈中元素的插入(或叫推入)和移除(或叫弹出),只发
生在一个位置——栈的顶部。ECMAScript 为数组专门提供了push()和pop()方法。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-27_562f33ad30d76.png)
push()方法可以接收任意数量的参数,把它们逐个添加到数组的末尾,并返回修改后数
组的长度。而pop()方法则从数组末尾移除最后一个元素,减少数组的length 值,然后返回
移除的元素。
var box = ['无脑码农', 30, '游戏开发']; //字面量声明
alert(box.push('北京'));//数组末尾添加一个元素,并且返回长度
alert(box); //查看数组
box.pop(); //移除数组末尾元素,并返回移除的元素
alert(box); //查看元素
队列方法
栈方法是后进先出,而列队方法就是先进先出。列队在数组的末端添加元素,从数组的
前端移除元素。通过push()向数组末端添加一个元素,然后通过shift()方法从数组前端移除一个元素。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-27_562f33ad4164e.png)
var box = ['无脑码农', 30, '游戏开发']; //字面量声明
alert(box.push('北京')); //数组末尾添加一个元素,并且返回长度
alert(box); //查看数组
alert(box.shift()); //移除数组开头元素,并返回移除的元素
alert(box); //查看数组
ECMAScript 还为数组提供了一个unshift()方法,它和shift()方法的功能完全相反。
unshift()方法为数组的前端添加一个元素。
var box = ['无脑码农', 30, '游戏开发']; //字面量声明
alert(box.unshift('北京','朝阳')); //数组开头添加两个元素
alert(box); //查看数组
alert(box.pop()); //移除数组末尾元素,并返回移除的元素
alert(box); //查看数组
PS:IE 浏览器对unshift()方法总是返回undefined 而不是数组的新长度。
重排序方法
数组中已经存在两个可以直接用来排序的方法:reverse()和sort()。
reverse() 逆向排序
var box = [1,2,3,4,5]; //数组
alert(box.reverse()); //逆向排序方法,返回排序后的数组
alert(box); //源数组也被逆向排序了,说明是引用
sort() 从小到大排序
var box = [4,1,7,3,9,2]; //数组
alert(box.sort()); //从小到大排序,返回排序后的数组
alert(box); //源数组也被从小到大排序了
sort 方法的默认排序在数字排序上有些问题,因为数字排序和数字字符串排序的算法是
一样的。我们必须修改这一特征,修改的方式,就是给sort(参数)方法传递一个函数参数。
这点可以参考手册说明。
function compare(value1, value2) { //数字排序的函数参数
if (value1 < value2) { //小于,返回负数
return -1;
} else if (value1 > value2) { //大于,返回正数
return 1;
} else { //其他,返回0
return 0;
}
var box = [0,1,5,10,15]; //验证数字字符串,和数字的区别
alert(box.sort(compare)); //传参
PS:如果要反向操作,即从大到小排序,正负颠倒即可。当然,如果要逆序用reverse()
更加方便。
操作方法
ECMAScript 为操作已经包含在数组中的元素提供了很多方法。concat()方法可以基于当
前数组创建一个新数组。slice()方法可以基于当前数组获取指定区域元素并创建一个新数组。
splice()主要用途是向数组的中部插入元素。
var box = ['无脑码农', 30, '北京']; //当前数组
var box2 = box.concat('游戏开发'); //创建新数组,并添加新元素
alert(box2); //输出新数组
alert(box); //当前数组没有任何变化
var box = ['无脑码农', 30, '北京']; //当前数组
var box2 = box.slice(1); //box.slice(1,3),2-4 之间的元素
alert(box2); //30,北京
alert(box); //当前数组
splice 中的删除功能:
var box = ['无脑码农', 30, '北京']; //当前数组
var box2 = box.splice(0,2); //截取前两个元素
alert(box2); //返回截取的元素
alert(box); //当前数组被截取的元素被删除
splice 中的插入功能:
var box = ['无脑码农', 30, '北京']; //当前数组
var box2 = box.splice(1,0,'游戏开发','朝阳'); //没有截取,但插入了两条
alert(box2); //在第2 个位置插入两条
alert(box); //输出
splice 中的替换功能:
var box = ['无脑码农', 30, '北京']; //当前数组
var box2 = box.splice(1,1,100); //截取了第2 条,替换成100
alert(box2); //输出截取的30
alert(box); //输出数组
第七章函数
最后更新于:2022-04-01 04:58:56
#第七章函数
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>函数是定义一次但却可以调用或执行任意多次的一段JS 代码。函数有时会有参数,即函数被调用时指定了值的局部变量。函数常常使用这些参数来计算一个返回值,这个值也成为函数调用表达式的值。
###1.>函数声明
>在任何地方、任何时候调用执行。ECMAScript中的函数使用function 关键字来声明,后跟一组参数以及函数体。
~~~
function box() { //没有参数的函数
alert('只有函数被调用,我才会被之执行');
}
box(); //直接调用函数
function box(name, age) { //带参数的函数
alert('你的姓名:'+name+',年龄:'+age);
}
box('无脑码农',28); //调用函数,并传参
~~~
###2.>return 返回值
>带参和不带参的函数,都没有定义返回值,而是调用后直接执行的。实际上,任何函数都可以通过return语句跟后面的要返回的值来实现返回值。
~~~
function box() { //没有参数的函数
return '我被返回了!'; //通过return 把函数的最终值返回
}
alert(box()); //调用函数会得到返回值,然后外面输出
function box(name, age) { //有参数的函数
return '你的姓名:'+name+',年龄:'+age;//通过return 把函数的最终值返回
}
alert(box('无脑码农',28));//调用函数得到返回值,然后外面输出
~~~
>我们还可以把函数的返回值赋给一个变量,然后通过变量进行操作。
~~~
function box(num1, num2) {
return num1 * num2;
}
var num = box(10, 5); //函数得到的返回值赋给变量
alert(num);
~~~
>return 语句还有一个功能就是退出当前函数,注意和break 的区别。PS:break 用在循环和switch 分支语句里。
~~~
function box(num) {
if (num < 5) return num; //满足条件,就返回num
return 100; //返回之后,就不执行下面的语句了
}
alert(box(10));
~~~
###3.>arguments 对象
>ECMAScript 函数不介意传递进来多少参数,也不会因为参数不统一而错误。实际上,函数体内可以通过arguments对象来接收传递进来的参数。
~~~
function box() {
return arguments[0]+' | '+arguments[1]; //得到每次参数的值
}
alert(box(1,2,3,4,5,6)); //传递参数
arguments 对象的length 属性可以得到参数的数量。
function box() {
return arguments.length; //得到6
}
alert(box(1,2,3,4,5,6));
~~~
>我们可以利用length这个属性,来智能的判断有多少参数,然后把参数进行合理的应用。比如,要实现一个加法运算,将所有传进来的数字累加,而数字的个数又不确定。
~~~
function box() {
var sum = 0;
if (arguments.length == 0) return sum; //如果没有参数,退出
for(var i = 0;i < arguments.length; i++) { //如果有,就累加
sum = sum + arguments[i];
}
return sum; //返回累加结果
}
alert(box(5,9,12));
~~~
>ECMAScript 中的函数,没有像其他高级语言那种函数重载功能。
~~~
function box(num) {
return num + 100;
}
function box (num) { //会执行这个函数
return num + 200;
}
alert(box(50)); //返回结果
~~~
第六章流程控制语句
最后更新于:2022-04-01 04:58:53
#第六章流程控制语句
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>ECMA-262 规定了一组流程控制语句。语句定义了ECMAScript 中的主要语法,语句通
常由一个或者多个关键字来完成给定的任务。诸如:判断、循环、退出等。
###1.>语句的定义
>在ECMAScript 中,所有的代码都是由语句来构成的。语句表明执行过程中的流程、限定与约定,形式上可以是单行语句,或者由一对大括号“{}”括起来的复合语句,在语法描述中,复合语句整体可以作为一个单行语句处理。
**语句的种类**
| 类型|子类型|语法 |
| -- | -- |
| 声明语句 | 变量声明语句 |var box = 100; |
| 声明语句 | 标签声明语句 |label : box; |
| 表达式语句 | 变量赋值语句 |box = 100; |
| 表达式语句 | 函数调用语句 |box(); |
| 表达式语句 | 属性赋值语句 |box.property = 100; |
| 表达式语句 | 方法调用语句 |box.method(); |
| 分支语句 | 条件分支语句 |if () {} else {} |
| 分支语句 | 多重分支语句 |switch () { case n : ...}; |
| 循环语句 | for | for (;;;) {} |
| 循环语句 | for ... in | for ( x in x) {} |
| 循环语句 | while | while () {}; |
| 循环语句 | do ... while | do {} while (); |
| 控制结构 | 继续执行子句 |continue ; |
| 控制结构 | 终端执行子句 |break ; |
| 控制结构 | 函数返回子句 |return ; |
| 控制结构 | 异常触发子句 |throw ; |
| 控制结构 | 异常捕获与处理 |try {} catch () {} finally {} |
| 其他 | 空语句 |; |
| 其他 | with | 语句with () {} |
###2.>if 语句
>if 语句即条件判断语句,一共有三种格式:
1 . if (条件表达式) 语句;
~~~
var box = 100;
if (box > 50) alert('box大于50');//一行的if语句,判断后执行一条语句
var box = 100;
if (box > 50)
alert('box 大于50'); //两行的if 语句,判断后也执行一条语句
alert('不管怎样,我都能被执行到!');
var box = 100;
if (box < 50) {
alert('box 大于50');
alert('不管怎样,我都能被执行到!');//用复合语句包含,判断后执行一条复合语句
}
~~~
>对于if 语句括号里的表达式,ECMAScript会自动调用Boolean()转型函数将这个表达式的结果转换成一个布尔值。如果值为true,执行后面的一条语句,否则不执行。
*PS:if 语句括号里的表达式如果为true,只会执行后面一条语句,如果有多条语句,那么就必须使用复合语句把多条语句包含在内。*
*PS2:推荐使用第一种或者第三种格式,一行的if语句,或者多行的if 复合语句。这样就不会因为多条语句而造成混乱。*
*PS3:复合语句我们一般喜欢称作为:代码块。*
2 . if (条件表达式) {语句;} else {语句;}
~~~
var box = 100;
if (box > 50) {
alert('box 大于50'); //条件为true,执行这个代码块
} else {
alert('box 小于50'); //条件为false,执行这个代码块
}
~~~
3 .if (条件表达式) {语句;} else if (条件表达式) {语句;} ... else {语句;}
~~~
var box = 100;
if (box >= 100) { //如果满足条件,不会执行下面任何分支
alert('甲');
} else if (box >= 90) {
alert('乙');
} else if (box >= 80) {
alert('丙');
} else if (box >= 70) {
alert('丁');
} else if (box >= 60) {
alert('及格');
} else { //如果以上都不满足,则输出不及格
alert('不及格');
}
~~~
###3.>switch 语句
>switch 语句是多重条件判断,用于多个值相等的比较。
~~~
var box = 1;
switch (box) { //用于判断box 相等的多个值
case 1 :
alert('one');
break; //break;用于防止语句的穿透
case 2 :
alert('two');
break;
case 3 :
alert('three');
break;
default : //相当于if 语句里的else,否则的意思
alert('error');
}
~~~
###4.>do...while 语句
>do...while 语句是一种先运行,后判断的循环语句。也就是说,不管条件是否满足,至少先运行一次循环体。
~~~
var box = 1; //如果是1,执行五次,如果是10,执行1 次
do {
alert(box);
box++;
} while (box <= 5); //先运行一次,再判断
~~~
###5.>while 语句
>while 语句是一种先判断,后运行的循环语句。也就是说,必须满足条件了之后,方可运行循环体。
~~~
var box = 1; //如果是1,执行五次,如果是10,不执行
while (box <= 5) { //先判断,再执行
alert(box);
box++;
}
~~~
###6.>for 语句
>for 语句也是一种先判断,后运行的循环语句。但它具有在执行循环之前初始变量和定义循环后要执行代码的能力。
~~~
for (var box = 1; box <= 5 ; box++) { //第一步,声明变量var box = 1;
alert(box); //第二步,判断box <=5
} //第三步,alert(box)
//第四步,box++
//第五步,从第二步再来,直到判断为false
~~~
###7.>for...in 语句
>for...in 语句是一种精准的迭代语句,可以用来枚举对象的属性。
~~~
var box = { //创建一个对象
'name' : '无脑码农', //键值对,左边是属性名,右边是值
'age' : 28,
'height' : 178
};
for (var p in box) { //列举出对象的所有属性
alert(p);
}
~~~
###8.>break 和continue 语句
>break 和continue语句用于在循环中精确地控制代码的执行。其中,break 语句会立即退出循环,强制继续执行循环体后面的语句。而continue 语句退出当前循环,继续后面的循环。
~~~
for (var box = 1; box <= 10; box++) {
if (box == 5) break; //如果box 是5,就退出循环
document.write(box);
document.write('<br />');
}
for (var box = 1; box <= 10; box++) {
if (box == 5) continue; //如果box 是5,就退出当前循环
document.write(box);
document.write('<br />');
}
~~~
###9.>with 语句
>with 语句的作用是将代码的作用域设置到一个特定的对象中。
~~~
var box = { //创建一个对象
'name' : '无脑码农', //键值对
'age' : 28,
'height' : 178
};
var n = box.name; //从对象里取值赋给变量
var a = box.age;
var h = box.height;
~~~
>可以将上面的三段赋值操作改写成:
~~~
with (box) { //省略了box 对象名
var n = name;
var a = age;
var h = height;
}
~~~
第五章运算符
最后更新于:2022-04-01 04:58:51
#第五章运算符
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>ECMA-262 描述了一组用于操作数据值的运算符,包括一元运算符、布尔运算符、算术运算符、关系运算符、三元运算符、位运算符及赋值运算符。ECMAScript中的运算符适用于很多值,包括字符串、数值、布尔值、对象等。不过,通过上一章我们也了解到,应用于对象时通常会调用对象的valueOf()和toString()方法,以便取得相应的值。
*PS:前面的章节我们讲过typeof操作符、new操作符,也可以称之为typeof 运算符、new 运算符,是同一个意思。*
###1.>什么是表达式
>表达式是ECMAScript中的一个“短语”,解释器会通过计算把它转换成一个值。最简单的表达式是字面量或者变量名。例如:
~~~
5.96 //数值字面量
'Zhang' //字符串字面量
true //布尔值字面量
null //空值字面量
/Java/ //正则表达式字面量
{x:1, y:2} //对象字面量、对象表达式
[1,2,3] //数组字面量、数组表达式
function(n) {return x+y;} //函数字面量、函数表达式
box //变量
当然,还可以通过合并简单的表达式来创建复杂的表达式。比如:
box + 5.96 //加法运算的表达式
typeof(box) //查看数据类型的表达式
box > 8 //逻辑运算表达式
~~~
>通过上面的叙述,我们得知,单一的字面量和组合字面量的运算符都可称为表达式。
###2.>一元运算符
>只能操作一个值的运算符叫做一元运算符。
1.递增++和递减--
~~~
var box = 100;
++box; //把box 累加一个1,相当于box = box+1
--box; //把box 累减一个1,相当于box = box-1
box++; //同上
box--; //同上
~~~
2.前置和后置的区别
>在没有赋值操作,前置和后置是一样的。但在赋值操作时,如果递增或递减运算符前置,那么前置的运算符会先累加或累减再赋值,如果是后置运算符则先赋值再累加或累减。
~~~
var box = 100;
var age = ++box; //age 值为101
var height = box++; //height 值为100
~~~
3.其他类型应用一元运算符的规则
~~~
var box = '89'; box++; //90,数值字符串自动转换成数值
var box = 'ab'; box++; //NaN,字符串包含非数值转成NaN
var box = false; box++; //1,false 转成数值是0,累加就是1
var box = 2.3; box++; //3.3,直接加1
var box = { //1,不设置toString 或valueOf 即为NaN
toString : function() {
return 1;
}
}; box++;
~~~
4.加和减运算符
>**加运算规则如下**:
~~~
var box = 100; +box; //100,对于数值,不会产生任何影响
var box = '89'; +box; //89,数值字符串转换成数值
var box = 'ab'; +box; //NaN,字符串包含非数值转成NaN
var box = false; +box; //0,布尔值转换成相应数值
var box = 2.3; +box; //2.3,没有变化
var box = { //1,不设置toString 或valueOf 即为NaN
toString : function() {
return 1;
}
}; +box;
~~~
>**减运算规则如下:**
~~~
var box = 100; -box; //-100,对于数值,直接变负
var box = '89'; -box; //-89,数值字符串转换成数值
var box = 'ab'; -box; //NaN,字符串包含非数值转成NaN
var box = false; -box; //0,布尔值转换成相应数值
var box = 2.3; -box; //-2.3,没有变化
var box = {
//-1,不设置toString 或valueOf 即为NaN
toString : function() {
return 1;
}
}; -box;
~~~
>加法和减法运算符一般用于算术运算,也可向上面进行类型转换。
###3.>算术运算符
>ECMAScript 定义了5个算术运算符,加减乘除求模(取余)。如果在算术运算的值不是数值,那么后台会先使用Number()转型函数将其转换为数值(隐式转换)。
**1.加法**
~~~
var box = 1 + 2; //等于3
var box = 1 + NaN; //NaN,只要有一个NaN 就为NaN
var box = Infinity + Infinity; //Infinity
var box = -Infinity + -Infinity; //-Infinity
var box = Infinity + -Infinity;//NaN,正无穷和负无穷相加等NaN
var box = 100 + '100';//100100,字符串连接符,有字符串就不是加法
var box = '您的年龄是:'+10+20;//您的年龄是:1020,被转换成字符串
var box = 10 + 20+'是您的年龄';//30是您的年龄,没有被转成字符串
var box = '您的年龄是:' + (10 + 20); //您的年龄是:30,没有被转成字符串
var box = 10 + 对象//10[objectObject],如果有toString()或valueOf()则返回10+返回数的值
~~~
**2.减法**
~~~
var box = 100 - 70; //等于30
var box = -100 - 70 //等于-170
var box = -100 - -70 //-30,一般写成-100 - (-70)比较清晰
var box = 1 - NaN; //NaN,只要有一个NaN 就为NaN
var box = Infinity - Infinity; //NaN
var box = -Infinity - -Infinity; //NaN
var box = Infinity - -Infinity; //Infinity
var box = -Infinity - Infinity; //-Infinity
var box = 100 - true; //99,true 转成数值为1
var box = 100 - ''; //100,''转成了0
var box = 100 - '70'; //30,'70'转成了数值70
var box = 100 - null; //100,null 转成了0
var box = 100 - 'Zhang'; //NaN,Zhang 转成了NaN
var box = 100 - 对象//NaN,如果有toString()或valueOf()则返回10-返回数的值
~~~
**3.乘法**
~~~
var box = 100 * 70; //7000
var box = 100 * NaN; //NaN,只要有一个NaN 即为NaN
var box = Infinity * Infinity; //Infinity
var box = -Infinity * Infinity ; //-Infinity
var box = -Infinity * -Infinity ; //Infinity
var box = 100 * true; //100,true 转成数值为1
var box = 100 * ''; //0,''转成了0
var box = 100 * null; //0,null 转成了0
var box = 100 * 'Zhang'; //NaN,Zhang 转成了NaN
var box = 100 * 对象//NaN,如果有toString()或valueOf()则返回10 - 返回数的值
~~~
**4.除法**
~~~
var box = 100 / 70; //1.42....
var box = 100 / NaN; //NaN
var box = Infinity / Infinity; //NaN
var box = -Infinity / Infinity ; //NaN
var box = -Infinity / -Infinity; //NaN
var box = 100 / true; //100,true 转成1
var box = 100 / ''; //Infinity,
var box = 100 / null; //Infinity,
var box = 100 / 'Zhang'; //NaN
var box = 100 / 对象;//NaN,如果有toString()或valueOf()则返回10 / 返回数的值
~~~
**5.求模**
~~~
var box = 10 % 3; //1,余数为1
var box = 100 % NaN; //NaN
var box = Infinity % Infinity; //NaN
var box = -Infinity % Infinity ; //NaN
var box = -Infinity % -Infinity; //NaN
var box = 100 % true; //0
var box = 100 % ''; //NaN
var box = 100 % null; //NaN
var box = 100 % 'Zhang'; //NaN
var box = 100 % 对象;//NaN,如果有toString()或valuevalueOf()则返回10 % 返回数的值
~~~
###4.>关系运算符
>用于进行比较的运算符称作为关系运算符:小于(<)、大于(>)、小于等于(<=)、大于等于(>=)、相等(==)、不等(!=)、全等(恒等)(===)、不全等(不恒等)(!==)
>和其他运算符一样,当关系运算符操作非数值时要遵循一下规则:
1.两个操作数都是数值,则数值比较;
2.两个操作数都是字符串,则比较两个字符串对应的字符编码值;
3.两个操作数有一个是数值,则将另一个转换为数值,再进行数值比较;
4.两个操作数有一个是对象,则先调用valueOf()方法或toString()方法,再用结果比较;
~~~
var box = 3 > 2; //true
var box = 3 > 22; //false
var box = '3' > 22; //false
var box = '3' > '22'; //true
var box = 'a' > 'b'; //false a=97,b=98
var box = 'a' > 'B'; //true B=66
var box = 1 > 对象;//false,如果有toString()或valueOf()则返回1 > 返回数的值
~~~
>在相等和不等的比较上,如果操作数是非数值,则遵循一下规则:
1.一个操作数是布尔值,则比较之前将其转换为数值,false转成0,true 转成1;
2.一个操作数是字符串,则比较之前将其转成为数值再比较;
3.一个操作数是对象,则先调用valueOf()或toString()方法后再和返回值比较;
4.不需要任何转换的情况下,null 和undefined 是相等的;
5.一个操作数是NaN,则==返回false,!=返回true;并且NaN和自身不等;
6.两个操作数都是对象,则比较他们是否是同一个对象,如果都指向同一个对象,则返回true,否则返回false。
7.在全等和全不等的判断上,比如值和类型都相等,才返回true,否则返回false。
~~~
var box = 2 == 2; //true
var box = '2' == 2; //true,'2'会转成成数值2
var box = false == 0; //true,false 转成数值就是0
var box = 'a' == 'A'; //false,转换后的编码不一样
var box = 2 == {}; //false,执行toString()或valueOf()会改变
var box = 2 == NaN; //false,只要有NaN,都是false
var box = {} == {};//false,比较的是他们的地址,每个新创建对象的引用地址都不同
var age = {};
var height = age;
var box = age == height; //true,引用地址一样,所以相等
var box = '2' === 2 //false,值和类型都必须相等
var box = 2 !== 2 //false,值和类型都相等了
~~~
**特殊值对比表**
| 表达式|值|
| -- | -- |
| null == undefined| true |
| 'NaN' == NaN| false |
| 5 == NaN| false |
| NaN == NaN| false |
| false == 0| true |
| true == 1| true |
| true == 2| false |
| undefined == 0| false |
| null == 0| false |
| '100' == 100| true |
| '100' === 100| false |
###5.>逻辑运算符
>逻辑运算符通常用于布尔值的操作,一般和关系运算符配合使用,有三个逻辑运算符:逻辑与(AND)、逻辑或(OR)、逻辑非(NOT)。
1.逻辑与(AND) :&&
`var box = (5 > 4) && (4 > 3)//true,两边都为true,返回true`
| 第一个操作数|第二个操作数|结果 |
| -- | -- |
| true| true| true |
| true| false |false |
| false| true| false |
| false| false| false |
>如果两边的操作数有一个操作数不是布尔值的情况下,与运算就不一定返回布尔值,此时,遵循已下规则:
1.第一个操作数是对象,则返回第二个操作数;
2.第二个操作数是对象,则第一个操作数返回true,才返回第二个操作数,否则返回false;
3.有一个操作数是null,则返回null;
4.有一个操作数是undefined,则返回undefined。
~~~
var box = 对象&& (5 > 4); //true,返回第二个操作数
var box = (5 > 4) && 对象; //[object Object]
var box = (3 > 4) && 对象; //false
var box = (5 > 4) && null; //null
~~~
>逻辑与运算符属于短路操作,顾名思义,如果第一个操作数返回是false,第二个数不管是true 还是false 都返回的false。
~~~
var box = true && age; //出错,age 未定义
var box = false && age; //false,不执行age 了
~~~
2.逻辑或(OR):||
`var box = (9>7)||(7>8);//true,两边只要有一边是true,返回true`
| 第一个操作数|第二个操作数|结果 |
| -- | -- |
| true| true| true |
| true| false |true |
| false| true| true |
| false| false| false |
>如果两边的操作数有一个操作数不是布尔值的情况下,逻辑与运算就不一定返回布尔
值,此时,遵循已下规则:
1.第一个操作数是对象,则返回第一个操作数;
2.第一个操作数的求值结果为false,则返回第二个操作数;
3.两个操作数都是对象,则返回第一个操作数;
4.两个操作数都是null,则返回null;
5.两个操作数都是NaN,则返回NaN;
6.两个操作数都是undefined,则返回undefined;
~~~
var box = 对象|| (5 > 3); //[object Object]
var box = (5 > 3) || 对象; //true
var box = 对象1 || 对象2; //[object Object]
var box = null || null; //null
var box = NaN || NaN; //NaN
var box = undefined || undefined; //undefined
~~~
>和逻辑与运算符相似,逻辑或运算符也是短路操作。当第一操作数的求值结果为true,
就不会对第二个操作数求值了。
var box = true || age; //true
var box = false || age; //出错,age 未定义
我们可以利用逻辑或运算符这一特性来避免为变量赋null 或undefined 值。
`var box = oneObject||twoObject;//把其中一个有效变量值赋给box`
3.逻辑非(NOT):!
>逻辑非运算符可以用于任何值。无论这个值是什么数据类型,这个运算符都会返回一个
布尔值。它的流程是:先将这个值转换成布尔值,然后取反,规则如下:
1.操作数是一个对象,返回false;
2.操作数是一个空字符串,返回true;
3.操作数是一个非空字符串,返回false;
4.操作数是数值0,返回true;
5.操作数是任意非0 数值(包括Infinity),false;
6.操作数是null,返回true;
7.操作数是NaN,返回true;
8.操作数是undefined,返回true;
~~~
var box = !(5 > 4); //false
var box = !{}; //false
var box = !''; //true
var box = !'Zhang'; //false
var box = !0; //true
var box = !8; //false
var box = !null; //true
var box = !NaN; //true
var box = !undefined; //true
~~~
使用一次逻辑非运算符,流程是将值转成布尔值然后取反。而使用两次逻辑非运算符就是将值转成成布尔值取反再取反,相当于对值进行Boolean()转型函数处理。
~~~
var box = !!0; //false
var box = !!NaN; //false
~~~
通常来说,使用一个逻辑非运算符和两个逻辑非运算符可以得到相应的布尔值,而使用三个以上的逻辑非运算符固然没有错误,但也没有意义。
###6.>*位运算符
*PS:在一般的应用中,我们基本上用不到位运算符。虽然,它比较基于底层,性能和速度会非常好,而就是因为比较底层,使用的难度也很大。所以,我们作为选学来对待。*
>位运算符有七种,分别是:位非NOT(~)、位与AND(&)、位或OR(|)、位异或XOR(^)、左移(<<)、有符号右移(>>)、无符号右移(>>>)。
~~~
var box = ~25; //-26
var box = 25 & 3; //1
var box = 25 | 3; //27
var box = 25 << 3; //200
var box = 25 >> 2; //6
var box = 25 >>> 2; //6
~~~
###7.>赋值运算符
>赋值运算符用等于号(=)表示,就是把右边的值赋给左边的变量。
`var box = 100; //把100 赋值给box 变量`
>复合赋值运算符通过x=的形式表示,x表示算术运算符及位运算符。
~~~
var box = 100;
box = box +100; //200,自己本身再加100
~~~
>这种情况可以改写为:
~~~
var box = 100;
box += 100; //200,+=代替box+100
~~~
>除了这种+=加/赋运算符,还有其他的几种如下:
1.乘/赋(*=)
2.除/赋(/=)
3.模/赋(%=)
4.加/赋(+=)
5.减/赋(-=)
6.左移/赋(<<=)
7.有符号右移/赋(>>=)
8.无符号有移/赋(>>>=)
###8.>其他运算符
1.字符串运算符
>字符串运算符只有一个,即:"+"。它的作用是将两个字符串相加。
规则:至少一个操作数是字符串即可。
~~~
var box = '100' + '100'; //100100
var box = '100' + 100; //100100
var box = 100 + 100; //200
~~~
2.逗号运算符
>逗号运算符可以在一条语句中执行多个操作。
~~~
var box = 100, age = 20, height = 178; //多个变量声明
var box = (1,2,3,4,5); //5,变量声明,将最后一个值赋给变量,不常用
var box = [1,2,3,4,5]; //[1,2,3,4,5],数组的字面量声明
var box = { //[object Object],对象的字面量声明
1 : 2,
3 : 4,
5 : 6
};
~~~
3.三元条件运算符
>三元条件运算符其实就是后面将要学到的if 语句的简写形式。var box = 5>4?'对':'错';//对,5>4返回true则把'对'赋值给box,反之,相当于:
~~~
var box = ''; //初始化变量
if (5 > 4) { //判断表达式返回值
box = '对'; //赋值
} else {
box = '错'; //赋值
}
~~~
###9.>运算符优先级
>在一般的运算中,我们不必考虑到运算符的优先级,因为我们可以通过圆括号来解决这种问题。比如:
~~~
var box = 5 - 4 * 8; //-27
var box = (5 - 4) * 8; //8
~~~
>但如果没有使用圆括号强制优先级,我们必须遵循以下顺序:
| 运算符|描述 |
| -- | -- |
| . [] ()| 对象成员存取、数组下标、函数调用等 |
| ++ -- ~ ! delete new typeof void| 一元运算符 |
| * / %| 乘法、除法、去模 |
| + - +| 加法、减法、字符串连接 |
| << >> >>>| 移位 |
| < <= > >= instanceof| 关系比较、检测类实例 |
| == != === !==| 恒等(全等) |
| & |位与 |
| ^ |位异或 |
| \| |位或 |
| && |逻辑与 |
| ll |逻辑或 |
| ?: |三元条件 |
| = x= |赋值、运算赋值 |
| , |多重赋值、数组元素 |
第四章数据类型
最后更新于:2022-04-01 04:58:49
#第四章数据类型
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>ECMAScript 中有5种简单数据类型:Undefined、Null、Boolean、Number 和String。还有一种复杂数据类型——Object。ECMAScript不支持任何创建自定义类型的机制,所有值都成为以上6种数据类型之一。
###1.>typeof 操作符
>typeof 操作符是用来检测变量的数据类型。对于值或变量使用typeof 操作符会返回如下字符串。
| 字符串|描述|
| -- | -- |
| undefined| 未定义|
| boolean| 布尔值 |
| string| 字符串 |
| number | 数值 |
| object | 对象或null |
| function | 函数 |
~~~
var box = '无脑码农';
alert(typeof box);
alert(typeof '无脑码农');
~~~
>typeof 操作符可以操作变量,也可以操作字面量。虽然也可以这样使用:typeof(box),但,typeof 是操作符而非内置函数。PS:函数在ECMAScript 中是对象,不是一种数据类型。所以,使用typeof 来区分function 和object 是非常有必要的。
###2.>Undefined 类型
>Undefined 类型只有一个值,即特殊的undefined。在使用var声明变量,但没有对其初始化时,这个变量的值就是undefined。
~~~
var box;
alert(box);
~~~
*PS:我们没有必要显式的给一个变量赋值为undefined,因为没有赋值的变量会隐式的(自动的)赋值为undefined;而undefined主要的目的是为了用于比较,ECMAScript第3版之前并没有引入这个值,引入之后为了正式区分空对象与未经初始化的变量。*
>未初始化的变量与根本不存在的变量(未声明的变量)也是不一样的。
~~~
var box;
alert(age); //age is not defined
~~~
*PS:如果typeof box,typeof age都返回的undefined。从逻辑上思考,他们的值,一个是undefined,一个报错;他们的类型,却都是undefined。所以,我们在定义变量的时候,尽可能的不要只声明,不赋值。*
###3.>Null 类型
>Null 类型是一个只有一个值的数据类型,即特殊的值null。它表示一个空对象引用(指针),而typeof 操作符检测null 会返回object。
~~~
var box = null;
alert(typeof box);
~~~
>如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为null。这样,当检查null值就知道是否已经变量是否已经分配了对象引用了。
~~~
var box = null;
if (box != null) {
alert('box 对象已存在!');
}
~~~
>有个要说明的是:undefined 是派生自null 的,因此ECMA-262 规定对它们的相等性测试返回true。
`alert(undefined == null);`
>由于undefined 和null两个值的比较是相等的,所以,未初始化的变量和赋值为null的变量会相等。这时,可以采用typeof变量的类型进行比较。但,建议还是养成编码的规范,不要忘记初始化变量。
~~~
var box;
var car = null;
alert(typeof box == typeof car)
~~~
###4.>Boolean 类型
>Boolean 类型有两个值(字面量):true 和false。而true 不一定等于1,false不一定等于0。JavaScript是区分大小写的,True 和False 或者其他都不是Boolean类型的值。
~~~
var box = true;
alert(typeof box);
~~~
>虽然Boolean 类型的字面量只有true 和false 两种,但ECMAScript 中所有类型的值都有与这两个Boolean值等价的值。要将一个值转换为其对应的Boolean 值,可以使用转型函数
~~~
Boolean()。
var hello = 'Hello World!';
var hello2 = Boolean(hello);
alert(typeof hello);
~~~
>上面是一种显示转换,属于强制性转换。而实际应用中,还有一种隐式转换。比如,在if 条件语句里面的条件判断,就存在隐式转换。
~~~
var hello = 'Hello World!';
if (hello) {
alert('如果条件为true,就执行我这条!');
} else {
alert('如果条件为false,就执行我这条!');
}
~~~
**以下是其他类型转换成 Boolean类型规则**
| 数据类型|转换为true 的值|转换为false 的值 |
| -- | -- | -- |
| Boolean| true |false |
| String| 任何非空字符串|空字符串 |
| Number| 任何非零数字值(包括无穷大)| 0 和NaN |
| Object| 任何对象|null |
| Undefined| |undefined |
###5.>Number 类型
>Number 类型包含两种数值:整型和浮点型。为了支持各种数值类型,ECMA-262 定义了不同的数值字面量格式。
>最基本的数值字面量是十进制整数。
`var box = 100; //十进制整数`
>八进制数值字面量,(以8为基数),前导必须是0,八进制序列(0~7)。
~~~
var box = 070; //八进制,56
var box = 079; //无效的八进制,自动解析为79
var box = 08; //无效的八进制,自动解析为8
~~~
>十六进制字面量前面两位必须是0x,后面是(0~9 及A~F)。
~~~
var box = 0xA; //十六进制,10
var box = 0x1f; //十六进制,31
~~~
>浮点类型,就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。
~~~
var box = 3.8;
var box = 0.8;
var box = .8; //有效,但不推荐此写法
~~~
>由于保存浮点数值需要的内存空间比整型数值大两倍,因此ECMAScript 会自动将可以转换为整型的浮点数值转成为整型。
~~~
var box = 8.; //小数点后面没有值,转换为8
var box = 12.0; //小数点后面是0,转成为12
~~~
>对于那些过大或过小的数值,可以用科学技术法来表示(e表示法)。用e 表示该数值的前面10 的指数次幂。
~~~
var box = 4.12e9; //即4120000000
var box = 0.00000000412; //即4.12e-9
~~~
>虽然浮点数值的最高精度是17位小数,但算术运算中可能会不精确。由于这个因素,做判断的时候一定要考虑到这个问题(比如使用整型判断)。
`alert(0.1+0.2); //0.30000000000000004`
>浮点数值的范围在:Number.MIN_VALUE ~ Number.MAX_VALUE 之间。
~~~
alert(Number.MIN_VALUE); //最小值
alert(Number.MAX_VALUE); //最大值
~~~
>如果超过了浮点数值范围的最大值或最小值,那么就先出现Infinity(正无穷)或者-Infinity(负无穷)。
~~~
var box = 100e1000; //超出范围,Infinity
var box = -100e1000; //超出范围,-Infinity
~~~
>也可能通过Number.POSITIVE_INFINITY和Number.NEGATIVE_INFINITY得到Infinity(正无穷)及-Infinity(负无穷)的值。
~~~
alert(Number.POSITIVE_INFINITY); //Infinity(正无穷)
alert(Number.NEGATIVE_INFINITY);//-Infinity(负无穷)
~~~
>要想确定一个数值到底是否超过了规定范围,可以使用isFinite()函数。如果没有超过,返回true,超过了返回false。
~~~
var box = 100e1000;
alert(isFinite(box)); //返回false 或者true
~~~
>NaN,即非数值(Not a Number)是一个特殊的值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。比如,在其他语言中,任何数值除以0都会导致错误而终止程序执行。但在ECMAScript 中,会返回出特殊的值,因此不会影响程序执行。
~~~
var box = 0 / 0; //NaN
var box = 12 / 0; //Infinity
var box = 12 / 0 * 0; //NaN
~~~
>可以通过Number.NaN得到NaN值,任何与NaN进行运算的结果均为NaN,NaN 与自身不相等(NaN 不与任何值相等)。
~~~
alert(Number.NaN); //NaN
alert(NaN+1); //NaN
alert(NaN == NaN) //false
~~~
>ECMAScript 提供了isNaN()函数,用来判断这个值到底是不是NaN。isNaN()函数在接收到一个值之后,会尝试将这个值转换为数值。
~~~
alert(isNaN(NaN)); //true
alert(isNaN(25)); //false,25 是一个数值
alert(isNaN('25')); //false,'25'是一个字符串数值,可以转成数值
alert(isNaN('Zhang')); //true,'Zhang'不能转换为数值
alert(isNaN(true)); //false true 可以转成成1
~~~
>isNaN()函数也适用于对象。在调用isNaN()函数过程中,首先会调用valueOf()方法,然后确定返回值是否能够转换成数值。如果不能,则基于这个返回值再调用toString()方法,再测试返回值。
~~~
var box = {
toString : function () {
return '123'; //可以改成return 'Zhang'查看效果
}
};
alert(isNaN(box)); //false
~~~
>有3 个函数可以把非数值转换为数值:Number()、parseInt()和parseFloat()。Number()函数是转型函数,可以用于任何数据类型,而另外两个则专门用于把字符串转成数值。
~~~
alert(Number(true));//1,Boolean类型的true和false分别转换成1 和0
alert(Number(25)); //25,数值型直接返回
alert(Number(null)); //0,空对象返回0
alert(Number(undefined)); //NaN,undefined 返回NaN
~~~
>如果是字符串,应该遵循一下规则:
1.只包含数值的字符串,会直接转成成十进制数值,如果包含前导0,即自动去掉。
~~~
alert(Number('456')); //456
alert(Number('070')); //70
~~~
2.只包含浮点数值的字符串,会直接转成浮点数值,如果包含前导和后导0,即自动去掉。
`alert(Number('08.90')); //8.9`
3.如果字符串是空,那么直接转成成0。
`alert(Number('')); //0`
4.如果不是以上三种字符串类型,则返回NaN。
`alert('Zhang123'); //NaN`
5.如果是对象,首先会调用valueOf()方法,然后确定返回值是否能够转换成数值。如果转换的结果是NaN,则基于这个返回值再调用toString()方法,再测试返回值。
~~~
var box = {
toString : function () {
return '123'; //可以改成return 'Zhang'查看效果
}
};
alert(Number(box)); //123
~~~
>由于Number()函数在转换字符串时比较复杂且不够合理,因此在处理整数的时候更常用的是parseInt()。
~~~
alert(parsetInt('333Zhang')); //333,会返回整数部分
alert(parsetInt('Zhang123Zhang'));//NaN,如果第一个不是数值,就返回NaNalert(parseInt('32Wu43Nao')); //12,从第一数值开始取,到最后一个连续数值结束
alert(parseInt('45.34')); //56,小数点不是数值,会被去掉
alert(parseInt('')); //NaN,空返回NaN
~~~
>parseInt()除了能够识别十进制数值,也可以识别八进制和十六进制。
~~~
alert(parseInt('0xA')); //10,十六进制
alert(parseInt('070')); //56,八进制
alert(parseInt('0xAZhang')); //100,十六进制,Zhang 被自动过滤掉
~~~
>ECMAScript 为parseInt()提供了第二个参数,用于解决各种进制的转换。
~~~
alert(parseInt('0xAF')); //175,十六进制
alert(parseInt('AF',16));//175,第二参数指定十六进制,可以去掉0x 前导
alert(parseInt('AF')); //NaN,理所当然
alert(parseInt('101010101',2)); //314,二进制转换
alert(parseInt('70',8)) //56,八进制转换
~~~
>parseFloat()是用于浮点数值转换的,和parseInt()一样,从第一位解析到非浮点数值位置。
~~~
alert(parseFloat('123Zhang')); //123,去掉不是别的部分
alert(parseFloat('0xA')); //0,不认十六进制
alert(parseFloat('123.4.5')); //123.4,只认一个小数点
alert(parseFloat('0123.400')); //123.4,去掉前后导
alert(parseFloat('1.234e7'));//12340000,把科学技术法转成普通数值
~~~
###6.>String 类型
>String 类型用于表示由于零或多个16位Unicode字符组成的字符序列,即字符串。字符串可以由双引号(")或单引号(')表示。
~~~
var box = 'Zhang';
var box = "Zhang";
~~~
>PS:在某些其他语言(PHP)中,单引号和双引号表示的字符串解析方式不同,而ECMAScript中,这两种表示方法没有任何区别。但要记住的是,必须成对出现,不能穿插使用,否则会出错。
`var box = '无脑码农"; //出错`
String 类型包含了一些特殊的字符字面量,也叫转义序列。
| 字面量|含义|
| -- | -- |
| \n| 换行 |
| \t| 制表 |
| \b| 空格 |
| \r| 回车 |
| \f| 进纸 |
| \\| 斜杠 |
| \'| 单引号 |
| \"| 双引号 |
| \xnn| 以十六进制代码nn 表示的一个字符(0~F)。例:\x41 |
| \unnn| 以十六进制代码nnn 表示的一个Unicode 字符(0~F)。例:\u03a3 |
>ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量。
~~~
var box = 'Mr.';
box = box + ' Zhang';
~~~
>toString()方法可以把值转换成字符串。
~~~
var box = 11;
var box = true;
alert(typeof box.toString());
~~~
>toString()方法一般是不需要传参的,但在数值转成字符串的时候,可以传递进制参数。
~~~
var box = 10;
alert(box.toString()); //10,默认输出
alert(box.toString(2)); //1010,二进制输出
alert(box.toString(8)); //12,八进制输出
alert(box.toString(10)); //10,十进制输出
alert(box.toString(16)); //a,十六进制输出
~~~
>如果在转型之前不知道变量是否是null 或者undefined 的情况下,我们还可以使用转型函数String(),这个函数能够将任何类型的值转换为字符串。
~~~
var box = null;
alert(String(box));
~~~
>PS:如果值有toString()方法,则调用该方法并返回相应的结果;如果是null 或者undefined,则返回"null"或者"undeinfed"。
###7.>Object 类型
>ECMAScript 中的对象其实就是一组数据和功能的集合。对象可以通过执行new 操作符后跟要创建的对象类型的名称来创建。
`var box = new Object();`
>Object()是对象构造,如果对象初始化时不需要传递参数,可以不用写括号,但这种方式我们是不推荐的。
`var box = new Object;`
>Object()里可以任意传参,可以传数值、字符串、布尔值等。而且,还可以进行相应的计算。
~~~
var box = new Object(2); //Object 类型,值是2
var age = box + 2; //可以和普通变量运算
alert(age); //输出结果,转型成Number 类型了
~~~
>既然可以使用new Object()来表示一个对象,那么我们也可以使用这种new 操作符来创建其他类型的对象。
~~~
var box = new Number(5); //new String('Zhang')、new Boolean(true)
alert(typeof box); //Object 类型
~~~
*PS:面向对象是JavaScript课程的重点,这里我们只是简单做个介绍。详细的课程将在以后的章节继续学习。*
第三章语法、关键保留字及变量
最后更新于:2022-04-01 04:58:46
#第三章语法、关键保留字及变量
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
###1.>语法构成
**区分大小写**
>ECMAScript中的一切,包括变量、函数名和操作符都是区分大小写的。例如:text 和Text 表示两种不同的变量。
**标识符**
>所谓标识符,就是指变量、函数、属性的名字,或者函数的参数。标识符可以是下列格式规则组合起来的一或多个字符:
1. 第一字符必须是一个字母、下划线(_)或一个美元符号($)。
2. 其他字符可以是字母、下划线、美元符号或数字。
3. 不能把关键字、保留字、true、false 和null 作为标识符。
>例如:myName、book123 等注
**注释**
>ECMAScript 使用C 风格的注释,包括单行注释和块级注释。
~~~
// 单行注释
/*
* 这是一个多行
* 注释
*/
~~~
**字面量**
>所有字面量,就是程序中直接显示出来的数据值。
300 //数字字面量
'无脑码农' //字符串字面量
true //布尔字面量
/js/gi //正则表达式字面量
null //对象字面量
在ECMAScript 第3版中,像数组字面量和对象字面量的表达式也是支持的,如下:
{x:1, y:2} //对象字面量表达式
[1,2,3,4,5] //数组字面量表达式
###2.>关键字和保留字
>ECMAScript-262 描述了一组具有特定用途的关键字,一般用于控制语句的开始或结束,
或者用于执行特定的操作等。关键字也是语言保留的,不能用作标识符。
**ECMAScript 全部关键字**
| break|else| new |var|
| -- | -- | -- | -- |
| case| finally| return| void |
| catch| for |switch |while |
| continue| function| this| with |
| default| if| throw| delete |
| in| try|do| instanceof| typeof |
>ECMAScript-262 还描述了另一组不能用作标识符的保留字。尽管保留字在JavaScript中还没有特定的用途,但它们很有可能在将来被用作关键字。
**ECMAScript-262 第3 版定义的全部保留字**
| abstract |enum |int |short |
| -- | -- | -- | -- |
| boolean| export| interface| static |
| byte| extends| long| super |
| char| final| native| synchronized |
| class| float| package| throws |
| const| goto| private| transient |
| debugger| implements| protected| volatile |
| double| import| public |
###3.>变量
>ECMAScript 的变量是松散类型的,所谓松散类型就是用来保存任何类型的数据。定义
变量时要使用var 操作符(var是关键),后面跟一个变量名(变量名是标识符)。
~~~
var box;
alert(box);
~~~
>这句话定义了box变量,但没有对它进行初始化(也就是没有给变量赋值)。这时,系统会给它一个特殊的值--undefined(表示未定义)。
~~~
var box= '无脑码农';
alert(box);
~~~
>所谓变量,就是可以初始化后可以再次改变的量。ECMAScript属于弱类型(松散类型)的语言,可以同时改变不同类型的量。(PS:虽然可以改变不同类型的量,但这样做对于后期维护带来困难,而且性能也不高,导致成本很高!)
~~~
var boxString = '无脑码农';
boxString = 100;
alert(boxString);
~~~
>重复的使用var 声明一个变量,只不过是一个赋值操作,并不会报错。但这样的操作是比较二的,没有任何必要。
~~~
var box= '无脑码农';
var box= 'Zhang';
~~~
>还有一种变量不需要前面var 关键字即可创建变量。这种变量和var的变量有一定的区别和作用范围,我们会在作用域那一节详细探讨。
`box= '无脑码农';`
>当你想声明多个变量的时候,可以在一行或者多行操作。
`var box= '无脑码农';var age= 100;`
>而当你每条语句都在不同行的时候,你可以省略分号。(PS:这是ECMAScript支持的,但绝对是一个非常不好的编程习惯,切记不要)。
~~~
var box= '无脑码农'
var age= 100
alert(box)
~~~
>可以使用一条语句定义多个变量,只要把每个变量(初始化或者不初始化均可)用逗号分隔开即可,为了可读性,每个变量,最好另起一行,并且第二变量和第一变量对齐(PS:这些都不是必须的)。
~~~
var box= '无脑码农',
age = 28,
height;
~~~
第二章Javascript的HelloWorld
最后更新于:2022-04-01 04:58:44
#第二章Javascript的HelloWorld
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
###1.>创建一张 HTML页面
>以前很多教材使用XHTML1.1来讲解JavaScript课程。但现在HTML5标准已经比较成熟,所以我们这里学习就直接使用HTML5的标准模板来学习。
`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
</body>
</html>`
>页面创建好后,编写一个最简单的JavaScript 脚本(简称JS 脚本)。注意网页的编码格式及文件存储的编码。
###2.> `<Script>`标签解析
>`<script>xxx</script>`这组标签,是用于在html 页面中插入js 的主要方法。它主要有以下
几个属性:
1. charset:可选。表示通过src属性指定的字符集。由于大多数浏览器忽略它,所以很少有人用它。
2. defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。由于大多数浏览器不支持,故很少用。
3. language:已废弃。原来用于代码使用的脚本语言。由于大多数浏览器忽略它,所以不要用了。
4. src:可选。表示包含要执行代码的外部文件。
5. type:必需。可以看作是language的替代品。表示代码使用的脚本语言的内容类型。范例:type="text/javascript"。
~~~
<script type="text/javascript">
alert('HelloWorld');
</script>
~~~
###3.> Javascript代码嵌入
>如果你想弹出一个</script>标签的字符串,那么浏览器会误解成JS 代码已经结束了。解决的方法,就是把字符串分成两个部分,通过连接符‘+’来连接。
~~~
<script type="text/javascript">
alert('</scr'+'ipt>');
</script>
~~~
>一般来说,JS 代码越来越庞大的时候,我们最好把他另存为一个.js 文件,通过src 引入即可。它还具有维护性高、可缓存(加载一次,无需加载)、方便未来扩展的特点。
`<script type="text/javascript" src="demo.js"></script>`
>这样标签内就没有任何JS代码了。但,要注意的是,虽然没有任何代码,也不能用单标签:
`<script type="text/javascript" src="demo.js" />`
>也不能在里面添加任何代码:
`<script type="text/javascript" src="demo.js">alert('放到这里,执行不到!')</script>`
>按照常规,我们会把<script>标签存放到`<head>...</head>`之间。但有时也会放在body之间。
第一章Javascript概述
最后更新于:2022-04-01 04:58:42
#第一章Javascript概述
讲师:_无脑码农(张磊)_
博客:http://www.h5code.com
课程内容:
[TOC]
* * * * *
>JavaScript 诞生于1995年。它当时的目的是为了验证表单输入的验证。因为在JavaScript问世之前,表单的验证都是通过服务器端验证的。而当时都是电话拨号上网的年代,服务器验证数据是一件非常痛苦的事情。经过许多年的发展,JavaScript从一个简单的输入验证成为一门强大的编程语言。所以,学会使用它是非常简单的,而真正掌握它则需要很漫长的时间。那么本套课程,我们将结合视频学习,将大家带入到Javascript的编程海洋。
###1.>什么是JavaScript?
>JavaScript 是一种具有面向对象能力的、解释型的程序设计语言。更具体一点,它是基于对象和事件驱动并具有相对安全性的客户端脚本语言。因为他不需要在一个语言环境下运行,而只需要支持它的浏览器即可。它的主要目的是,验证发往服务器端的数据、增加Web互动、加强用户体验度等。
目前除了应用于Web外,很多框架或引擎也开发自己的Javascript解析器,实现了Javascript负责业务逻辑层的功能。这样我们就能使用Javascript进行全平台的开发了。例如:cocos2d-js、白鹭引擎、Electron等。
###2.>JavaScript特点
>JavaScript是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。
1. 是一种解释性脚本语言(代码不进行预编译)。
2. 主要用来向HTML(标准通用标记语言下的一个应用)页面添加交互行为。
3. 可以直接嵌入HTML页面,但写成单独的js文件有利于结构和行为的分离。
4. 跨平台特性,在绝大多数浏览器的支持下,可以在多种平台下运行(如Windows、Linux、Mac、Android、iOS等)。
>Javascript脚本语言同其他语言一样,有它自身的基本数据类型,表达式和算术运算符及程序的基本程序框架。Javascript提供了四种基本的数据类型和两种特殊数据类型用来处理数据和文字。而变量提供存放信息的地方,表达式则可以完成较复杂的信息处理。
###3.>Javascript的历程
>它最初由Netscape的BrendanEich设计。JavaScript是甲骨文公司的注册商标。Ecma国际以JavaScript为基础制定了ECMAScript标准。JavaScript也可以用于其他场合,如服务器端编程。完整的JavaScript实现包含三个部分:ECMAScript,文档对象模型,浏览器对象模型。
Netscape在最初将其脚本语言命名为LiveScript,后来Netscape在与Sun合作之后将其改名为JavaScript。JavaScript最初受Java启发而开始设计的,目的之一就是“看上去像Java”,因此语法上有类似之处,一些名称和命名规范也借自Java。但JavaScript的主要设计原则源自Self和Scheme。JavaScript与Java名称上的近似,是当时Netscape为了营销考虑与Sun微系统达成协议的结果。为了取得技术优势,微软推出了JScript来迎战JavaScript的脚本语言。为了互用性,Ecma国际(前身为欧洲计算机制造商协会)创建了ECMA-262标准(ECMAScript)。两者都属于ECMAScript的实现。尽管JavaScript作为给非程序人员的脚本语言,而非作为给程序人员的脚本语言来推广和宣传,但是JavaScript具有非常丰富的特性。
发展初期,JavaScript的标准并未确定,同期有Netscape的JavaScript,微软的JScript和CEnvi的ScriptEase三足鼎立。1997年,在ECMA(欧洲计算机制造商协会)的协调下,由Netscape、Sun、微软、Borland组成的工作组确定统一标准:ECMA-262。
**ECMAScript版本历史**
>**1998年6月,ECMAScript 2.0版发布。**
>**1999年12月,ECMAScript 3.0版发布,成为JavaScript的通行标准,得到了广泛支持。**
>**2007年10月,ECMAScript 4.0版草案发布**,对3.0版做了大幅升级,预计次年8月发布正式版本。草案发布后,由于4.0版的目标过于激进,各方对于是否通过这个标准,发生了严重分歧。以Yahoo、Microsoft、Google为首的大公司,反对JavaScript的大幅升级,主张小幅改动;以JavaScript创造者Brendan Eich为首的Mozilla公司,则坚持当前的草案。
>**2008年7月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激进,ECMA开会决定,中止ECMAScript 4.0的开发**,将其中涉及现有功能改善的一小部分,发布为ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为Harmony(和谐)。会后不久,ECMAScript 3.1就改名为ECMAScript 5。
>**2009年12月,ECMAScript 5.0版正式发布**。Harmony项目则一分为二,一些较为可行的设想定名为JavaScript.next继续开发,后来演变成ECMAScript 6;一些不是很成熟的设想,则被视为JavaScript.next.next,在更远的将来再考虑推出。
>**2011年6月,ECMAscript** 5.1版发布,并且成为ISO国际标准(ISO/IEC 16262:2011)。
>**2013年3月,ECMAScript** 6草案冻结,不再添加新功能。新的功能设想将被放到ECMAScript 7。
>**2013年12月,ECMAScript** 6草案发布。然后是12个月的讨论期,听取各方反馈。
>**2015年6月17日,ECMAScript 6发布正式版本,即ECMAScript 2015。**
ECMA的第39号技术专家委员会(Technical Committee 39,简称TC39)负责制订ECMAScript标准,成员包括Microsoft、Mozilla、Google等大公司。TC39的总体考虑是,ES5与ES3基本保持兼容,较大的语法修正和新功能加入,将由JavaScript.next完成。
###4.>开发环境搭建
开发工具:WebStorm、Atom、Sublime等
浏览器:谷歌浏览器,火狐浏览器,IE 浏览器,IETest 工具等。
###5.>课程要求
课程目的:为后续的cocos2d-js、白鹭引擎等提供Javascript语言基础课程。
学员要求:0基础学习,严格按照课程内容练习,必须完成课程作业。