张东
zhangdong@tedu.cn
1. ***数组:
什么是:
为什么:
何时:
如何: 创建,访问元素,遍历
1. ***数组:
什么是: 内存中连续存储多个数据的一块存储空间
vs 变量: 内存中存储一个数据的存储空间
为什么: ***程序=数据结构+算法
算法: 解决问题的步骤
数据结构: 数据在内存中的存储结构
好的数据结构可以极大提高程序的执行效率
何时: 今后,只要连续存储多个相关的数据,都要用数组
如何: 创建,访问元素
创建: 3个场景:
1. 创建空数组: var 变量=[];
=new Array();
何时: 在创建数组时,暂时不知道数组中的元素内容
2. 创建数组时,初始化数组内容:
var 变量=[值1,值2,...];
=new Array(值1,值2,...);
何时: 在创建数组时,已经知道元素的内容
强调: 每个元素之间,必须用逗号分隔
3. 创建n个空元素的数组: var 变量=new Array(n);
何时: 在创建数组时,仅知道元素的个数,暂时不知道元素的内容。
访问数组中的元素:
下标: 数组中唯一标识每个元素存储位置的序号
默认从0开始,连续不重复
arr[i]: 访问arr数组中下标为i位置的元素值
数组中每个元素的用法和普通变量完全一样。
其实,数组也是一组变量的集合,再起一个统一的变量名
三个不限制:
1. 不限制元素的数据类型
2. 不限制元素的个数
3. 不限制下标越界:
赋值: 如果下标越界,不报错!而是在指定位置自动添加新元素。——稀疏数组: 下标不连续的数组
取值: 如果下标越界,不报错!而是返回undefined
length属性: 记录理论上数组的元素个数
length永远是最大下标+1
固定套路:
1. 最后一个元素: arr[arr.length-1]
2. 倒数第n个元素: arr[arr.length-n]
3. 在末尾追加新元素: arr[arr.length]=值
4. 清空数组: arr.length=0;
5. 删除末尾最后一个元素: arr.length--;
6. 删除末尾的n个元素: arr.length-=n;
遍历: 依次访问数组中每个元素:
for(var i=0;i<arr.length;i++){
arr[i]//当前元素
}
何时: 今后只要对数组中每个元素执行相同的操作时
***数组是引用类型的对象:
数据类型: 基础类型: 值直接存储在变量本地的数据类型
引用类型: 值无法保存在变量本地的数据类型
数据实际存储在变量外的一个独立存储空间
同时存储多个数据的一块存储空间-对象
每个对象都有唯一的内存地址
变量中仅保存对象的地址值!
访问变量等效于通过地址值找到具体对象去访问
数组就是引用类型的对象
***按值传递:
两变量间赋值,或将变量作为参数传入函数时,其实仅将原变量中值,复制一个副本给对方变量:
对基础类型: 修改新变量的值,不影响原变量;
对引用类型: 仅复制对象的地址给对方,不创建新对象
通过新变量修改对象,等效于修改原对象
null vs undefined
undefined: 空, 专门用于程序自动给变量初始化空值
null: 空, 专门用于程序员主动释放对一个对象的引用
垃圾回收: 内存中引擎会自动释放不再被任何变量引用的对象
垃圾回收器: 在内存中自动执行的小程序
自动释放不被任何变量引用的对象
好的习惯: 只要一个对象不再使用,都要主动将变量置为null
正课:
1. ***数组:
API:
拼接和选取
修改
翻转
****排序: 自定义排序算法: 冒泡排序
sort()
1. 拼接和选取:
拼接: 将其它元素或其它数组拼接到当前数组末尾,返回新数组
var newArr=arr1.concat(值1,值2,arr2,......)
强调: 1. 无权修改原对象,只能返回新对象
2. 打散传入的数组参数——珍贵
选取: 复制原数组中指定位置的元素组成新数组
var subArr=arr1.slice(starti,endi+1);
强调: 1. 无权修改原对象,只能返回新对象
2. 规律: 所有两个参数都是下标的API,都含头不含尾
简写: 1. 省略第二个参数: arr1.slice(starti)
从starti位置一直选取到结尾
2. 不写参数: arr1.slice()——复制整个数组中所有元素
3. 如果离结尾近:
arr1.slice(arr1.length-n,arr1.length-m+1)
选择倒数第n到倒数第m的元素
arr1.slice(-n,-m+1);
2. 修改数组splice: 删除,插入,替换
删除: arr.splice(starti,n) 从arr中starti开始,删除n个元素
强调: 1. 不遵循含头不含尾
2. 直接修改原数组
3. starti也支持负数参数,表示倒数第n个位置
简写: 省略第二个参数: arr.splice(starti) 删除starti后所有
返回值: var deletes=arr.splice(starti,n)
返回被删除的元素组成的临时新数组
插入: arr.splice(starti,0,值1,值2,...)
在arr中starti位置插入: 值1,值2,...
原starti位置及其之后的值,向后顺移
强调: 不能打散数组参数
替换: arr.splice(starti,n,值1,值2,...)
在arr中先删除starti位置后的n个元素
再在starti位置插入新元素
强调: 删除的元素个数和插入的新元素个数不必一样
数组自动调整length
3. 翻转数组: arr.reverse();
4. ****排序:
自定义排序算法: 冒泡,快速,插入
冒泡: 原理:
正课:
1. ***数组:
排序:
栈和队列:
二维数组:
2. ***String
1. 排序:
自定义排序: 冒泡
排序API: arr.sort();
大问题: 默认将所有元素转为字符串再按字符串排列
只能对字符串类型的元素正确排序
解决: 自定义比较规则:
比较器函数: 专门比较任意两值大小的函数:
要求: 两个参数: a,b
返回值: 如果a>b,就返回正数
如果a<b,就返回负数
如果a=b,就返回0
最简单的数字比较器函数:
function compare(a,b){return a-b;}
如何使用: 将比较器函数名作为参数传入sort函数中
arr.sort(compare) //强调: 不要加()
compare函数作为参数传入sort中,被sort反复调用
降序: 颠倒比较器函数的正负号,可改升序为降序
最简单的数字降序比较器函数:
function compare(a,b){return b-a;}
2. 栈和队列: js中没有专门的栈和队列类型
一切栈和队列都是用普通数组模拟的
栈: 一端封闭,只能从另一端进出的数组
特点: FILO
何时: 今后只要希望始终使用最新的元素时
如何:
1. 从结尾出入栈:
入: arr.push(值) => arr[arr.length]=值
出: var last=arr.pop()
特点: 每次出入栈,都不影响其他元素的位置
2. 从开头出入栈:
入: arr.unshift(值)
出: var first=arr.shift()
特点: 每次出入栈,其它元素位置都会依次顺移
队列: 只能从一端进入,从另一端出的数组
特点: FIFO
何时: 只要希望按先后顺序使用数组中的元素时
1. 结尾入: 入: arr.push(值)
2. 开头出: var first=arr.shift()
3. 二维数组:
什么是: 数组中的元素又引用了另一个子数组
何时: 1. 保存横行竖列的二维数据结构
2. 对一组数据,再进行细致分类时
如何:
创建: 2种:
1. 创建数组时,还不知道子数组的内容:
var arr=[];
arr[0]=[值1,值2,...];
arr[1]=[值1,值2,...];
2. 创建数组同时初始化元素:
var arr=[
[值1,值2,...],
[值1,值2,...],
...
]
访问: arr[r][c]->用法和普通数组元素的用法完全一样
强调: 二维数组,行下标r不能越界!——报错!
遍历: 外层循环控制行,内层循环控制列
for(var r=0; r<arr.length; r++){//遍历每一行
for(var c=0;c<arr[r].length;c++){//遍历第r行中每一列
arr[r][c]//当前元素
}
}
练习: Math.random() 生成0~1之间的一个随机小数
正课:
1. ***数组:
关联数组:
API:
转字符串
拼接和选取
修改
翻转
1. 关联数组:
索引数组: 下标为数字的数组
问题: 每个元素,只有值,没有有意义的名称
解决: 关联数组: 可自定义下标名称的数组
如何:
创建: 2步: 1. 先创建空数组: var scores=[]
2. 在空数组中添加新元素,使用自定义的下标名
scores["MATH"]=81;
访问元素: 用自定义的下标名称:
scores["MATH"] 用法和普通变量完全一样
关联数组原理:
hash算法: 散列: 接收一个字符串,然后根据字符串计算出一个尽量不重复的数字。
相同字符串,计算出的数字一定相同
不同的字符串,计算出的数字几乎不同的
关联数组: hash数组
存入元素: 将下标名交给hash算法,计算出一个尽量不重复的序号,将元素存储到数组中序号指定的位置
获取元素: 将下标名交给hash算法,计算出和存入时完全一样的序号,直接去数组中序号位置获取元素值——不用遍历
为什么: 1. 因为下标有意义,便于维护
2. 查找极快——和总元素个数以及存储位置无关!
何时: 1. 今后只要快速查找某个元素时,都用hash数组
2. 今后只要存储有意义的一组元素时,都用hash数组
鄙视: 谈谈对hash的理解:
hash算法; 存入; 获取; 优;
特点: length属性失效,永远为0
遍历: for in循环:
for(var key in hash){//in依次获得每个元素的下标名称
key//自动获得当前元素的下标名
hash[key]//当前元素值
}
2. 数组API: 浏览器已经实现的,我们直接用的函数
数组对象: 存储一组数据,提供操作数据的API
1. 数组转为字符串: 2种:
1. var str=String(arr): 将arr中每个元素转为字符串,再用逗号链接成一个字符串。
2. 可自定义连接符:
var str=arr.join("自定义连接符")
固定套路: 1. 无缝拼接: arr.join("")
2. 判断数组是空数组: arr.join("")===""
3. 动态生成页面元素的内容
正课:
1. ***String
什么是:
***内置对象:
***包装类型:
字符串API
1. 什么是: 多个字符组成的只读字符数组
vs 数组: 下标i
length
slice() concat
不同: 数组中凡是直接修改原数组的API,字符串都不能用!
2. 内置对象: ES标准中规定的,浏览器厂商已经实现的现成的对象和API
11个: Number String Boolean
Array Date RegExp Math
Error
Function Object
Global(浏览器中被替换为window)
3. 包装类型对象:
什么是: 专门封装基础类型的值,并提供操作基础类型值的API的对象
为什么: 基础类型的值,本身不包含任何API功能
何时: 只要试图对基础类型的值调用API时,都会自动创建对应类型的包装类型对象来封装基础类型的值。
调用后: 包装类型对象,自动释放!
比如: var n=345.678;
n.toFixed(2)=>345.678.toFixed(2)
=>new Number(345.678).toFixed(2)
4. String的API:
***所有字符串API都无权修改原字符串,只能返回新字符串!
大小写转换: 将字符串中所有英文字母转为统一的大小写
何时: 只要不区分大小写时,都要先转为一致的大小写,再判断。 比如: 用户名,邮箱地址,验证码
如何: str.toUpperCase() //都转大写
str.toLowerCase() //都转小写
获得指定位置的字符: str[i]
var char=str.charAt(i);
获得指定字符的unicode号:
var unicode=str.charCodeAt(i); //省略i,默认是0
将unicode号反向转回文字
var char=String.fromCharCode(unicode);
选取子字符串: str.slice(starti,endi+1)
str.substring(starti,endi+1) 不支持负数参数
str.substr(starti,n): 选取starti开始的n个元素
正课:
1. ***String API
查找关键词
替换
切割字符串
2. *****正则表达式
1. 查找关键词: 4种
1. 查找一个固定的关键词出现的位置:
var i=str.indexOf("关键词",fromi)
在str中,从fromi位置开始查找"关键词"的位置
如果找到,返回关键词所在位置的下标
找不到,返回-1
简写: 省略fromi,默认从0开始
专门找最后一个关键词的位置:
var i=str.lastIndexOf("关键词")
在str中,找最后一个关键词出现的位置
问题: 只能找第一个关键词
解决: 正则表达式:
2. 使用正则表达式查找指定的一类关键词的位置:
按模式匹配:
var i=str.search(/正则表达式/);
在str中查找第一个符合正则表达式要求的关键词的位置
返回值: 找到的关键词的下标, 如果找不到返回-1
何时: 仅判断是否包含敏感词时,就用search
如果返回不是-1,说明包含,否则说明没找到
忽略大小写: /正则/i
问题: 1. 只能获得第一个的位置,不能获得所有敏感词
2. 只能返回位置,不能返回内容
3. 使用正则表达式查找指定的一类关键词的内容:
var arr=str.match(/正则/ig);
默认只找第一个,找所有,必须加g
返回值: 所有敏感词组成的数组
没找到返回null!
强调: 如果一个API有可能返回null,就必须先判断不是null,再使用!
arr.length 表示找到的关键词个数
问题: 仅返回所有关键词的内容,无法返回每个关键词位置
4. 即找所有关键词内容,又找每个关键词的位置?
reg.exec();
2. 替换: 将字符串中所有敏感词替换为新内容
基本替换:
str=str.replace(/正则/ig,“替换值”);
4. *****正则表达式:
什么是: 专门定义一类字符串统一规则的表达式
何时: 1. 按照指定规则模糊查找一类关键词时
2. 表单中验证输入项的格式
如何: 语法:
1. 最简单的正则其实就是关键词原文
2. 字符集: 规定字符串中一位字符可用的备选字符列表
何时: 只要某一位字符,有多个备选字时
如何: [备选字符列表]
强调: 一个字符集只能匹配一位字符
简写: 如果备选字符列表是连续的,就可用-省略中间字符
一位字母: [a-zA-Z]
一位数字: [0-9]
一位汉字: [\u4e00-\u9fa5]
特殊: 除了: [^排除的字符列表]
强调: ^必须写在[开头]
3. 预定义字符集: 4个:
\w 一位字母数字或_ =>[a-zA-Z0-9_]
\d 一位数字 => [0-9]
\s 一位空字符: 空格,Tab,...
. 一位任意字符
强调: 一个预定义字符集仅匹配一位字符
只有规则和预定义字符完全一致时,才能使用
如果不一致, 依然需要手写普通字符集
字符集仅控制每个字符的内容
4. 量词: 专门固定字符出现的次数
有明确数量边界:
字符集{min,max} 规定字符集必须最少出现min次
最多max次
字符集{min,} 最少min次, 多了不限
字符集{n} 必须n次
没有明确数量边界:
字符集? 可有可无,最多一次
字符集* 可有可无,多了不限
字符集+ {1,}
强调: 仅修改相邻的前一个字符集
5. 选择和分组:
分组: 将多个字符集分成一组:
何时: 如果希望一个量词同时修饰多个字符集时
比如: 我(了个?)?去: 我去 我了去 我了个去 我个去X
regexper.com
选择: 其实就是"或" 规则1|规则2
只要匹配任意一个规则即可
(微|w(ei)?)\s*(信|x(in)?)
手机号:
(\+86|0086)? +86或0086 可有可无,最多一次
\s* 空字符 可有可无,多了不限
1
[34578] 34578中挑一个
\d{9} 9位数字
(\+86|0086)?\s*1[34578]\d{9}
邮箱:
字母/数字或_ 一次以上
@
字母或数字 2位以上
(.和 字母或数字 2到3位) 1到2次
\w+@[a-zA-Z0-9]{2,}(.[a-zA-Z0-9]{2,3}){1,2}
正课:
1. 正则:
指定匹配位置
2. ***String API:
替换: 衍生: 删除和格式化
切割
3. ***RegExp对象
1. 正则:
指定匹配位置: 三个位置:
字符串的开头 ^
字符串的结尾 $
比如: 开头的空字符: ^\s+
结尾的空字符: \s+$
开头或结尾的空字符^\s+|\s+$
固定套路: 只要希望字符串和正则从头到尾完全匹配
比如同时前加^后加$
只要用正则表达式执行验证时,必须前加^后加$
单词边界 \b 包含: ^ $ 空格 标点
比如: 单词首字母: \b[a-z]
单词尾字母: [a-z]\b
单独的一个单词no: \bno\b
2. ***StringAPI
替换: 简单替换: str=str.replace(/正则/ig, "替换值");
问题: 不能根据不同的关键词,选择不同的值替换
解决: 高级替换:
str=str.replace(/正则/ig,function(kw){
//kw会自动获得本次找到的关键词内容
return //根据不同kw,返回不同的替换值
})
何时: 只要根据不同的关键词,替换不同内容时
衍生:
删除: 将关键词替换为""
格式化: 将原字符串重新拼接为新的格式
比如: "19831226" => "1983年12月26日"
2步: 1. 正则将原字符串分组
/(\d{4})(\d{2})(\d{2})/
// 1 2 3
2. 使用简单替换: str.replace(/正则/,"...$n...")
$n可自动获得第n个分组的子内容
n从1开始
切割: 将原字符串,按指定字符,分隔为多个子字符串
如何: var substrs=str.split(/正则/)
返回切割后的多个子字符串组成的数组
结果中,不再包含分隔符
固定套路: 将字符串打散成字符数组: var chars=str.split("")
3. ***RegExp:
什么是: 封装一条正则表达式, 提供了使用正则表达式进行查找和验证的API
何时: 1. 查询关键词的第四种情况: 即查内容,又查位置
2. 利用正则表达式执行验证
如何:
创建: 2种:
1. 如果正则表达式是固定不变的: var reg=/正则/ig;
强调: /正则/中正则中的/都要转义为\/
2. 如果正则表达式是动态生成的:
var reg=new RegExp("正则"[,"ig"]);
强调: "正则"中的" \ 都要转义为\" \\
比如: "\d{6}" => "\\d{6}"
正课:
1. ***RegExp:
2. Math
3. ***Date
1. ***RegExp
API:
验证: 检查字符串是否完全符合正则表达式的要求!
如何: var bool=reg.test(待检测字符串)
强调: 只要验证,reg中必须前加^后加$
查找关键词: 第四种情况: 即找内容,又找位置
如何: var arr=reg.exec(待查找的完整字符串)
在"待查找的完整字符串"中,依次查找每个符合reg要求得关键词。
返回值: 本次找到的一个关键词及其位置
arr[0]: 关键词内容
如果正则中有分组
arr[n]: 自动保存第n个分组匹配的子内容
arr["index"]: 当前关键词位置 -> 可简写为arr.index
如果没找到,返回null
每次查找后,都将reg.lastIndex属性(下次开始位置)修改为当前index+关键词长度,相当跳过当前关键词继续向后找
固定套路: 找所有关键词:
while((arr=reg.exec(str))!=null){
arr[0] 关键词内容
arr[n] 自动获得第n个分组的子内容
arr.index 当前关键词位置
}
如果只需要分组的子字符串,不需要完整关键词:
可省略arr,用RegExp.$n
while(reg.exec(str)!=null){
RegExp.$n 自动获得第n个分组的子内容
}
练习:
贪婪模式: 正则表达式默认匹配最长的符合条件的子字符串
默认使用贪婪模式
.* .+
懒惰模式: 仅匹配最短的符合条件的子字符串
贪婪->懒惰: .*? .+?
2. Math:
什么是: 专门封装数学计算所用常量,并提供数学计算所用API
何时: 只要数学计算时
特点: 不能new!
API:
1. 取整:
Math.ceil(num) 上取整: 只要超过,就取下一个整数
Math.floor(num) 下取整: 省略小数部分
Math.round(num) 四舍五入取整:
vs toFixed(d):
1. 小数位数: Math.round()只能取整,不能规定小数位数
toFixed(d)可取整,也可规定小数位数
2. 返回值: Math.round()返回number
toFixed(d)返回string
自定义round函数:
2. 乘方和开平方:
乘方: Math.pow(底数, 幂)
开平方: Math.sqrt(n)
3. 最大值和最小值:
Math.max(值1,值2,...);
Math.min(值1,值2,...);
问题: 不支持数组
解决: Math.max.apply(null,arr)
4. 随机数:
Math.random() 0<=r<1 随机小数
从min~max之间取随机整数:
Math.floor(Math.random()*(max-min+1)+min)
从0~n之间去随机:
Math.floor(Math.random()*(n+1));
3. ***Date
什么是: 封装一个时间,提供操作时间的API
何时: 只要存储时间,都要用Date对象
如何:
创建: 4种:
1. 创建日期对象,并自动获得当前客户端系统时间
var now=new Date();
2. 创建日期对象,并封装自定义时间:
var date=new Date("yyyy/MM/dd hh:mm:ss");
var date=new Date(yyyy,MM-1,dd,hh,mm,ss)
3. 复制一个日期对象:
问题: 日期对象的计算都是直接修改原日期对象
计算后无法保留计算前的旧时间
解决: 今后如果需要同时保留开始和结束时间时
都要先将开始时间复制一个副本,再用副本计算
var date1=new Date(...);
var date2=new Date(date1);
4. 用毫秒数创建日期对象:
Date对象的原理:
Date对象中保存了一个巨大的毫秒数
起始时间为: 1970年1月1日0点至今的毫秒数
var date=new Date(ms);
两个日期对象可相减: 得到毫秒差
正课:
1. ***日期API
2. ***Error
1. ***日期API
单位: FullYear Month Date Day
Hours Minutes Seconds Milliseconds
API: 1. 每个单位都有一个对儿get/set方法
比如: var year=date.getFullYear()//获取单位的值
date.setFullYear(year)//设置单位的值
特殊: Day没有set方法
2. 命名: 年月日星期没有s结尾
时分秒毫秒都有s结尾
3. 取值范围: 只有Date从1~31
不考虑FullYear, 其余都是从0开始,到进制-1结束
Month: 0~11 总比现实中小1, 需要修正
Date: 1~31 不用修正
Day: 0~6 不用修正
Hours: 0~23 不用修正
Minutes/Seconds: 0~59 不用修正
日期计算:
1. 计算两个日期的时间差: 两日期相减,得毫秒数,再换算
2. 对任意单位做加减: 3步:
1. 取分量: var d=date.getDate();
2. 做加减: d+=60
3. 放回去: date.setDate(d);
强调: 所有set方法可自动调整时间进制
其实可简写为: date.setDate(date.getDate()+60)
转字符串:
date.toString() -> 当地时间的完整时间格式
date.toLocaleString() ->当地时间的简化版格式
date.toLocaleDateString() -> 当地时间的日期部分
date.toLocaleTimeString() -> 当地时间的时间部分
date.toGMTString() -> 标准时区的标准时间
作业: 自定义format函数: 2_format.html
2. ***Error
什么是错误(bug): 程序执行过程中遇到的异常中断。
一旦发生错误,程序立刻退出。
什么是错误处理: 即使程序发生错误,也能保证程序不异常中断的一种机制。
如何:
try{
可能发生错误的代码
}catch(err){//仅在发生错误时,才执行
错误处理代码: 1. 提示用户错误信息(String(err))
2. 记录系统日志
}finally{
无论是否发生错误,都必须执行的代码。
比如: 释放资源!
}
err: Error对象: 在错误发生时,自动创建的,保存错误信息的对象。
错误类型6种:
SyntaxError 语法错误
ReferenceError 要使用的变量没找到
TypeError 错误的调用了对象的方法
RangeError 参数范围越界 比如: toFixed(d) 0~20
EvalError URIError
正课:
1. ***错误处理
2. ***Function
*****闭包
1. ***错误处理
只要可以提前预料的错误,都要用if...else...来代替try catch
只有无法提前预料的错误,采用try catch
主动抛出错误:
为什么: 抛出错误通常是为了提醒使用者错误的使用的程序
如何: throw new Error("错误消息")
2. ***Function:
什么是: js中一切函数都是对象
函数对象是专门封装函数定义的对象。
创建: 3种:
1. 声明: function 函数名(参数列表){函数体; return 返回值;}
何时: 只要一段代码被反复使用,都要先定义在一个专门的函数中,再反复调用函数即可——复用
何时使用参数: 只要函数步骤中必须某些数据才能正常执行时,就要定义参数。
何时使用返回值: 如果函数的调用者需要函数的执行结果时,函数就必须返回值。
可被声明提前:
2. 函数直接量:
var 函数名=function(参数列表){函数体; return 返回值;};
不会被声明提前。
****声明提前(hoist): 在开始执行程序前,将所有var声明的变量和function声明的函数提前到*当前作用域*的顶部,集中创建。
赋值留在原地!
何时: 只要不希望被声明提前时。
揭示了: 函数名仅是一个普通的变量
函数定义其实是一个对象
函数名中仅保存了函数对象的地址——引用
3. 用new:
var fun=
new Function("参数1","参数2",...,"函数体; return 返回值")
比如: function compare(a,b){return a-b;}
var compare=function(a,b){return a-b;}
var compare=new Function("a","b","return a-b;");
***重载(overload):
什么是: 相同函数名,不同参数列表的多个函数,在调用时,可根据传入参数的不同,自动选择对应的函数调用!
为什么: 减轻调用者的负担,一个函数名,可执行多种操作
何时: 一项任务,根据不同的参数,执行不同的操作流程时
如何: js语法不支持重载效果
变通: 所有函数对象内,都自动内建了一个arguments对象
arguments对象:
专门保存传入函数的所有参数值的类数组对象
类数组对象: (object like array)
vs 数组: 相同: 下标, length, for遍历
不同: 类数组对象是Object,不是Array,无法使用Array的API
数组是Array类型,可以使用数组类型所有的API
匿名函数:
什么是: 函数创建时,不被任何变量引用的函数
为什么: 节约内存
何时: 如果一个函数只用一次,用完希望自动释放时
1. 回调callback: 将函数作为参数传递给另一个函数去调用
比如: arr.sort(function (a,b){return a-b});
str.replace(/reg/g,function(kw,$1,...){return ...})
2. 自调: 创建函数后立刻调用自己!
何时: 如果一个函数只执行一次,不会再重用时
为什么: 建立临时作用域!避免全局污染!
如何:
(function(参数列表){函数体; return 返回值})();
正课:
1. *****作用域和作用域链
2. *****闭包
1. *****作用域和作用域链
作用域scope:
什么是: 一个变量的使用范围——使用
本质上作用域是一个对象——存储
作用域中的变量都是对象的成员
程序/函数的执行过程:
1. 开始执行程序前:
创建ECS(执行环境栈):
依次保存每个调用的函数的执行环境
在ECS中压入第一个全局执行环境(全局EC)
创建window对象,全局EC引用window对象
window就是全局作用域对象
2. 开始执行程序:
所有全局变量都保存在全局作用域对象window中
3. 定义函数时:
在全局添加函数名变量
创建函数对象封装函数定义
函数名变量引用函数对象
函数对象中有一个scope属性,引用回创建函数时的作用域对象。通常都是window。
4. 调用函数时:
在ECS中压入一个新的EC
为本次函数调用创建专门的活动对象(AO)
在AO中创建所有函数定义中规定的局部变量
其实AO就是函数作用域对象
所有局部变量都是AO的成员
新的EC引用活动对象AO
AO的parent指向window
变量的使用顺序:
先用AO(函数作用域)中的局部变量
如果AO中没有,才去window(全局作用域)中找
5. 函数调用后:
本次函数调用的EC出栈
导致函数作用域对象AO释放
导致局部变量一同释放
作用域链(scope chain): 由多个作用域对象连续引用形成的链式结构。
顺序: 先函数作用域对象AO->全局作用域对象window
所有的变量都保存在作用域链上的对象中
局部变量都保存在函数作用域对象AO中
全局变量都保存在全局作用域对象window中
控制了: 变量的使用顺序
先用AO(函数作用域)中的局部变量
如果AO中没有,才去window(全局作用域)中找
闭包:
什么是: 即重用变量,又防止变量被污染的一种机制
为什么: 全局变量: 优: 可重用 缺: 易被全局污染
局部变量: 优: 不会被污染 缺: 不可重用
何时: 即重用变量,又防止变量被污染
如何: 3步:
1. 用外层函数包裹住受保护的变量和操作变量的内层函数
2. 外层函数将内层函数返回到外部,被外部的变量保存
3. 通过外部变量调用内层函数,访问受保护的变量
缺: 1. 占用更多内存: 外层函数的AO
2. 容易造成内存泄漏
三特点: 1. 函数嵌套:
2. 外层函数包含一个受保护的局部变量
3. 外层函数将内层函数对象返回
回顾:
1. *****闭包:
鄙视: 快速绘制闭包图
1. 受保护的变量,并确定外层函数调用后,变量的值
2. 找所有操作受保护变量的内层函数
正课:
1. *****面向对象OOP:
什么是: 程序中都是先用对象来定义数据和功能,再按照逻辑的需要,访问对象中的数据和功能。
为什么: 和现实中人的想法非常接近。
什么是对象: 内存中同时存储多个数据和功能的存储空间
描述现实中一个具体事物的属性和功能的程序结构
事物的属性,会成为对象中的属性
事物的功能,会成为对象中的方法
何时: 今后开始写程序前,都要先用对象,描述好要操作的事物的属性和功能,再按需使用对象的功能,访问对象的属性
如何: 面向对象三大特点: 封装,继承,多态
封装: 将一个具体事物的属性和功能集中定义在一个对象中
创建自定义对象: ——封装 3种:
1. 使用对象直接量:
var obj={
属性名: 属性值,
... : ... ,
方法名: function(){... this.属性名 ...},
... : ... ,
}
强调: 对象自己的方法,要访问自己的属性,必须用this.属性名.
this->正在调用函数的当前对象自己
2. 使用new: 2步:
var obj=new Object(); //创建一个空对象
//向空对象中添加属性和方法
obj.属性名=属性值;
obj.方法名=function(){...this.属性名...};
对象的本质: js中一切对象的底层都是关联数组
每个属性/方法都是关联数组中的元素
属性名/方法名是key,属性值/函数对象是value
问题: 一次只能创建一个对象
3. 解决: 用构造函数:
什么是构造函数: 专门描述一类对象统一结构的函数
何时: 今后只要反复创建多个相同结构的对象时,都要先定义构造函数
为什么: 复用对象的结构代码
如何: 2步:
1. 定义构造函数
function 类型名(属性参数列表){
this.属性名=属性参数值;
...=...;
this.方法名=function(){ ... this.属性名 ... }
}
2. 用new调用构造函数,创建并装修新对象
var obj=new 类型名(属性值列表);
创建一个指定“类型”的对象
用new调用指定"类型"的构造函数来创建对象
new: 4件事:
1. 创建新的空对象
2. 让新对象继承构造函数的原型对象
3. 用新对象去调用构造函数
向新对象中添加构造函数规定的属性
将属性参数的值,保存到新对象的新属性中
向新对象中添加构造函数规定的方法
4. 将新对象的地址保存在变量
按需访问对象的属性,调用对象的方法:
访问对象的属性: obj.属性名 用法和普通的变量完全一样
属性就是保存在对象中的一个变量
调用对象的方法: obj.方法名() 用法和普通的函数完全一样
强调: 方法中的this,默认指.前的对象
构造函数的问题: 只能复用代码,不能节约内存
继承: 父对象的成员,子对象不用重复创建,也可直接使用
为什么: 即节约内存,又代码重用
何时: 只要一类子对象,需要相同的属性或功能时,都要将相同的属性和功能仅在父对象中定义一次即可
如何:
原型对象: 集中存储同一类型的子对象所需的所有共有属性和方法的父对象
正课:
1. *****OOP
内置对象的原型对象
共有属性和自有属性
原型链
原型相关API
*****自定义继承
1. 内置对象的原型对象:
所有内置对象都是一个构造函数(除Math外)
每类内置对象都有自己的原型对象(prototype)
所有内置对象的API都保存在类型.prototype对象中
何时: 解决浏览器兼容问题: 2步:
如果类型.prototype.方法===undefined
类型.prototype.方法=function(...){
this->自动获得将来调用该方法的当前对象
}
2. 共有属性和自有属性:
自有属性: 直接保存在对象本地的属性
共有属性: 保存在父级原型对象中的属性
访问共有/自有属性:
读取属性值: 即可用子对象读取,也可用原型对象读取
修改属性值:
自有属性: 子对象.属性名=值
共有属性: 原型对象.属性名=值
如何判断自有还是共有:
自有: var bool=obj.hasOwnProperty("属性名")
判断obj中是否包含自有属性"属性名"
共有: 不是自有! 且 子对象可访问到!
3. ***原型链(prototype chain):
什么是原型链: 多级父对象连续继承,形成的链式结构
保存了: 对象的属性和方法
控制了: 对象的成员的使用顺序
优先使用自有成员
自己没有才延原型链向父级查找,只要找到就不再向上
如果整个原型链上都没有,才返回undefind
vs 作用域链(scope chain)
保存了: 全局/局部的变量
控制了: 变量的使用顺序
优先在当前函数调用的函数作用域对象(AO)中查找
如果函数作用域对象(AO)中没有,才延作用域链向全局方向查找。只要找到,就不再继续
如果整个作用域链上都没有,才报错
鄙视题: 判断一个对象是不是数组类型,有几种方式
0. typeof只能区分基础类型和function
无法进一步区分对象的类型
1. 判断原型对象:
如果obj的原型是Array.prototype说明是数组
obj.__proto__==Array.prototype
问题: __proto__是内部属性,本来浏览器是禁止使用的
解决: Object.getPrototypeOf(obj)
获得obj的原型对象
正课:
1. *****OOP
原型链
*****自定义继承
1. 原型链:
判断一个对象是不是数组类型,有几种方法: 4种
0. typeof X
1. 判断原型对象:
obj.__proto__==Array.prototype
问题: __proto__是内部属性,可能不允许使用
Object.getPrototypeOf(obj)==Array.prototype
问题: 只能判断直接父对象是Array.prototype的情况
无法判断间接继承Array.prototype的情况
解决: var bool=father.isPrototypeOf(child)
判断father是否是child的父级对象
不但检查直接父对象,且检查整个原型链!
2. 判断构造函数:
每个原型对象都有一个constructor属性指回构造函数
obj.constructor==Array
还可以用: obj instanceof Array
instance: 实例-用一个构造函数创建出的一个具体对象
比如: var lilei=new Student(...);
称lilei是Student类型的一个实例
实例化一个Student类型的对象lilei
检查整个原型链
要求不够严格: 只要有继承关系,就认为是数组
要求严格: 只有用数组类型创建的对象,才是真正的数组。
3. 检查对象的class属性
什么是class: 对象的内部属性,专门保存创建对象时使用的类型名。
只有一个办法获得class属性值:
调用Object.prototype.toString();->"[object Class]"
问题: 所有内置对象都重写了Object中的toString
重写(override): 如果子对象觉得,父对象的成员不好用,可在本地定义同名的自有成员,覆盖父对象中的。
——多态
解决: 用call强行借用
call: 强行借用一个本无法调用的方法
何时: 想调用一个原本无法调用的方法
如何: 想借用的函数.call(要替换的对象)
比如: Object.prototype.toString.call(obj)
相当于: obj.toString()
返回: "[object Class]"
4. Array.isArray(obj);
问题: ES5 IE9+
解决: 自定义isArray方法
鄙视题: 对象实例方法 vs 构造函数方法
对象实例方法: 必须用一个对象实例才能调用的方法
仅当前类型的对象可用!
对象实例方法一律保存在该类型的原型对象中,所有子对象共用。
何时: 一个方法只希望当前类型的子对象才能使用时
构造函数方法: 不需要任何对象实例,用构造函数即可直接调用的方法。
构造函数方法一律保存在构造函数对象上
何时: 一个方法的执行和对象的类型无关时
2. *****自定义继承关系:
1. 修改单个对象的继承关系:
obj.__proto__=father;
问题: 内部属性: Object.setPrototypeOf(child,father);
设置child继承father
2. 批量修改多个对象的继承关系:
直接修改构造函数的prototype引用新的父对象
obj.prototype=father
强调: 必须在创建第一个子对象之前就换
3. 两种类型间的继承: 最像Java的继承
何时: 只要两种类型间包含相同的属性结构定义或相同的方法。
如何: 3步:
1. 抽象出一个父类型
共同的属性定义,集中到父类型的构造函数中
共同的方法,集中到父类型的原型对象中
2. 在子类型构造函数中借用父类型构造函数
不能直接调用: this->window
应该用call,将this替换为当前正在创建的新对象
父类型构造.call(this,参数...)
3. 让子类型原型对象继承父类型原型对象
正课:
1. *****ES5
对对象的保护:
对单个属性的保护:
数据属性:
访问器属性:
对对象的保护:
问题: 属性可随时直接用=赋值任何值
属性可随时被访问
可随时添加和删除属性
——不严格!
解决: 对对象提供保护:
如何:
1. 对对象的属性提供保护
将对象的属性分两大类:
1. 命名属性: 可随时通过.属性名方式访问的属性
又分为2类:
1. 数据属性: 实际存储属性值的属性
如何保护: 每个属性都包含四大特性:
{
value: 实际存储属性值,
writable: true/false, //是否可修改
enumerable: true/false,//是否可for in遍历
//依然可用.访问到
configurable: true/false,
//1. 是否可修改前两个特性
//2. 是否可删除当前属性
//一旦改为false,不可逆!
}
特殊: 如果要定义的属性不存在:
defineProperty会自动添加:
自动添加后,属性的特性值都为false
问题: 只能提供基本(只读,遍历,删除)保护
无法按照自定义规则保护属性
解决:
2. 访问器属性: 不实际存储属性值
专门对其它属性提供验证保护
何时: 只要按照自定义规则保护属性
如何: 也有四大特性:
{
get:function(){return 受保护的属性值},
set:function(val){
验证要赋的新值val
验证通过才将val保存到受保护的属性中
},
enumerable:true/false,
configurable:true/false,
}
当通过访问器属性获取受保护的属性值时
自动调用get方法
当通过访问器属性为受保护的属性赋值时
自动调用set方法
参数val,自动获得要赋的新值
大问题: 受保护的属性值应该保存在哪儿?
才能做到比人不能直接用,只能通过访问器属性访问
解决: 闭包!
2. 内部属性: 无法通过.属性名方式访问到的属性
class Object.prototype.toString.call(obj)
__proto__ Object.getPrototypeOf(obj)
Object.setPrototypeOf(child,father)
2. 对整个对象提供保护
正课:
1. *****ES5
对对象的保护:
对属性的保护
防篡改
Object.create();
数组API:
*****bind()
1. 对对象的保护:
对属性:
命名属性
数据属性:
访问器属性:
大问题: 受保护的属性值应该保存在?
解决: 闭包
内部属性
防篡改: 禁止修改对象的属性结构
3个级别:
1. 防扩展: 禁止向对象中添加新属性
Object.preventExtensions(obj)
2. 密封: 即防扩展,又禁止删除旧属性
Object.seal(obj)
其实是将所有属性的configurable设置为false
3. 冻结: 即密封,又禁止修改所有属性值!
何时: 如果一个对象中保存了大量不变的属性值时
Object.freeze(obj);
其实是将所有属性的writable设置为false!
2. Object.create():
var newObj=Object.create(father,{扩展的新属性})
创建一个新对象newObj,继承father,并为newObj扩展新的自有属性
何时: 只要继承一个现有对象,创建一个新的子对象时
相当于: var newObj={};
newObj.__proto__=father;
Object.defineProperties(newObj,{
扩展的新属性
})
3. 数组API:
1. 判断: 数组中的元素是否符合要求
1. 所有元素是否都符合要求
var bool=
arr.every(function(val,i,arr){ return 判断条件 }) 2. 是否包含符合要求的元素
var bool=
arr.some(function(val,i,arr){ return 判断条件 })
2. 遍历API: 依次对数组中每个元素执行相同的操作
1. 对原数组中每个元素执行相同的操作,结果保存回原数组
arr.forEach(function(val,i,arr){ arr[i]=新值; });
2. 取出原数组中每个元素的值,执行相同的操作后,保存到一个新数组中
var newArr=arr.map(function(val,i,arr){
return 操作后的元素值
});
3. 过滤和汇总:
过滤: 选择原数组中符合条件的元素,组成新数组
var subArr=arr.filter(function(val,i,arr){
return 判断条件;
});
汇总: 将原数组中每个元素统计出一个汇总结果
var r=arr.reduce(function(prev,val,i,arr){
return prev+val;
},0);
其中: 0: 表示初始值
prev: 截止到目前的阶段汇总值
回调函数的返回值,自动作为下次的prev值