1.HTML和CSS
Html5新增标签和属性
增加标签: 1、结构标签 (1)section:独立内容区块,可以用h1~h6组成大纲,表示文档结构,也可以有章节、页眉、页脚或页眉的其他部分; (2)article:特殊独立区块,表示这篇页眉中的核心内容; (3)aside:标签内容之外与标签内容相关的辅助信息; (4)header:某个区块的头部信息/标题; (5)hgroup:头部信息/标题的补充内容; (6)footer:底部信息; (7)nav:导航条部分信息 (8)figure:独立的单元,例如某个有图片与内容的新闻块。 2、表单标签 (1)email:必须输入邮件; (2)url:必须输入url地址; (3)number:必须输入数值; (4)range:必须输入一定范围内的数值; (5)Date Pickers:日期选择器; a.date:选取日、月、年 b.month:选取月、年 c.week:选取周和年 d.time:选取时间(小时和分钟) e.datetime:选取时间、日、月、年(UTC时间) f.datetime-local:选取时间、日、月、年(本地时间) (6)search:搜索常规的文本域; (7)color:颜色 3、媒体标签 (1)video:视频 (2)audio:音频 (3)embed:嵌入内容(包括各种媒体),Midi、Wav、AU、MP3、Flash、AIFF等。 4、其他功能标签 (1)mark:标注(像荧光笔做笔记) (2)progress:进度条;<progress max="最大进度条的值" value="当前进度条的值"> (3)time:数据标签,给搜索引擎使用;发布日期<time datetime="2014-12-25T09:00">9:00</time>更新日期<time datetime="2015- 01-23T04:00" pubdate>4:00</time> (4)ruby和rt:对某一个字进行注释;<ruby><rt>注释内容</rt><rp>浏览器不支持时如何显示</rp></ruby> (5)wbr:软换行,页面宽度到需要换行时换行; (6)canvas:使用JS代码做内容进行图像绘制; (7)command:按钮; (8)deteils :展开菜单; (9)dateilst:文本域下拉提示; (10)keygen:加密; 新增的属性: 对于js进行添加的属性。 <script defer src=".....js" onload="alert('a')"></script> <script async src=".....js" onload="alert('b')"></script> 如果没有以上两个属性的话,执行顺序为先加载(下载)第一个src,然后在执行其onload,然后在向下依次同步执行defer属性在h5之前就已经有了,输入延迟加载(推迟执行),它会先加载(下载)src中文件内容,然后等页面全部加载完成后,再加载onload中js.async属性属于异步加载,它会在加载src后,立即执行onload,同时还会继续加载页面以上执行顺序,alert显示会先显示b然后再显示a 网页中标签中加入小图标的样式代码 <link rel="icon" href="url..." type="图片名称" sizes="16*16"> 有序列表ol:新增start(列表起始值),reversed(是否倒置)menu菜单type属性(3个菜单类型)内嵌css样式:在标签内部来定义一个样式区块(scoped),只对样式标签内部才有效内嵌框架:iframe元素,新增了seamless无边距无边框,srcdoc定义了内嵌框架的内容 <iframe>新增属性: <!--seamless定义框架无边框 无边距--> <!--srcdoc的显示级别比sandbox高--> <!--sandbox用来规定一个内嵌框架的安全级别--> <!--sandbox="allow-forms:允许提交表单"--> <!--sandbox="allow-origin:允许是相同的源"--> <!--sandbox="allow-scripts:允许执行脚本"--> <!--sandbox="allow-top-navigation:允许使外面的页面进行跳转"--> manifest属性: 定义页面需要用到的离线应用文件,一般放在<html>标签里 charset属性: meta属性之一,定义页面的字符集 sizes属性: <link>新增属性,当link的rel="icon"时,用以设置图标大小 base属性: <base href="http://localhost/" target="_blank">表示当在新窗口打开一个页面时,会将href中的内容作为前缀添加到地址前 defer属性: script标签属性,表示脚本加载完毕后,只有当页面也加载完毕才执行(推迟执行) async属性: script标签属性,脚本加载完毕后马上执行(运行过程中浏览器会解析下面的内容),即使页面还没有加载完毕(异步执行) media属性: <a>元素属性:表示对何种设备进行优化 hreflang属性: <a>的属性,表示超链接指向的网址使用的语言 ref属性: <a>的属性,定义超链接是否是外部链接 reversed属性: <ol>的属性,定义序号是否倒叙 start属性: <ol>的属性,定义序号的起始值 scoped属性: 内嵌CSS样式的属性,定义该样式只局限于拥有该内嵌样式的元素,适用于单页开发 HTML5全局属性:对任意标签都可以使用的,以下6个 data-yourvalue 、hidden、Spenllecheck、tabindex、contenteditable、desginMode; 全局属性: 1.可直接在标签里插入的:data-自定义属性名字; hidden(直接放上去就是隐藏); spellcheck="true"(语法纠错); tabindex="1"(Tab跳转顺序); contenteditable="true"(可编辑状态,单击内容,可修改); 2.在JavaScript里插入的window.document.designMode = 'on'(JavaScript的全局属性,整个页面的文本都可以编辑了);
垂直水平居中
仅居中元素定宽高适用 absolute + 负margin absolute + margin auto absolute + calc 居中元素不定宽高 absolute + transform lineheight writing-mode table css-table flex grid
2.Javascript
数组去重
一、利用ES6 Set去重(ES6中最常用)
function unique (arr) { return Array.from(new Set(arr)) } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。
二、利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr){ for(var i=0; i<arr.length; i++){ for(var j=i+1; j<arr.length; j++){ if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个 arr.splice(j,1); j--; } } } return arr; } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}没有去重,两个null直接消失了
双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。
想快速学习更多常用的ES6语法,可以看我之前的文章《学习ES6笔记──工作中常用到的ES6语法》。
三、利用indexOf去重
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } var array = []; for (var i = 0; i < arr.length; i++) { if (array .indexOf(arr[i]) === -1) { array .push(arr[i]) } } return array; } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}] //NaN、{}没有去重
新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。
四、利用sort()
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return; } arr = arr.sort() var arrry= [arr[0]]; for (var i = 1; i < arr.length; i++) { if (arr[i] !== arr[i-1]) { arrry.push(arr[i]); } } return arrry; } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) // [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined] //NaN、{}没有去重
利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。
五、利用对象的属性不能相同的特点进行去重(这种数组去重的方法有问题,不建议用,有待改进)
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } var arrry= []; var obj = {}; for (var i = 0; i < arr.length; i++) { if (!obj[arr[i]]) { arrry.push(arr[i]) obj[arr[i]] = 1 } else { obj[arr[i]]++ } } return arrry; } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "true", 15, false, undefined, null, NaN, 0, "a", {…}] //两个true直接去掉了,NaN和{}去重
六、利用includes
function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } var array =[]; for(var i = 0; i < arr.length; i++) { if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值 array.push(arr[i]); } } return array } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}] //{}没有去重
七、利用hasOwnProperty
function unique(arr) { var obj = {}; return arr.filter(function(item, index, arr){ return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true) }) } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}] //所有的都去重了
利用hasOwnProperty 判断是否存在对象属性
八、利用filter
function unique(arr) { return arr.filter(function(item, index, arr) { //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素 return arr.indexOf(item, 0) === index; }); } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]
九、利用递归去重
function unique(arr) { var array= arr; var len = array.length; array.sort(function(a,b){ //排序后更加方便去重 return a - b; }) function loop(index){ if(index >= 1){ if(array[index] === array[index-1]){ array.splice(index,1); } loop(index - 1); //递归loop,然后数组去重 } } loop(len-1); return array; } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
十、利用Map数据结构去重
function arrayNonRepeatfy(arr) { let map = new Map(); let array = new Array(); // 数组用于返回结果 for (let i = 0; i < arr.length; i++) { if(map .has(arr[i])) { // 如果有该key值 map .set(arr[i], true); } else { map .set(arr[i], false); // 如果没有该key值 array .push(arr[i]); } } return array ; } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。
十一、利用reduce+includes
function unique(arr){ return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]); } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)); // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
十二、[...new Set(arr)]
[...new Set(arr)] //代码就是这么少----(其实,严格来说并不算是一种,相对于第一种方法来说只是简化了代码)
js常用的模式
一.单例模式
单例模式也称作为单子模式,更多的也叫做单体模式。为软件设计中较为简单但是最为常用的一种设计模式。 在JavaScript里,实现单例的方式有很多种,其中最简单的一个方式是使用对象字面量的方法,其字面量里可以包含大量的属性和方法。
要扩展该对象,可以添加自己的私有成员和方法,然后使用闭包在其内部封装这些变量和函数声明。样例代码如下:
二、工厂模式
工厂模式是由一个方法来决定到底要创建哪个类的实例,而这些实例经常都拥有相同的接口。这种模式主要用在所实例化的类型在编译期并不能确定, 而是在执行期决定的情况。
实例:
这段代码来自es5的new和构造器的相关说明, new本身只是一个对象的复制和改写过程, 而具体会生成什么是由调用ObjectFactory时传进去的参数所决定的。
三、 适配模式
适配模式主要是为了解决一些接口不兼容产生的解决方法。适配器可以在不修改这些不兼容接口的情况下给使用者提供统一的包装过的适配接口。表面上又感觉和之前的门面模式比较像,均是对其他对象或者接口进行包装再呈现,而适配器模式偏向的是解决兼容性问题,门面模式则偏向方便性为原则。
比如一个简单的学生查询学科成绩的方法:
这是一个关于适配器来处理参数方面兼容的形式。 适配器模式意义上很简单 - 适配,解决兼容问题。
例子二:jquery里边的$选择器需要改成$id才能和项目搭配,将$转换成$id就很轻松了。如下:
四、外观模式
外观模式,是一种相对简单而又无处不在的模式。外观模式提供一个高层接口,这个接口使得客户端或子系统更加方便调用。 用一段再简单不过的代码来表示:
实现一个简单的订阅发布者
观察者模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
事实上,只要你曾经在DOM节点上绑定过事件函数,那么你就曾经使用过观察者模式了!
document.body.addEventListener('click', function () { alert(2); });
但是这只是对观察者模式最简单的使用,在很多场景下我们经常会实现一些自定义事件来满足我们的需求。
举个例子:
你去一家公司应聘,谈了一顿下来,hr跟你说:"好了,你回去等通知吧!"。
这个时候,1.你会问公司的电话,然后每天打过去问一遍结果
2.把自己的手机号留给hr,然后等他给你打电话
相信很多时候呢,大家都是选择了后者。
万一你每天给hr打电话弄烦他了,或许他本来打算招你的,现在也不再打算再鸟你啦!
那么这个时候,hr就相当于一个发布者,而你就是一个订阅者啦!
好吧,大部分叫你回去等消息的就等于没救啦......
我还遇到过一个如果你没被录取,就连通知都不通知你的公司!
那么一个简单的观察者模式应该怎么实现呢?
要指定一个发布者; 给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者;(这家公司很多人来应聘) 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数;(你up or 你over)
var event = {}; //发布者(hr) event.clietList = []; //发布者的缓存列表(应聘者列表) event.listen = function(fn) { //增加订阅者函数 this.clietList.push(fn); }; event.trigger = function() { //发布消息函数 for (var i = 0; i < this.clietList.length; i++) { var fn = this.clietList[i]; fn.apply(this, arguments); } }; event.listen(function(time) { //某人订阅了这个消息 console.log('正式上班时间:' + time); }); event.trigger('2016/10',yes); //发布消息 //输出 正式上班时间:2016/10
到这里,我们已经实现了一个最简单的观察者模式了!
但是上面的函数其实存在一个问题,那就是发布者没办法选择自己要发布的消息类型!
比如这家公司同时在招php,web前端,如果使用上面的函数就没办法区分职位了!只能一次性把全部订阅者都发送一遍消息。
对上面的代码进行改写:
var event = {}; //发布者(hr) event.clietList = []; //发布者的缓存列表(应聘者列表) event.listen = function(key, fn) { //增加订阅者函数 if (!this.clietList[key]) { this.clietList[key] = []; } this.clietList[key].push(fn); }; event.trigger = function() { //发布消息函数 var key = Array.prototype.shift.call(arguments), fns = this.clietList[key]; for (var i = 0; i < fns.length; i++) { var fn = fns[i]; fn.apply(this, arguments); } }; event.listen('web前端', fn1 = function(time) { //小强订阅了这个消息。 console.log('姓名:小强'); console.log('正式上班时间:' + time); }); event.listen('web前端', fn2 = function(time) { //大大强订阅了这个消息 console.log('姓名:大大强'); console.log('正式上班时间:' + time); }); //发布者发布消息 event.trigger('web前端','小强', '2016/10'); //姓名:小强 正式上班时间:2016/10 event.trigger('php','大大强', '2016/15'); //姓名:大大强 正式上班时间:2016/15
通过添加了一个key,我们实现了对职位的判断。
有了订阅事件,我们怎么能少了取消订阅事件呢?
event.remove = function(key, fn) { var fns = this.clietList[key]; if (!fns) { return false; } if (!fn) { //如果没有传入fn回调函数,直接取消key对应消息的所有订阅 this.clietList[key] = []; } else { for (var i = 0; i < fns.length; i++) { //遍历回调函数列表 var _fn = fns[i]; if (_fn === fn) { fns.splice(i, 1); //删除订阅者的回调函数 } } } }; //这时候必须指定回调函数,否则无法在remove函数中进行对比删除。 event.listen('web前端', fn1 = function(time) { //小强订阅了这个消息。 console.log('姓名:小强'); console.log('正式上班时间:' + time); }); event.listen('web前端', fn2 = function(time) { //大大强订阅了这个消息 console.log('姓名:大大强'); console.log('正式上班时间:' + time); }); event.remove('web前端',fn1); //发布者发布消息 event.trigger('web前端','2016/10'); //输出 姓名:大大强 正式上班时间:2016/10
对上面代码进行改进,创建一个全局对象来实现观察者模式,
使用闭包实现私有变量,仅暴露必须的API给使用者:
var event = (function() { var clietList = []; //发布者的缓存列表(应聘者列表) var listen = function(key, fn) { //增加订阅者函数 if (!this.clietList[key]) { this.clietList[key] = []; } this.clietList[key].push(fn); }; var trigger = function() { //发布消息函数 var key = Array.prototype.shift.call(arguments), fns = this.clietList[key]; for (var i = 0; i < fns.length; i++) { var fn = fns[i]; fn.apply(this, arguments); } }; var remove = function(key, fn) { var fns = this.clietList[key]; if (!fns) { return false; } if (!fn) { //如果没有传入fn回调函数,直接取消key对应消息的所有订阅 this.clietList[key] = []; } else { for (var i = 0; i < fns.length; i++) { //遍历回调函数列表 var _fn = fns[i]; if (_fn === fn) { fns.splice(i, 1); //删除订阅者的回调函数 } } } }; return{ listen:listen, trigger:trigger, remove:remove } })();
观察者模式进阶:
使用命名空间防止事件名冲突
实现先发布后订阅功能
3.ES6
说说weakMap
### **Objects** 我们应该首先讨论如何使用对象。 好吧,我相信90%以上的人已经知道这部分内容了,因为你点击这篇文章是为了了解新的集合对象,但对于JavaScript的初学者来说,我们还是简单说说它们吧。 1. const algorithm = { site: "leetcode" }; 2. console.log(algorithm.site); // leetcode 4. for (const key in algorithm) { 5. console.log(key, algorithm[key]); 6. } 8. // site leetcode 9. delete algorithm.site; 10. console.log(algorithm.site); // undefined 所以我做了一个 algorithm 对象,它的key和value是一个字符串类型的值,而我通过用 . 关键字来调用该值。 另外,for-in 循环也很适合在对象中循环。你可以用 [] 关键字访问其键对应的值。但是不能使用 for-of 循环,因为对象是不可迭代的。 对象的属性可以用 delete 关键字来删除。这样就可以彻底摆脱对象的属性,大家要注意不要和这种方法混淆。 1. const algorithm = { site: "leetcode" }; 2. // Property is not removed!! 3. algorithm.site = undefined; 4. // Property is removed!! 5. delete algorithm.site; algorithm.site = undefined 只是将新值分配给 site。 好的,我们已经快速讨论了有关对象的一些事项: * 如何添加属性 * 如何遍历对象 * 如何删除属性 ### **Map** Map 是JavaScript中新的集合对象,其功能类似于对象。但是,与常规对象相比,存在一些主要差异。 首先,让我们看一个创建Map对象的简单示例。 **/ 如何添加属性 /** 1. const map = new Map(); 2. // Map(0) {} Map 不需要创建任何内容,但是添加数据的方式略有不同。 1. map.set('name', 'john'); 2. // Map(1) {"name" => "john"} Map 有一种特殊的方法可在其中添加称为 set 的属性。它有两个参数:键是第一个参数,值是第二个参数。 1. map.set('phone', 'iPhone'); 2. // Map(2) {"name" => "john", "phone" => "iPhone"} 3. map.set('phone', 'iPhone'); 4. // Map(2) {"name" => "john", "phone" => "iPhone"} 但是,它不允许你在其中添加现有数据。如果 Map 对象中已经存在与新数据的键对应的值,则不会添加新数据。 1. map.set('phone', 'Galaxy'); 2. // Map(2) {"name" => "john", "phone" => "Galaxy"} 但是你可以用其他值覆盖现有数据。 **/ 如何遍历对象 /** Map 是一个可迭代的对象,这意味着可以使用 for-of 语句将其映射。 1. for (const item of map) { 2. console.dir(item); 3. } 4. // Array(2) ["name", "john"] 5. // Array(2) ["phone", "Galaxy"] 要记住的一件事是 Map 以数组形式提供数据,你应该解构数组或访问每个索引以获取键或值。 要仅获取键或值,还有一些方法可供你使用。 1. map.keys(); 2. // MapIterator {"name", "phone"} 3. map.values(); 4. // MapIterator {"john", "Galaxy"} 5. map.entries(); 6. // MapIterator {"name" => "john", "phone" => "Galaxy"} 你甚至可以使用展开操作符(...)来获取Map的全部数据,因为展开操作符还可以在幕后与可迭代对象一起工作。 1. const simpleSpreadedMap = [...map]; 2. // [Array(2), Array(2)] **/ 如何删除属性 /** 从 Map 对象中删除数据也很容易,你所需要做的就是调用 delete。 1. map.delete('phone'); 2. // true 3. map.delete('fake'); 4. // false delete 返回布尔值,该布尔值指示 delete 函数是否成功删除了数据。如果是,则返回 true,否则返回 false。 ### WeakMap WeakMap起源于Map,因此它们彼此非常相似。但是,WeakMap具有很大的不同。 WeakMap的名字是怎么来的呢?嗯,是因为它与它的引用链接所指向的数据对象的连接或关系没有Map的连接或关系那么强,所以它是弱的。 那么,这到底是什么意思呢? **/ 差异1:key必须是对象 /** 1. const John = { name: 'John' }; 2. const weakMap = new WeakMap(); 3. weakMap.set(John, 'student'); 4. // WeakMap {{...} => "student"} 5. weakMap.set('john', 'student'); 6. // Uncaught TypeError: Invalid value used as weak map key 你可以将任何值作为键传入Map对象,但WeakMap不同,它只接受一个对象作为键,否则,它将返回一个错误。 **/ 差异2:并非Map中的所有方法都支持 /** 可以使用WeakMap的方法如下。 * delete * get * has * set 这个话题最大的不同是WeakMap不支持迭代对象的方法。但是为什么呢?下面将对此进行描述。 **/ 区别3:当GC清理引用时,数据会被删除 /** 与Map相比,这是最大的不同。 1. let John = { major: "math" }; 3. const map = new Map(); 4. const weakMap = new WeakMap(); 6. map.set(John, 'John'); 7. weakMap.set(John, 'John'); 9. John = null; 10. /* John 被垃圾收集 */ 当John对象被垃圾回收时,Map对象将保持引用链接,而WeakMap对象将丢失链接。所以当你使用WeakMap时,你应该考虑这个功能。 ### Set Set也非常类似于Map,但是Set对于单个值更有用。 **/ 如何添加属性 /** 1. const set = new Set(); 3. set.add(1); 4. set.add('john'); 5. set.add(BigInt(10)); 6. // Set(4) {1, "john", 10n} 与Map一样,Set也阻止我们添加相同的值。 1. set.add(5); 2. // Set(1) {5} 4. set.add(5); 5. // Set(1) {5} **/ 如何遍历对象 /** 由于Set是一个可迭代的对象,因此可以使用 for-of 或 forEach 语句。 1. for (const val of set) { 2. console.dir(val); 3. } 4. // 1 5. // 'John' 6. // 10n 7. // 5 9. set.forEach(val => console.dir(val)); 10. // 1 11. // 'John' 12. // 10n 13. // 5 **/ 如何删除属性 /** 这一部分和 Map 的删除完全一样。如果数据被成功删除,它返回 true,否则返回 false。 1. set.delete(5); 2. // true 4. set.delete(function(){}); 5. // false; 如果你不想将相同的值添加到数组表单中,则Set可能会非常有用。 1. /* With Set */ 2. const set = new Set(); 3. set.add(1); 4. set.add(2); 5. set.add(2); 6. set.add(3); 7. set.add(3); 8. // Set {1, 2, 3} 10. // Converting to Array 11. const arr = [ ...set ]; 12. // [1, 2, 3] 14. Object.prototype.toString.call(arr); 15. // [object Array] 17. /* Without Set */ 18. const hasSameVal = val => ar.some(v === val); 19. const ar = []; 21. if (!hasSameVal(1)) ar.push(1); 22. if (!hasSameVal(2)) ar.push(2); 23. if (!hasSameVal(3)) ar.push(3); ### WeakSet 与WeakMap一样,WeakSet也将丢失对内部数据的访问链接(如果内部数据已被垃圾收集)。 1. let John = { major: "math" }; 3. const set = new Set(); 4. const weakSet = new WeakSet(); 6. set.add(John); 7. // Set {{...}} 8. weakSet.add(John); 9. // WeakSet {{...}} 11. John = null; 12. /* John 被垃圾收集 */ 一旦对象 John 被垃圾回收,WeakSet就无法访问其引用 John 的数据。而且WeakSet不支持 for-of 或 forEach,因为它不可迭代。 ### 比较总结 相同点:添加相同的值不支持。 Map vs. WeakMap:WeakMap仅接受对象作为键,而Map不接受。 **Map and Set:** * 可迭代的对象,支持 for..of,forEach 或 ... 运算符 * 脱离GC关系 **WeakMap and WeakSet:** * 不是一个可迭代的对象,不能循环。 * 如果引用数据被垃圾收集,则无法访问数据。 * 支持较少的方法。
ES6的常用API
@ES6相关知识总结
let和const
let
- 用法和var差不多,新增用来定义变量的关键字,但是有些地方不同需要注意
- let不允许重复声明变量,强行声明报错
- let声明变量只能在块级作用域内有效,ES6新增加块级作用域的概念
- let声明变量不能声明和函数形参相同的变量,会报错
- let声明变量不会被提升,所以只能在声明之后才能进行使用,在之前使用会报错
暂时性死区(TDZ):ES6规定,在一个区块中使用let或者const声明变量,那么此区域变成为块级作用域,用这两个关键字声明的变量可视作绑定该区域,不受外部影响,在该变量声明前不能使用。
const
- 此关键字声明一个变量,此变量就会成为一个可读的常量,不能被修改,重新赋值会报错
- const声明的变量必须一开始就赋值,不能留到后面再赋值
- 为了区别常量和变量,常量一般写为全部大写
- 从本质上面看,const关键字保证的并不是值不变,而是指向的内存地址不变
箭头函数:定义(形参)=>{函数体}
箭头函数与原生JS函数之间的区别
- 对于this的指向问题:箭头函数中的this不再是指向其函数的调用者,其中this指向和箭头函数定义的位置有关,实际原因是因为箭头函数内部没有自己的this,是继承外面的this,所以在箭头函数中使用this时需要看清楚指向,箭头函数内部的this不可变,不能重新改变指向
- new不能用,箭头函数不能使用new关键字来实例化对象,会报错
- 箭头函数内没有arguments对象,更不可以通过它来访问传入的参数
字符串模板:反引号``+${}
解构赋值:从对象或者数组中提取值,对变量进行赋值。
- 可以简化两个变量交换值
- 可以用于函数参数,让其传入的参数顺序进行变化
Array.from():将含有length属性的对象或类数组转成真正的数组
- Array.from(obj,map函数):第一个参数是要转换的对象,第二个参数是一个函数,类似于map函数(map函数:遍历——操作——返回)
三个点(…)
扩展运算符:把数组和类数组对象展开成一系列用逗号隔开的参数序列
reset运算符:与前者相反,将一系列用逗号隔开的参数序列组合成一个数组
Set和Map
Set:是一个构造函数,用来生成Set数据结构
- 类似于数组,但是成员值都是唯一的,不会出现重复值,初始化Set可以传入一个数组或者类数组对象作为参数,也可以不传
- 经典面试题——一句代码解决数组去重
Set的常用属性和方法
- size:返回成员总数
- add(value):添加某个值,返回Set本身
- delete(value):删除一个值,返回布尔值,表示是否删除成功
- has(value):检测是否为Set中的成员,返回布尔值
- clear():清除所有成员,没有返回值
遍历Set的方法
Map:是一个构造函数,生成Map数据结构,类似于对象,键值对集合,但是键可以是非字符串,初始化Map需要一个二维数组,或者直接初始化空的Map;
var m1 = new Map(); var m2 = new Map([['a', 123], ['b', 456], [3, 'abc']]);
Map的常见属性和方法
- size:返回成员总数;
- set(key,value):设置键值对
- get(key):获取键对应的值;
- has(key):是否存在某个键,返回布尔值
- delete(key):删除某个键值对,返回布尔值,表示是否删除成功
遍历Map的方法
Symbol类型:ES6新引入的一种原始数据类型(第七种数据类型),表示独一无二的值
在对象中的使用
Symbol类型的属性取值必须是obj[xm],不能直接obj.xm
- 特殊实例
Symbol可以用来保护对象的某个属性,因为对象的Symbol属性不会被遍历出来
- Object.getOwnPropertySymbols 方法会返回当前对象的所有 Symbol 属性,返回数组
- Symbol表示独一无二的值,所以是唯一的,
let s1 = Symbol('name'); let s2 = Symbol('name'); console.log( s1 === s2 ); // false •
让两个Symbol声明的值相等可以采用官方提供的方法
let s1 = Symbol('name'); let s2 = Symbol('name'); console.log( s1 === s2 ); // false
Object拓展
- ES6实现原型链接:Object.setPrototypeOf(obj1,obj2)把obj1的原型链接到obj2上
- Object.getOwnPropertyDescriptors(obj):获取指定对象的所有自身属性的描述符,没有返回空对象
- Object.values(obj):返回一个数组,成员是参数对象自身的所有可枚举属性的值
- Object.entries(obj):方法返回一个数组,成员是参数对象自身的(不含继承)所有可遍历属性的键值对数组——(此方法可以将对象转为真正的Map结构)
const obj = { foo: 'bar', baz: 42 }; Object.entries(obj); // [ ["foo", "bar"], ["baz", 42] ]
- Object.assign(target,source):方法用于对象的合并,将源对象source的可枚举属性复制到目标对象target(是一种对象浅拷贝,是引用属性只能是拷贝地址)
- 对象浅拷贝
var obj1 = {a: 1, b: 2, c: {d: 4, e: 5}}; var obj2 = Object.assign({}, obj1); console.log(obj1.c === obj2.c); // ture
- 对象深拷贝
var obj1 = {a: 1, b: 2, c: {d: 4, e: 5}}; var obj2 = JSON.parse(JSON.stringify(obj1)); console.log(obj1.c === obj2.c); // false
- Object.is(val1,val2):用来比较两个值是否严格相等,有两个特殊的地方
- 在此方法下,+0不等于-0
- 在此方法下,NaN等于NaN
ES6的类和继承
类
- 描述:用关键字class定义类,里面有一个constructor方法,构造方法,this代表实例对象,构造函数内部的方法和属性是实例对象自己的,外面的属性和方法是所有实例对象共享的
继承
- class之间是通过extends关键字实现继承
- super关键字:它指向的是父类的实例(也就是this指向的对象),子类必须在构造方法中调用super方法,因为子类没有自己的this对象,而是继承父类的this对象,不调用super方法,子类得不到this对象
- super关键自己当一个对象使用时只能调用函数方法,不能访问属性
- ES6的继承机制实质:先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this
class Cat { mm = 789; // 原型上的属性 constructor(n,c){ // 构造器 this.name = n; this.color = c; this.trait = function () { console.log('卖萌~'); } }; skill(){ // 原型上的方法 console.log('抓老鼠'); }; } class Dog extends Cat { // 继承 constructor(n,c,f){ super(n,c); // 构造函数继承 this.food = f; // super.skill();//super当一个对象来使用时,只能访问方法(函数) // console.log(super.abc);//不能访问属性 // console.log(this.abc);//123 // this.skill();//'抓老鼠' // console.log(super);报错 }; } var dog1 = new Dog('大黄','黑色','shi'); dog1.trait(); dog1.skill(); console.log( dog1.name ); console.log( dog1.color ); console.log( dog1.food ); console.log( dog1.mm ); console.log( dog1.constructor ); // Dog
4.Vue
Vue路由模式
hash与history 对于Vue 这类渐进式前端开发框架,为了构建SPA(单页面应用),需要引入前端路由系统,这也就是Vue-router存在的意义。前端路由的核心,就在于——— 改变视图的同时不会向后端发出请求。
一、为了达到这个目的,浏览器提供了以下两种支持:
1、hash ——即地址栏URL中的#符号(此hsah 不是密码学里的散列运算)。 比如这个URL:http://www.abc.com/#/hello, hash 的值为#/hello。它的特点在于:hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。
2、history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法。(需要特定浏览器支持) 这两个方法应用于浏览器的历史记录站,在当前已有的back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改是,虽然改变了当前的URL,但你浏览器不会立即向后端发送请求。 history模式,会出现404 的情况,需要后台配置。
二、404 错误
1、hash模式下,仅hash符号之前的内容会被包含在请求中,如 http://www.abc.com, 因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误;
2、history模式下,前端的url必须和实际向后端发起请求的url 一致,如http://www.abc.com/book/id 。如果后端缺少对/book/id 的路由处理,将返回404错误。
hash和history的实现方式
hash 和 history是主流的两种前端路由实现方式
主要说一下新增的两个API history.pushState() 和 history.replaceState()
history
pushState() 和 history.replaceState()一样采用三个参数:状态对象,标题(当前被忽略)和(可选)URL。让我们更详细地研究这三个参数中的每一个
- 状态对象(state object) —
一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate事件都会被触发,并且事件对象的state属性都包含历史记录条目的状态对象的拷贝。
- 标题(title) —
FireFox浏览器目前会忽略该参数,虽然以后可能会用上。考虑到未来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也可以传入一个简短的标题,标明将要进入的状态。
- 地址(URL) —
新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的URL不一定是绝对路径;如果是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,否则,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。
history.replaceState()操作完全一样history.pushState(),只是replaceState()修改当前的历史条目,而不是创建一个新的。请注意,这不会阻止在全局浏览器历史记录中创建新条目。
replaceState() 当您想要更新当前历史记录条目的状态对象或URL以响应某些用户操作时,此功能特别有用。
不同之处在于,pushState()会增加一条新的历史记录,而replaceState()则会替换当前的历史记录。
举一个例子
在百度页面打开控制台输入
window.history.pushState(null, null, "https://www.baidu.com/?name=history");
按下回车会发现地址栏变成这样
上面的例子中 改变url页面并没有刷新,同样根据API所述,浏览器会产生浏览记录
注意pushState()的url不支持跨域
通过用户的历史记录中向后和向前移动使用做了back(),forward()和go() 方法。