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 onload="alert('a')"></script>
<script async onload="alert('b')"></script>
如果没有以上两个属性的话,执行顺序为先加载(下载)第一个src,然后在执行其onload,然后在向下依次同步执行defer属性在h5之前就已经有了,输入延迟加载(推迟执行),它会先加载(下载)src中文件内容,然后等页面全部加载完成后,再加载onload中js.async属性属于异步加载,它会在加载src后,立即执行onload,同时还会继续加载页面以上执行顺序,alert显示会先显示b然后再显示a
网页中标签中加入小图标的样式代码
<link rel="icon" href="https://segmentfault.com/a/1190000023447952/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里,实现单例的方式有很多种,其中最简单的一个方式是使用对象字面量的方法,其字面量里可以包含大量的属性和方法。

image

要扩展该对象,可以添加自己的私有成员和方法,然后使用闭包在其内部封装这些变量和函数声明。样例代码如下:

image

二、工厂模式

工厂模式是由一个方法来决定到底要创建哪个类的实例,而这些实例经常都拥有相同的接口。这种模式主要用在所实例化的类型在编译期并不能确定, 而是在执行期决定的情况。

实例:

image

这段代码来自es5的new和构造器的相关说明, new本身只是一个对象的复制和改写过程, 而具体会生成什么是由调用ObjectFactory时传进去的参数所决定的。

三、 适配模式

适配模式主要是为了解决一些接口不兼容产生的解决方法。适配器可以在不修改这些不兼容接口的情况下给使用者提供统一的包装过的适配接口。表面上又感觉和之前的门面模式比较像,均是对其他对象或者接口进行包装再呈现,而适配器模式偏向的是解决兼容性问题,门面模式则偏向方便性为原则。

比如一个简单的学生查询学科成绩的方法:

image

这是一个关于适配器来处理参数方面兼容的形式。 适配器模式意义上很简单 – 适配,解决兼容问题。

例子二:jquery里边的$选择器需要改成$id才能和项目搭配,将$转换成$id就很轻松了。如下:

image

四、外观模式

外观模式,是一种相对简单而又无处不在的模式。外观模式提供一个高层接口,这个接口使得客户端或子系统更加方便调用。 用一段再简单不过的代码来表示:

image

实现一个简单的订阅发布者
观察者模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
事实上,只要你曾经在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() 方法。

hash

我们经常在 url 中看到 #,这个 # 有两种情况,一个是我们所谓的锚点,比如典型的回到顶部按钮原理、Github 上各个标题之间的跳转等,路由里的 # 不叫锚点,我们称之为 hash,大型框架的路由系统大多都是哈希实现的。

同样我们需要一个根据监听哈希变化触发的事件 ——hashchange 事件

我们用 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录中,这样我们跳转页面就可以在 hashchange 事件中注册 ajax 从而改变页面内容。

hashchange 在低版本 IE 需要通过轮询监听 url 变化来实现,我们可以模拟如下

(function(window) {
// 如果浏览器不支持原生实现的事件,则开始模拟,否则退出。
if ( "onhashchange" in window.document.body ) { return; }
var location = window.location,
oldURL = location.href,
oldHash = location.hash;
// 每隔100ms检查hash是否发生变化
setInterval(function() {
var newURL = location.href,
newHash = location.hash;
// hash发生变化且全局注册有onhashchange方法(这个名字是为了和模拟的事件名保持统一)
if ( newHash != oldHash && typeof window.onhashchange === "function"  ) {
// 执行方法
window.onhashchange({
type: "hashchange",
oldURL: oldURL,
newURL: newURL
});
oldURL = newURL;
oldHash = newHash;
}
}, 100);
})(window)

个人感觉还是hash方案好一点,因为照顾到低级浏览器,就是多了#号感觉不太美观,两者兼容也是可以的,只能根据浏览器的版本给出对应的方案 不过也支持IE8+ 还是不错的

兄弟组件的传值

注意:注册的 Bus 要在组件销毁时卸载,否则会多次挂载,造成触发一次但多个响应的情况。

beforeDestroy () {
this.$Bus.$off(‘sendMessage’, this.message);
}

  1. Vuex 状态管理器

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

图片引用自网络:

vuex

Vuex 的具体使用。

  1. 通过父组件进行过渡

不是方法的方法:

子组件 A 通过事件 $emit 传值传给父组件。
父组件通过属性 props 传值给子组件 B。

深层次嵌套组件传值

  1. 依赖注入 provide/inject

provide 选项允许我们指定我们想要提供给后代组件的数据/方法。

provide: function () {
return {

getMap: this.getMap

}
}

然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的属性:

inject: [‘getMap’]

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

// 父级组件提供 ‘foo’
var Provider = {
provide: {

foo: 'bar'

},
// …
}

// 子组件注入 ‘foo’
var Child = {
inject: [‘foo’],
created () {

console.log(this.foo) // => "bar"

}
// …
}

然而,依赖注入还是有负面影响的。它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。同时所提供的属性是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟使用 $root 做这件事都是不够好的。如果你想要共享的这个属性是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像 Vuex 这样真正的状态管理方案了。
  1. $attrs/inheritAttrs

这个两个属性是 2.4 新增的特性。

$attrs:

官网介绍的很累赘,暂且理解为非 props 属性集合。更多介绍。

当一个组件中没有声明任何 prop 时,this.$attrs 可以获取到所有父作用域的属性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传给其内部组件 —— 在创建高级别的组件时非常有用。

inheritAttrs:

控制元素属性是否显示在 dom 上,默认值为 true。

默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例属性 $attrs 可以让这些特性生效,且可以通过 v-bind 显性的绑定到非根元素上。

祖先组件:

<template>
<div>

<List-item :title="title" :message="message"></List-item>

</div>
</template>
<script>
import ListItem from “./List-item”;
export default {
data() {

return {
title: "我是title",
message: "传给后代"
}

},
components: {

ListItem

}
}
</script>

父组件:

<template>
<div>

<h1>{{title}}</h1>
<h2>{{$attrs.message}}</h2>
<!-- 通过 v-bind="$attrs" 传入后代组件-->
<ListItem2 v-bind='$attrs'></ListItem2>

</div>
</template>
<script>
import ListItem2 from ‘./List-item2’
export default {
props: {

title: String

},
components: {

ListItem2

},
// 默认为 true,如果传入的属性子组件没有 prop 接受,就会以字符串的形式作为标签的属性存在 <div message=”传给后代”></div>
// 设为 false,在 dom 中就看不到这些属性 <div>…</div>
inheritAttrs: false
}
</script>

后代组件:

<template>
<div>

{{$attrs.message}}

</div>
</template>
<script>
export default {
mounted() {

console.log(this.$attrs)    // {message: "传给后代"}

}
}
</script>

渲染出来的结果为:

$attrs/inheritAttrs
插槽 slot 与子组件传值

在实际项目中确实有遇到插槽后备内容 动态显示的情况,所以这里要补充一下插槽 后备内容 是如何与子组件进行通信的。

插槽后备内容是指:写在父组件中,包含在子组件标签里的,与子组件中的 slot 对应。

<template>
<child-component>

我是插槽的后备内容

</child-component>
</template>

比如这里有一个含有 slot 的 current-user 组件,它的模版结构是这样的:

<!– 子组件 current-user.vue –>
<template>
<div>

<div>current-user组件</div>
<slot>插槽里默认显示:{{user.firstName}}</slot>

</div>
</template>

<script>
export default {
data() {

return {
user: {
firstName: "zhao",
lastName: "xinglei"
}
}

}
}
</script>

它的父组件是这样的:

<!– 父组件 Users.vue –>
<template>
<div>

<div>我是Users组件</div>
<current-user>
我是插槽里的后备内容: {{user.lastName}}(我想显示为子组件中 user.lastName )
</current-user>

</div>
</template>

<script>
import CurrentUser from ‘./Current-User.vue’
export default {
components: {

CurrentUser

}
}
</script>

我们看到,在父组件 Users 中,为子组件 current-user 提供的后备内容中,想要显示子组件定义的 user.firstName 是不能做到的。

官网中提供一个指令 v-slot,它与 props 结合使用从而达到插槽后备内容与子组件通信的目的。

我们首先需要在子组件的 slot 中传递一个 props(这个props 叫做插槽props),这里我们起名叫 user:

<!– 子组件 current-user.vue –>
<template>
<div>

<div>current-user组件</div>
<slot :user="user">
插槽里默认显示:{{user.firstName}}
</slot>

</div>
</template>

在父组件中,包含插槽后备内容的子组件标签上我们绑定一个 v-slot 指令,像这样:

<template>
<div>

<div>我是Users组件</div>
<!-- slotProps里的内容就是子组件传递过来的 props -->
<!-- "user": { "firstName": "zhao", "lastName": "xinglei" } -->
<current-user v-slot="slotProps">
{{slotProps}}
</current-user>

</div>
</template>

最后渲染出来的结果为:

作用域插槽

官网给这种插槽起名叫做作用域插槽,更多了解。
总结

  1. 组件之间传值无非就是通过属性、事件和操作 Vue 实例进行的。
  2. 操作实例进行组件件通信,实例属性 $root、$parent、$children 分别对应了根实例、父实例、子实例。

3 ref 子组件引用,在操作表单元素时会应用的到。

  1. Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,简单的应用不要使用 Vuex。
  2. Vue.observable() 让一个对象可响应,可以作为最小化的跨组件状态存储器(本文未提到)。

说说vue的响应式

Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。

Mixin
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"

vue的订阅发布者

##### 1、vue响应原理:
vue.js采用数据劫持结合发布-订阅者模式,通过Object.defineProperty()来劫持data中各个属性的setter、getter,在数据变动时,发布消息给订阅者,触发响应的监听回调。
(setter和getter是对象的存储器属性,是一个函数,用来获取和设置值)
##### 2、发布-订阅者模式的作用:
##### 1、vue响应原理:
vue.js采用数据劫持结合发布-订阅者模式,通过Object.defineProperty()来劫持data中各个属性的setter、getter,在数据变动时,发布消息给订阅者,触发响应的监听回调。
(setter和getter是对象的存储器属性,是一个函数,用来获取和设置值)
##### 2、发布-订阅者模式的作用:
处理一对多的场景,应用于不同情况下的不同函数调用
优点:低耦合性,易于代码维护;
缺点:若订阅的消息未发生,需消耗一定的时间和内存。
[![复制代码](https://upload-images.jianshu.io/upload_images/23849911-0de625688003404e.gif?imageMogr2/auto-orient/strip)](javascript:void(0); "复制代码")
<pre><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Vue发布-订阅模式</title>
</head>
<body>
<div id="app"> 订阅试图-1:<span class="box-1">第一个值</span>
订阅试图-2:<span class="box-2">第二个值</span>
</div>
<script>
//订阅器模型
var Dep = {
list: {},
listen: function (key, fn) {
(this.list[key] || (this.list[key] = [])).push(fn);
},
trigger: function () { var key = Array.prototype.shift.call(arguments);
fns = this.list[key]; if (!fns || fns.length == 0) return; for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments);//发布消息附带的参数
}
}
}; //劫持的方法 Object.defineProperty方法,给对象的属性赋值
var dataHijack = function ({ data, tag, datakey, selector }) { debugger
var value = '';
el = document.querySelector(selector);
Object.defineProperty(data, datakey, { //拿到数据
get: function () {
console.log('我获取到值了'); return value;
}, //设置数据
set: function (newVal) {
console.log('我设置值了');
value = newVal;
Dep.trigger(tag, newVal); //发布消息,更新变化
}
}) //绑定观察者
Dep.listen(tag, function (text) {
el.innerHTML = text;
})
}; var dataObj = {}; //数据
//数据劫持
dataHijack({
data: dataObj,
tag: 'view-1',
datakey: 'one',
selector: '.box-1' });
dataHijack({
data: dataObj,
tag: 'view-2',
datakey: 'two',
selector: '.box-2' }); </script>
</body>
</html></pre>
[![复制代码](https://upload-images.jianshu.io/upload_images/23849911-c0f72b5bf7fc48f0.gif?imageMogr2/auto-orient/strip)](javascript:void(0); "复制代码")
[![复制代码](https://upload-images.jianshu.io/upload_images/23849911-09d49fa4ab9cd9bd.gif?imageMogr2/auto-orient/strip)](javascript:void(0); "复制代码")
<pre>        // jquery中的发布-订阅者
//创建一个事件池 $.Callback()
let $pond= $.Callback();
$('.submit').click(function(){ //发布  点击的时候通知事件池中的方法执行,同时传递实参
$pond.fire(100,200);
});
let fn1=function(){console.log(1)}
let fn2=function(){console.log(2)}
let fn3=function(n,m){console.log(3,n+m)} //把需要做的事情添加到事件池中
//事件池相当于一个登记册,把所有订阅者收集到上面
$pond.add(fn1);
$pond.add(fn2);
$pond.add(fn3); </pre>

5.HTTP

常见的状态码

各类别常见状态码:
2xx (3种)
200 OK:表示从客户端发送给服务器的请求被正常处理并返回;
204 No Content:表示客户端发送给客户端的请求得到了成功处理,但在返回的响应报文中不含实体的主体部分(没有资源可以返回);
206 Patial Content:表示客户端进行了范围请求,并且服务器成功执行了这部分的GET请求,响应报文中包含由Content-Range指定范围的实体内容。
3xx (5种)
301 Moved Permanently:永久性重定向,表示请求的资源被分配了新的URL,之后应使用更改的URL;
302 Found:临时性重定向,表示请求的资源被分配了新的URL,希望本次访问使用新的URL;
301与302的区别:前者是永久移动,后者是临时移动(之后可能还会更改URL)
303 See Other:表示请求的资源被分配了新的URL,应使用GET方法定向获取请求的资源;
302与303的区别:后者明确表示客户端应当采用GET方式获取资源
304 Not Modified:表示客户端发送附带条件(是指采用GET方法的请求报文中包含if-Match、If-Modified-Since、If-None-Match、If-Range、If-Unmodified-Since中任一首部)的请求时,服务器端允许访问资源,但是请求为满足条件的情况下返回改状态码;
307 Temporary Redirect:临时重定向,与303有着相同的含义,307会遵照浏览器标准不会从POST变成GET;(不同浏览器可能会出现不同的情况);
4xx (4种)
400 Bad Request:表示请求报文中存在语法错误;
401 Unauthorized:未经许可,需要通过HTTP认证;
403 Forbidden:服务器拒绝该次访问(访问权限出现问题)
404 Not Found:表示服务器上无法找到请求的资源,除此之外,也可以在服务器拒绝请求但不想给拒绝原因时使用;
5xx (2种)
500 Inter Server Error:表示服务器在执行请求时发生了错误,也有可能是web应用存在的bug或某些临时的错误时;
503 Server Unavailable:表示服务器暂时处于超负载或正在进行停机维护,无法处理请求;

Webpack

Webpack性能优化

优化一: vue-router路由懒加载

 `懒加载:`也叫延迟加载,即在需要的时候进行加载,随用随载。      使用懒加载的原因: `vue`是单页面应用,使用`webpcak`打包后的文件很大,会使进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。运用懒加载后,就可以按需加载页面,提高用户体验。

懒加载的写法:

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
component: resolve => require(['@/components/DefaultIndex'],resolve),
children: [
{
path: '',
component: resolve => require(['@/components/Index'],resolve)
},
{
path: '*',
redirect: '/Index'
}
]
})
复制代码

非懒加载的路由配置:

import Vue from 'vue'
import Router from 'vue-router'
import DefaultIndex from '@/components/DefaultIndex'
import Index from '@/components/Index'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
component: 'DefaultIndex ',
children: [
{
path: '',
component: 'Index'
},
{
path: '*',
redirect: '/Index'
}
]
})
复制代码

优化二:webpack压缩图片(减少图片大小)

 一般在`vue`项目中用`webpack`打包时,会根据`webpack.base.conf.js`中`url-loader`中设置`limit`大小来对图片处理,对小于`limit`的图片转化为`base64`格式,其余的不做操作。所以对有些较大的图片资源,在请求资源的时候,加载会很慢,可以用`image-webpack-loader`来压缩图片。
安装:
npm install image-webpack-loader --save-dev
复制代码
配置:
  在`webpack.base.conf.js`文件中引入配置(此项目我用的是脚手架搭建的,所以是`webpack.base.conf.js`)
1: 引入:
require("image-webpack-loader")
2:配置:
module: {
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options:   {
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
},
或者也可配置为:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
}
复制代码

优化三:打包后的js过大,将js打包多个文件

 由于`webpack`打包后的`js`过大,以至于在加载资源时间过长。所以将文件打包成多个`js`文件,在需要的时候按需加载。
优化方案:
entry:{
main:'xxx.js'
}
plugins:{
new commonsChunkPlugin({
name:'commons',
minChunks:function(module){
// 下边return参考的vue-cli配置
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}) ,
// 以下才是关键
new commonsChunkPlugin({
name:'charts',
chunks:['commons']
minChunks:function(module){
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0 && ['jquery.js', 'highcharts.js','echarts'].indexOf( module.resource.substr(module.resource.lastIndexOf('/')+1).toLowerCase() ) != -1
)
}
})
}
复制代码

优化四:去掉不必要的插件

 1:打包时,将一些不必要的插件可以去掉,以防止打包一些无用的资源,导致打包后的文件过大,影响性能。
2:在引入第三方插件的时候,如果该插件的组件过大,可以按需引入,如`element-ui`。
3:使用`webpack.optimize.UglifyJsPlugin`插件压缩混淆[js](http://lib.csdn.net/base/javascript "JavaScript知识库")代码,使用方法如下:

plugins: [//webpack.config.jsnew webpack.optimize.UglifyJsPlugin({    warnings: false,
compress: {
join_vars: true,
warnings: false,
},
toplevel: false,
ie8: false,
]
复制代码

优化五: gzip压缩

 web前端项目,静态资源放在`cdn`上比较多,`gzip`的压缩是非常必要的,它直接改变了`js`文件的大小,减少两到三倍。 参考[加速nginx: 开启gzip和缓存](https://link.jianshu.com/?t=https%3A%2F%2Fwww.darrenfang.com%2F2015%2F01%2Fsetting-up-http-cache-and-gzip-with-nginx%2F),`nginx`的`gzip`配置非常简单,在你对应的域名底下,添加下面的配置,重启服务即可。`gzip_comp_level`的值大于`2`的时候并不明显,建议设置在`1或者2`之间。
# 开启gzip
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
gzip_comp_level 2;
# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
# 禁用IE 6 gzip
gzip_disable "MSIE [1-6]\.";
复制代码
 这种方法我 没有使用过,有用过的亲,可以留言,沟通一下下。

优化六: 服务器缓存

 为了提高服务器获取数据的速度,`nginx`缓存着静态资源是非常必要的。如果是测试服务器对`html`应该不设置缓存,而`js`等静态资源环境因为文件尾部会加上一个`hash`值,这可以有效实现缓存的控制。
location ~* ^.+\.(ico|gif|jpg|jpeg|png)$ {
access_log   off;
expires      30d;
}
location ~* ^.+\.(css|js|txt|xml|swf|wav)$ {
access_log   off;
expires      24h;
}
location ~* ^.+\.(html|htm)$ {
expires      1h;
}

6.其他

Angular和vue的区别
angularjs和vue的区别:

1、vueJS简单易学,而angularJS的上手较高;

2、vue专注于View层,主打的是快速便捷,而angularJS功能则比较全面,当然体量也较大,相对没有vue那么便捷;

3、angularJS的指令都是ng-xxx,而vueJS的指令都是v-xxx;

4、angularJS的所有指令和方法都是绑定在$scope上的,而vueJS是new出来一个实例,所有的方法和指令都在这个实例上,一个页面上可以有多个vue实例,但是angularJS的对象只能有一个;

5、angularJS是由google开发和维护的,vueJS是由个人维护的;

6、vueJS一般用于移动端的开发,而angularJS一般应用于大型的项目

Post和get请求区别

GET 被强制服务器支持
浏览器对URL的长度有限制,所以GET请求不能代替POST请求发送大量数据
GET请求发送数据更小
GET请求是安全的
GET请求是幂等的
POST请求不能被缓存
POST请求相对GET请求是「安全」的

Css动画跟js动画的区别

CSS动画
优点: (1)浏览器可以对动画进行优化。
         1、 浏览器使用与 requestAnimationFrame 类似的机制,requestAnimationFrame比起setTimeout,setInterval设置动画的优势主要是:1)requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。2)在隐藏或不可见的元素中requestAnimationFrame不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
   2、强制使用硬件加速 (通过 GPU 来提高动画性能)
      (2)代码相对简单,性能调优方向固定
(3)对于帧速表现不好的低版本浏览器,CSS3可以做到自然降级,而JS则需要撰写额外代码
缺点:
  1、 运行过程控制较弱,无法附加事件绑定回调函数。CSS动画只能暂停,不能在动画中寻找一个特定的时间点,不能在半路反转动画,不能变换时间尺度,不能在特定的位置添加回调函数或是绑定回放事件,无进度报告
   2、  代码冗长。想用 CSS 实现稍微复杂一点动画,最后CSS代码都会变得非常笨重。
JS动画
优点:
    (1)JavaScript动画控制能力很强, 可以在动画播放过程中对动画进行控制:开始、暂停、回放、终止、取消都是可以做到的。
(2)动画效果比css3动画丰富,有些动画效果,比如曲线运动,冲击闪烁,视差滚动效果,只有JavaScript动画才能完成
(3)CSS3有兼容性问题,而JS大多时候没有兼容性问题
缺点:
   (1)JavaScript在浏览器的主线程中运行,而主线程中还有其它需要运行的JavaScript脚本、样式计算、布局、绘制任务等,对其干扰导致线程可能出现阻塞,从而造成丢帧的情况。
(2)代码的复杂度高于CSS动画
总结:如果动画只是简单的状态切换,不需要中间过程控制,在这种情况下,css动画是优选方案。它可以让你将动画逻辑放在样式文件里面,而不会让你的页面充斥 Javascript 库。然而如果你在设计很复杂的富客户端界面或者在开发一个有着复杂UI状态的 APP。那么你应该使用js动画,这样你的动画可以保持高效,并且你的工作流也更可控。所以,在实现一些小的交互动效的时候,就多考虑考虑CSS动画。对于一些复杂控制的动画,使用javascript比较可靠。
css 动画和 js 动画的差异
1. 代码复杂度,js 动画代码相对复杂一些
2. 动画运行时,对动画的控制程度上,js 能够让动画,暂停,取消,终止,css动画不能添加事件
3. 动画性能看,js 动画多了一个js 解析的过程,性能不如 css 动画好

Hash跟history的区别


vue-router 中hash模式和history模式。
在vue的路由配置中有mode选项,最直观的区别就是在url中hash 带了一个很丑的 # ,而history是没有#的。vue默认使用hash。
mode:"hash";
mode:"history";
hash
—— 即地址栏 URL 中的 # 符号(此 hash 不是密码学里的散列运算)。
比如这个 URL:http://www.aaa.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
history
—— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)
这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
因此可以说,hash 模式和 history 模式都属于浏览器自身的特性,Vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由。
history模式的问题
通过history api,我们丢掉了丑陋的#,但是它也有个问题:不怕前进,不怕后退,就怕刷新,f5,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。
在hash模式下,前端路由修改的是#中的信息,而浏览器请求时不会将 # 后面的数据发送到后台,所以没有问题。但是在history下,你可以自由的修改path,当刷新时,如果服务器中没有相应的响应或者资源,则会刷新出来404页面。

跨域问题

2. 前端解决跨域问题

1> document.domain + iframe (只有在主域相同的时候才能使用该方法)

1) 在www.a.com/a.html中:

document.domain = 'a.com';var ifr = document.createElement('iframe');ifr.src = 'http://www.script.a.com/b.html';ifr.display = none;document.body.appendChild(ifr);ifr.onload = function(){    var doc = ifr.contentDocument || ifr.contentWindow.document;    //在这里操作doc,也就是b.html    ifr.onload = null;};

2) 在www.script.a.com/b.html中:

document.domain = 'a.com';

2> 动态创建script

这个没什么好说的,因为script标签不受同源策略的限制。

function loadScript(url, func) {  var head = document.head || document.getElementByTagName('head')[0];  var script = document.createElement('script');  script.src = url;   script.onload = script.onreadystatechange = function(){    if(!this.readyState || this.readyState=='loaded' || this.readyState=='complete'){      func();      script.onload = script.onreadystatechange = null;    }  };   head.insertBefore(script, 0);}window.baidu = {  sug: function(data){    console.log(data);  }}loadScript('http://suggestion.baidu.com/su?wd=w',function(){console.log('loaded')});//我们请求的内容在哪里?//我们可以在chorme调试面板的source中看到script引入的内容

3> location.hash + iframe

原理是利用location.hash来进行传值。

假设域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html传递信息。
1) cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html页面
2) cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据
3) 同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一旦有变化则获取获取hash值
注:由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe

代码如下:
先是a.com下的文件cs1.html文件:

function startRequest(){    var ifr = document.createElement('iframe');    ifr.style.display = 'none';    ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';    document.body.appendChild(ifr);} function checkHash() {    try {        var data = location.hash ? location.hash.substring(1) : '';        if (console.log) {            console.log('Now the data is '+data);        }    } catch(e) {};}setInterval(checkHash, 2000);

cnblogs.com域名下的cs2.html:

//模拟一个简单的参数处理操作switch(location.hash){    case '#paramdo':        callBack();        break;    case '#paramset':        //do something……        break;} function callBack(){    try {        parent.location.hash = 'somedata';    } catch (e) {        // ie、chrome的安全机制无法修改parent.location.hash,        // 所以要利用一个中间的cnblogs域下的代理iframe        var ifrproxy = document.createElement('iframe');        ifrproxy.style.display = 'none';        ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata';    // 注意该文件在"a.com"域下        document.body.appendChild(ifrproxy);    }}

a.com下的域名cs3.html

//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值parent.parent.location.hash = self.location.hash.substring(1);

4> window.name + iframe

window.name 的美妙之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

1) 创建a.com/cs1.html

2) 创建a.com/proxy.html,并加入如下代码

<head>  <script>  function proxy(url, func){    var isFirst = true,        ifr = document.createElement('iframe'),        loadFunc = function(){          if(isFirst){            ifr.contentWindow.location = 'http://a.com/cs1.html';            isFirst = false;          }else{            func(ifr.contentWindow.name);            ifr.contentWindow.close();            document.body.removeChild(ifr);            ifr.src = '';            ifr = null;          }        };     ifr.src = url;    ifr.style.display = 'none';    if(ifr.attachEvent) ifr.attachEvent('onload', loadFunc);    else ifr.onload = loadFunc;     document.body.appendChild(iframe);  }</script></head><body>  <script>    proxy('http://www.baidu.com/', function(data){      console.log(data);    });  </script></body>

3 在b.com/cs1.html中包含:

<script>    window.name = '要传送的内容';</script>

5> postMessage(HTML5中的XMLHttpRequest Level 2中的API)

1) a.com/index.html中的代码:

<iframe id="ifr"></iframe><script type="text/javascript">window.onload = function() {    var ifr = document.getElementById('ifr');    var targetOrigin = 'http://b.com';  // 若写成'http://b.com/c/proxy.html'效果一样                                        // 若写成'http://c.com'就不会执行postMessage了    ifr.contentWindow.postMessage('I was there!', targetOrigin);};</script>

2) b.com/index.html中的代码:

<script type="text/javascript">    window.addEventListener('message', function(event){        // 通过origin属性判断消息来源地址        if (event.origin == 'http://a.com') {            alert(event.data);    // 弹出"I was there!"            alert(event.source);  // 对a.com、index.html中window对象的引用                                  // 但由于同源策略,这里event.source不可以访问window对象        }    }, false);</script>

6> CORS

CORS背后的思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

IE中对CORS的实现是xdr

var xdr = new XDomainRequest();xdr.onload = function(){    console.log(xdr.responseText);}xdr.open('get', 'http://www.baidu.com');......xdr.send(null);

其它浏览器中的实现就在xhr中

var xhr =  new XMLHttpRequest();xhr.onreadystatechange = function () {    if(xhr.readyState == 4){        if(xhr.status >= 200 && xhr.status < 304 || xhr.status == 304){            console.log(xhr.responseText);        }    }}xhr.open('get', 'http://www.baidu.com');......xhr.send(null);

实现跨浏览器的CORS

function createCORS(method, url){    var xhr = new XMLHttpRequest();    if('withCredentials' in xhr){        xhr.open(method, url, true);    }else if(typeof XDomainRequest != 'undefined'){        var xhr = new XDomainRequest();        xhr.open(method, url);    }else{        xhr = null;    }    return xhr;}var request = createCORS('get', 'http://www.baidu.com');if(request){    request.onload = function(){        ......    };    request.send();}

7> JSONP

JSONP包含两部分:回调函数和数据。

回调函数是当响应到来时要放在当前页面被调用的函数。

数据就是传入回调函数中的json数据,也就是回调函数的参数了。

function handleResponse(response){    console.log('The responsed data is: '+response.data);}var script = document.createElement('script');script.src = 'http://www.baidu.com/json/?callback=handleResponse';document.body.insertBefore(script, document.body.firstChild);/*handleResonse({"data": "zhe"})*///原理如下://当我们通过script标签请求时//后台就会根据相应的参数(json,handleResponse)//来生成相应的json数据(handleResponse({"data": "zhe"}))//最后这个返回的json数据(代码)就会被放在当前js文件中被执行//至此跨域通信完成

jsonp虽然很简单,但是有如下缺点:

1)安全问题(请求代码中可能存在安全隐患)

2)要确定jsonp请求是否失败并不容易

8> web sockets

web sockets是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用)

web sockets原理:在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。

只有在支持web socket协议的服务器上才能正常工作。

var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wsssocket.send('hello WebSockt');socket.onmessage = function(event){    var data = event.data;}

vue 的params和query的区别
初学vue的时候,不知道如何在方法中跳转界面并传参,百度过后,了解到两种方式,params 与 query。然后,错误就这么来了:

   router文件下index.js里面,是这么定义路由的:

<pre>{

  path:"/detail",
name:"detail",
component:home
}</pre>

  我想用params来传参,是这么写的,嗯~

复制代码; “复制代码”)

复制代码

this.$router.push({
path:”/detail”,
params:{
name:’nameValue’,
code:10011
}
});

复制代码

复制代码; “复制代码”)

  结果可想而知,接收参数的时候:

<pre>this.$route.params.code //undefined</pre>

  这是因为,params只能用name来引入路由,下面是正确的写法:

复制代码; “复制代码”)

复制代码

<pre>this.$router.push({
name:”detail”,
params:{

name:'nameValue',
code:10011

}
});</pre>

复制代码

复制代码; “复制代码”)

这回就对了,可以直接拿到传递过来的参数nameValue了。

说完了我的犯傻,下面整理一下这两者的差别:

1、用法上的

刚才已经说了,query要用path来引入,params要用name来引入,接收参数都是类似的,分别是this.$route.query.name和this.$route.params.name。

注意接收参数的时候,已经是$route而不是$router了哦!!

2、展示上的

query更加类似于我们ajax中get传参,params则类似于post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示

query: image

params: image

原型,原型链,闭包

原型与原型链

  • 所有函数都有一个特别的属性:

    • prototype : 显式原型属性
  • 所有实例对象都有一个特别的属性:

    • __proto__ : 隐式原型属性
  • 显式原型与隐式原型的关系

    • 函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象
    • 实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
    • 原型对象即为当前实例对象的父对象
  • 原型链

    • 所有的实例对象都有__proto__属性, 它指向的就是原型对象
    • 这样通过__proto__属性就形成了一个链的结构—->原型链
    • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
    • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作

执行上下文与执行上下文栈

  • 变量提升与函数提升

    • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
    • 函数提升: 在函数定义语句之前, 就执行该函数
    • 先有变量提升, 再有函数提升
  • 理解

    • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
    • 执行上下文栈: 用来管理产生的多个执行上下文
  • 分类:

    • 全局: window
    • 函数: 对程序员来说是透明的
  • 生命周期

    • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
    • 函数 : 调用函数时产生, 函数执行完时死亡
  • 包含哪些属性:

    • 全局 :

      • 用var定义的全局变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===>window
    • 函数

      • 用var定义的局部变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===> 调用函数的对象, 如果没有指定就是window
      • 形参变量 ===>对应实参值
      • arguments ===>实参列表的伪数组

继承模式

  • 原型链继承 : 得到方法

    function Parent(){}
    Parent.prototype.test = function(){};
    function Child(){}
    Child.prototype = new Parent(); // 子类型的原型指向父类型实例
    Child.prototype.constructor = Child
    var child = new Child(); //有test()
  • 借用构造函数 : 得到属性

    function Parent(xxx){this.xxx = xxx}
    Parent.prototype.test = function(){};
    function Child(xxx,yyy){
    Parent.call(this, xxx);//借用构造函数   this.Parent(xxx)
    }
    var child = new Child('a', 'b');  //child.xxx为'a', 但child没有test()
  • 组合

    function Parent(xxx){this.xxx = xxx}
    Parent.prototype.test = function(){};
    function Child(xxx,yyy){
    Parent.call(this, xxx);//借用构造函数   this.Parent(xxx)
    }
    Child.prototype = new Parent(); //得到test()
    var child = new Child(); //child.xxx为'a', 也有test()
  • new一个对象背后做了些什么?

    • 创建一个空对象
    • 给对象设置__proto__, 值为构造函数对象的prototype属性值 this.__proto__ = Fn.prototype
    • 执行构造函数体(给对象添加属性/方法)
  • 执行上下文创建和初始化的过程

    • 全局:

      • 在全局代码执行前最先创建一个全局执行上下文(window)
      • 收集一些全局变量, 并初始化
      • 将这些变量设置为window的属性
    • 函数:

      • 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
      • 收集一些局部变量, 并初始化
      • 将这些变量设置为执行上下文的属性

作用域与作用域链

  • 理解:

    • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
    • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
  • 分类:

    • 全局
    • 函数
    • js没有块作用域(在ES6之前)
  • 作用

    • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
    • 作用域链: 查找变量
  • 区别作用域与执行上下文

    • 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
    • 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
    • 联系: 执行上下文环境是在对应的作用域中的

闭包

  • 理解:

    • 当嵌套的内部函数引用了外部函数的变量时就产生了闭包
    • 通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
  • 作用:

    • 延长局部变量的生命周期
    • 让函数外部能操作内部的局部变量
  • 写一个闭包程序

    function fn1() {
    var a = 2;
    function fn2() {
    a++;
    console.log(a);
    }
    return fn2;
    }
    var f = fn1();
    f();
    f();
  • 闭包应用:

    • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
    • 循环遍历加监听
    • JS框架(jQuery)大量使用了闭包
  • 缺点:

    • 变量占用内存的时间可能会过长
    • 可能导致内存泄露
    • 解决:

      • 及时释放 : f = null; //让内部函数对象成为垃圾对象

内存溢出与内存泄露

  1. 内存溢出

    • 一种程序运行出现的错误
    • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  2. 内存泄露

    • 占用的内存没有及时释放
    • 内存泄露积累多了就容易导致内存溢出
    • 常见的内存泄露:

      • 意外的全局变量
      • 没有及时清理的计时器或回调函数
      • 闭包

箭头函数和普通函数的区别

箭头函数:

let fun = () => {

console.log('lalalala');

}

普通函数:

function fun() {

console.log('lalla');

}

箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种只包含一个表达式,连{ … }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ … }和return。

箭头函数是匿名函数,不能作为构造函数,不能使用new

let FunConstructor = () => {

console.log('lll');

}

let fc = new FunConstructor();

箭头函数不绑定arguments,取而代之用rest参数…解决

复制代码; “复制代码”)

function A(a){
console.log(arguments);
}
A(1,2,3,4,5,8); // [1, 2, 3, 4, 5, 8, callee: ƒ, Symbol(Symbol.iterator): ƒ]
let B = (b)=>{
console.log(arguments);
}
B(2,92,32,32); // Uncaught ReferenceError: arguments is not defined
let C = (…c) => {
console.log(c);
}
C(3,82,32,11323); // [3, 82, 32, 11323]

复制代码; “复制代码”)

箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值

复制代码; “复制代码”)

var obj = {
a: 10,
b: () => {

console.log(this.a); // undefined
console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

},
c: function() {

console.log(this.a); // 10
console.log(this); // {a: 10, b: ƒ, c: ƒ}

}
}
obj.b();
obj.c();

复制代码; “复制代码”)

复制代码; “复制代码”)

var obj = {
a: 10,
b: function(){

console.log(this.a); //10

},
c: function() { return ()=>{

       console.log(this.a); //10

}
}
}
obj.b();
obj.c()();

复制代码; “复制代码”)

箭头函数通过 call()  或   apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。


let obj2 = {
a: 10,
b: function(n) {
let f \= (n) => n + this.a; return f(n);
},
c: function(n) {
let f \= (n) => n + this.a;
let m \= {
a: 20 }; return f.call(m,n);
}
};
console.log(obj2.b(1));  // 11
console.log(obj2.c(1)); // 11

箭头函数没有原型属性


var a = ()=>{ return 1;
} function b(){ return 2;
}
console.log(a.prototype); // undefined
console.log(b.prototype);   // {constructor: ƒ}

箭头函数不能当做Generator函数,不能使用yield关键字

总结

  • 箭头函数的 this 永远指向其上下文的  this ,任何方法都改变不了其指向,如 call() ,  bind() ,  apply() 
  • 普通函数的this指向调用它的那个对象

工作中常用到的ES6语法

一、let和const

在JavaScript中咱们以前主要用关键var来定义变量,ES6之后,新增了定义变量的两个关键字,分别是let和const。
对于变量来说,在ES5中var定义的变量会提升到作用域中所有的函数与语句前面,而ES6中let定义的变量则不会,let声明的变量会在其相应的代码块中建立一个暂时性死区,直至变量被声明。
let和const都能够声明块级作用域,用法和var是类似的,let的特点是不会变量提升,而是被锁在当前块中。

一个非常简单的例子:

function test() {
if(true) {
console.log(a)//TDZ,俗称临时死区,用来描述变量不提升的现象
let a = 1
}
}
test()  // a is not defined
function test() {
if(true) {
let a = 1
}
console.log(a)
}
test() // a is not defined

唯一正确的使用方法:先声明,再访问。

function test() {
if(true) {
let a = 1
console.log(a)
}
}
test() // 1

const
声明常量,一旦声明,不可更改,而且常量必须初始化赋值。
const虽然是常量,不允许修改默认赋值,但如果定义的是对象Object,那么可以修改对象内部的属性值。

const type = {
a: 1
}
type.a = 2 //没有直接修改type的值,而是修改type.a的属性值,这是允许的。
console.log(type) // {a: 2}

const和let的异同点
相同点:const和let都是在当前块内有效,执行到块外会被销毁,也不存在变量提升(TDZ),不能重复声明。
不同点:const不能再赋值,let声明的变量可以重复赋值。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

块级作用域的使用场景
除了上面提到的常用声明方式,我们还可以在循环中使用,最出名的一道面试题:循环中定时器闭包的考题
在for循环中使用var声明的循环变量,会跳出循环体污染当前的函数。

for(var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) //5, 5, 5, 5, 5
}, 0)
}
console.log(i) //5 i跳出循环体污染外部函数
//将var改成let之后
for(let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) // 0,1,2,3,4
}, 0)
}
console.log(i)//i is not defined i无法污染外部函数

在实际开发中,我们选择使用var、let还是const,取决于我们的变量是不是需要更新,通常我们希望变量保证不被恶意修改,而使用大量的const。使用const声明,声明一个对象的时候,也推荐使用const,当你需要修改声明的变量值时,使用let,var能用的场景都可以使用let替代。

symbol
ES6 以前,我们知道5种基本数据类型分别是Undefined,Null,Boolean,Number以及String,然后加上一种引用类型Object构成了JavaScript中所有的数据类型,但是ES6出来之后,新增了一种数据类型,名叫symbol,像它的名字表露的一样,意味着独一无二,意思是每个 Symbol类型都是独一无二的,不与其它 Symbol 重复。
可以通过调用 Symbol() 方法将创建一个新的 Symbol 类型的值,这个值独一无二,不与任何值相等。

var mySymbol=Symbol();
console.log(typeof mySymbol) //"symbol"

二、字符串

ES6字符串新增的方法

UTF-16码位:ES6强制使用UTF-16字符串编码。关于UTF-16的解释请自行百度了解。

codePointAt():该方法支持UTF-16,接受编码单元的位置而非字符串位置作为参数,返回与字符串中给定位置对应的码位,即一个整数值。

String.fromCodePoiont():作用与codePointAt相反,检索字符串中某个字符的码位,也可以根据指定的码位生成一个字符。

normalize():提供Unicode的标准形式,接受一个可选的字符串参数,指明应用某种Unicode标准形式。

在ES6中,新增了3个新方法。每个方法都接收2个参数,需要检测的子字符串,以及开始匹配的索引位置。

模板字符串
字符串是JavaScript中基本类型之一,应该算是除了对象之外是使用最为频繁的类型吧,字符串中包含了例如substr,replace,indexOf,slice等等诸多方法,ES6引入了模板字符串的特性,用反引号来表示,可以表示多行字符串以及做到文本插值(利用模板占位符)。

// 以前的多行字符串我们这么写:
console.log("hello world 1\n\
hello cala");
// "hello world
// hello cala"
//有了模板字符串之后
console.log(`hello world
string text line 2`);
// "hello world
// hello cala"

可以用${}来表示模板占位符,可以将你已经定义好的变量传进括弧中,例如:

var name="cala";
var age=22;
console.log(`hello,I'am ${name},my age is ${age}`)
//hello,I'am cala,my age is 22

includes(str, index):如果在字符串中检测到指定文本,返回true,否则false。

let t = 'abcdefg'
if(t.includes('cde')) {
console.log(2)
}
//true

startsWith(str, index):如果在字符串起始部分检测到指定文本,返回true,否则返回false。

let t = 'abcdefg'
if(t.startsWith('ab')) {
console.log(2)
}
//true

endsWith(str, index):如果在字符串的结束部分检测到指定文本,返回true,否则返回false。

let t = 'abcdefg'
if(t.endsWith('fg')) {
console.log(2)
}
//true

如果你只是需要匹配字符串中是否包含某子字符串,那么推荐使用新增的方法,如果需要找到匹配字符串的位置,使用indexOf()。

三、函数

函数的默认参数
在ES5中,我们给函数传参数,然后在函数体内设置默认值,如下面这种方式。

function a(num, callback) {
num = num || 6
callback = callback || function (data) {console.log('ES5: ', data)}
callback(num * num)
}
a() //ES5: 36,不传参输出默认值
//你还可以这样使用callback
a(10, function(data) {
console.log(data * 10) // 1000, 传参输出新数值
})

在ES6中,我们使用新的默认值写法

function a(num = 6, callback = function (data) {console.log('ES6: ', data)}) {
callback(num * num)
}
a() //ES6: 36, 不传参输出默认值
a(10, function(data) {
console.log(data * 10) // 1000,传参输出新数值
})

四、箭头函数(=>)

(箭头函数比较重要,现在简单提一下,迟一点有空专门写一篇箭头函数的文章。)

const arr = [5, 10]
const s = arr.reduce((sum, item) => sum + item)
console.log(s) // 15

箭头函数中this的使用跟普通函数也不一样,在JavaScript的普通函数中,都会有一个自己的this值,主要分为:
普通函数:
1、函数作为全局函数被调用时,this指向全局对象
2、函数作为对象中的方法被调用时,this指向该对象
3、函数作为构造函数的时候,this指向构造函数new出来的新对象
4、还可以通过call,apply,bind改变this的指向
箭头函数:
1、箭头函数没有this,函数内部的this来自于父级最近的非箭头函数,并且不能改变this的指向。
2、箭头函数没有super
3、箭头函数没有arguments
4、箭头函数没有new.target绑定。
5、不能使用new
6、没有原型
7、不支持重复的命名参数。

箭头函数的简单理解

1、箭头函数的左边表示输入的参数,右边表示输出的结果。

const s = a => a
console.log(s(2)) // 2

2、在箭头函数中,this属于词法作用域,直接由上下文确定,对于普通函数中指向不定的this,箭头函数中处理this无疑更加简单,如下:

//ES5普通函数
function Man(){
this.age=22;
return function(){
this.age+1;
}
}
var cala=new Man();
console.log(cala())//undefined
//ES6箭头函数
function Man(){
this.age=22;
return () => this.age+1;
}
var cala=new Man();
console.log(cala())//23

3、箭头函数中没有arguments(我们可以用rest参数替代),也没有原型,也不能使用new 关键字,例如:

//没有arguments
var foo=(a,b)=>{return arguments[0]*arguments[1]}
console.log(foo(3,5))
//arguments is not defined
//没有原型
var Obj = () => {};
console.log(Obj.prototype);
// undefined
//不能使用new 关键字
var Obj = () => {"hello world"};
var o = new Obj();
// TypeError: Obj is not a constructor

4、箭头函数给数组排序

const arr = [10, 50, 30, 40, 20]
const s = arr.sort((a, b) => a - b)
console.log(s) // [10,20,30,40,50]

尾调用优化
尾调用是指在函数return的时候调用一个新的函数,由于尾调用的实现需要存储到内存中,在一个循环体中,如果存在函数的尾调用,你的内存可能爆满或溢出。

ES6中,引擎会帮你做好尾调用的优化工作,你不需要自己优化,但需要满足下面3个要求:
1、函数不是闭包
2、尾调用是函数最后一条语句
3、尾调用结果作为函数返回

尾调用实际用途——递归函数优化
在ES5时代,我们不推荐使用递归,因为递归会影响性能。
但是有了尾调用优化之后,递归函数的性能有了提升。

//新型尾优化写法
"use strict";
function a(n, p = 1) {
if(n <= 1) {
return 1 * p
}
let s = n * p
return a(n - 1, s)
}
//求 1 x 2 x 3的阶乘
let sum = a(3)
console.log(sum) // 6

五、ES6对象新增方法

Object.assign()
Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。
String类型和 Symbol 类型的属性都会被拷贝。
合并对象

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。

合并具有相同属性的对象

var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };
var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

六、Map和Set

Map和Set都叫做集合,但是他们也有所不同。Set常被用来检查对象中是否存在某个键名,Map集合常被用来获取已存的信息。
Set是有序列表,含有相互独立的非重复值。
Array和Set对比
都是一个存储多值的容器,两者可以互相转换,但是在使用场景上有区别。如下:
Array的indexOf方法比Set的has方法效率低下
Set不含有重复值(可以利用这个特性实现对一个数组的去重)
Set通过delete方法删除某个值,而Array只能通过splice。两者的使用方便程度前者更优
Array的很多新方法map、filter、some、every等是Set没有的(但是通过两者可以互相转换来使用)
Object和Map对比
Object是字符串-值,Map是值-值
Object键为string类型,Map的键是任意类型
手动计算Object尺寸,Map.size可以获取尺寸
Map的排序是插入顺序
Object有原型,所以映射中有一些缺省的键。可以理解为Map=Object.create(null)

Set操作集合

let set = new Set()
// Set转化为数组
let arr = Array.from(set)
let arr = [...set]
// 实例属性(继承自Set)
set.constructor === Set
set.size
// 操作方法
set.add(1) // 添加一个值
set.delete(1) //删除一个值
set.has(1) //判断是否有这个值(Array中的indexOf)
set.clear() //清除所有值
// 获取用于遍历的成员方法(Set的遍历顺序就是插入顺序)
set.keys() // 返回键名的遍历器
set.values() // 返回键值得遍历器
set.entries() // 返回键值对的遍历器
set.forEach() // 循环遍历每个值(和Array的方法一致)
for (let key of set.keys()){}
for (let val of set.values()){}
for (let entry of set.entries()){}
// 使用数组方法来处理set值
set = new Set(arr)
set = new Set([...set].map((x) => x = x * 2))
set = new Set([...set].filter((x) => x > 2))

Map的方法集合

let map = new Map()
// 实例属性(继承自Map)
map.constructor === Map
map.size
// 操作方法
map.set(1,2)
map.get(1)
map.delete(1)
map.has(1)
map.clear()
// 遍历方法
map.keys()
map.values()
map.entries()
map.forEach()
// Map和数组的转换
map = new Map([['key','val'],[2,1]]) // 要求双成员数组
let arr = [...map]
// 值得注意的是Map的键是跟内存绑定的
map.set([1], 's')
map.get([1])
let arr = [1]
let arr1 = [1]
map.set(arr, 's')
map.get(arr)
map.set(arr1, 's')
map.get(arr1)

想要深入理解Set和Map,可以查看《深入理解:ES6中的Set和Map数据结构,Map与其它数据结构的互相转换》

七、迭代器(Iterator)

1、entries() 返回迭代器:返回键值对

//数组
const arr = ['a', 'b', 'c'];
for(let v of arr.entries()) {
console.log(v)
}
// [0, 'a'] [1, 'b'] [2, 'c']
//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.entries()) {
console.log(v)
}
// ['a', 'a'] ['b', 'b'] ['c', 'c']
//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.entries()) {
console.log(v)
}
// ['a', 'a'] ['b', 'b']

2、values() 返回迭代器:返回键值对的value

//数组
const arr = ['a', 'b', 'c'];
for(let v of arr.values()) {
console.log(v)
}
//'a' 'b' 'c'
//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.values()) {
console.log(v)
}
// 'a' 'b' 'c'
//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.values()) {
console.log(v)
}
// 'a' 'b'

3、keys() 返回迭代器:返回键值对的key

//数组
const arr = ['a', 'b', 'c'];
for(let v of arr.keys()) {
console.log(v)
}
// 0 1 2
//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.keys()) {
console.log(v)
}
// 'a' 'b' 'c'
//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.keys()) {
console.log(v)
}
// 'a' 'b'

虽然上面列举了3种内建的迭代器方法,但是不同集合的类型还有自己默认的迭代器,在for of中,数组和Set的默认迭代器是values(),Map的默认迭代器是entries()。

for of循环解构

对象本身不支持迭代,但是我们可以自己添加一个生成器,返回一个key,value的迭代器,然后使用for of循环解构key和value。

const obj = {
a: 1,
b: 2,
*[Symbol.iterator]() {
for(let i in obj) {
yield [i, obj[i]]
}
}
}
for(let [key, value] of obj) {
console.log(key, value)
}
// 'a' 1, 'b' 2

字符串迭代器

const str = 'abc';
for(let v of str) {
console.log(v)
}
// 'a' 'b' 'c'

ES6给数组添加了几个新方法:find()、findIndex()、fill()、copyWithin()

1、find():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。

const arr = [1, "2", 3, 3, "2"]
console.log(arr.find(n => typeof n === "number")) // 1

2、findIndex():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它的下标,终止搜索。

const arr = [1, "2", 3, 3, "2"]
console.log(arr.findIndex(n => typeof n === "number")) // 0

3、fill():用新元素替换掉数组内的元素,可以指定替换下标范围。

arr.fill(value, start, end)

4、copyWithin():选择数组的某个下标,从该位置开始复制数组元素,默认从0开始复制。也可以指定要复制的元素范围。

arr.copyWithin(target, start, end)
const arr = [1, 2, 3, 4, 5]
console.log(arr.copyWithin(3)) // [1,2,3,1,2] 从下标为3的元素开始,复制数组,所以4, 5被替换成1, 2
const arr1 = [1, 2, 3, 4, 5]
console.log(arr1.copyWithin(3, 1)) // [1,2,3,2,3] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,所以4, 5被替换成2, 3
const arr2 = [1, 2, 3, 4, 5]
console.log(arr2.copyWithin(3, 1, 2)) // [1,2,3,2,5] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,结束位置为2,所以4被替换成2

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Scroll Up