张东
犀牛书
前端大全
正课:
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[i]//当前元素 } 何时: 今后只要对数组中每个元素执行相同的操作时 ***数组是引用类型的对象: 数据类型: 基础类型: 值直接存储在变量本地的数据类型 引用类型: 值无法保存在变量本地的数据类型 数据实际存储在变量外的一个独立存储空间 同时存储多个数据的一块存储空间-对象 每个对象都有唯一的内存地址 变量中仅保存对象的地址值! 访问变量等效于通过地址值找到具体对象去访问 数组就是引用类型的对象 ***按值传递: 两变量间赋值,或将变量作为参数传入函数时,其实仅将原变量中值,复制一个副本给对方变量: 对基础类型: 修改新变量的值,不影响原变量; 对引用类型: 仅复制对象的地址给对方,不创建新对象 通过新变量修改对象,等效于修改原对象 null vs undefined undefined: 空, 专门用于程序自动给变量初始化空值 null: 空, 专门用于程序员主动释放对一个对象的引用 垃圾回收: 内存中引擎会自动释放不再被任何变量引用的对象 垃圾回收器: 在内存中自动执行的小程序 自动释放不被任何变量引用的对象 好的习惯: 只要一个对象不再使用,都要主动将变量置为null 正课: 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. ***数组: 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
如果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 for(var c=0;c arr[r][c]//当前元素 } } 练习: Math.random() 生成0~1之间的一个随机小数 正课: 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); 两个日期对象可相减: 得到毫秒差 Javascript day05 正课: 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 返回值})(); Javascript day06 正课: 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,默认指.前的对象 构造函数的问题: 只能复用代码,不能节约内存 继承: 父对象的成员,子对象不用重复创建,也可直接使用 为什么: 即节约内存,又代码重用 何时: 只要一类子对象,需要相同的属性或功能时,都要将相同的属性和功能仅在父对象中定义一次即可 如何: 原型对象: 集中存储同一类型的子对象所需的所有共有属性和方法的父对象 Javascript day07 正课: 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. 让子类型原型对象继承父类型原型对象 Javascript day08 正课: 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值 Javascript day02
Javascript day03
Javascript day04