BFC 块级格式化上下文 一块独立的区域,有自己的规则,bfc中的元素与外界的元素互不影响
BFC是一块用来独立的布局环境,保护其中内部元素不受外部影响,也不影响外部。
怎么触发BFC
1. float的值left或right
2. overflow的值不为visible(默认)
3. display的值为inline-block、table-cell、table-caption
4. position的值为absolute(绝对定位)或fixed固定定位
规则:
1、BFC的区域不会与float box重叠。
2、BFC是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。
3、计算BFC的高度时,浮动元素也会参与计算。
4、内部的Box会在垂直方向上一个接一个放置。
5、Box垂直方向的距离由margin决定,属于同一个BFC的两个相邻Box的margin会发生重叠。
1、可以用来自适应布局
利用BFC的这个原理可以实现两栏布局,左边定宽,右边自适应。不会相互影响,哪怕高度不相等。
给左边盒子加浮动,右边盒子加overflow:hidden;变成BFC,就可以消除外部左边盒子因浮动对他的影响
2、可以清除浮动
一个父元素中的子元素,设置浮动时,父元素没有设置高度,这时子元素脱离文档流,父元素感知不到子元素的高度,造成父元素的塌陷。 这时候给父元素添加overflow:hidden / auto,变成BFC就可以解决这种问题。
1.父子关系的边距重叠
父子关系,如果子元素设置了外边距,在没有把父元素变成BFC的情况下,父元素也会产生外边距。
解决办法: 是给父元素添加一个 overflow:hidden,这样父元素就变为BFC,不会随子元素产生外边距
2.同级兄弟关系的重叠
同级元素在垂直方向上外边距会出现重叠现象,最后外边距的大小取两者绝对值大的那个
可通过添加一个空元素或伪类元素,设置overflow:hidden;解决
渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后在针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
· 浏览器根据请求的 URL 交给 DNS 进行域名解析,找到真实 IP 地址,向服务器发起请求;
· 服务器交给后台处理完成后返回数据,浏览器接收文件(HTML、CSS、JS、images等);
· 浏览器对加载到的资源(HTML、CSS、JS、images等)进行语法解析,建立相应的内部数据结构(如HTML的DOM);
· 载入解析到的资源文件,渲染页面,完成。
1、继承性:子标签会继承父标签的某些样式,如文本颜色和字号。(text- font- color)
2、层叠性:样式冲突,遵循的原则是就近原则。
3、优先级:定义CSS样式时,经常出现两个或更多规则应用在同一元素上,此时,谁的权重高显示谁的样式。
(选择器相同,则执行层叠性;选择器不同,就会出现优先级的问题。)
!Important > 行内式 > id > 类/伪类/属性 > 标签选择器 > 全局
(对应权重:无穷大∞>1000>100>10>1>0)
static: 默认值 没有定位,元素出现在正常的流中
relative(相对定位):生成相对定位的元素,相对于其正常(原先本身)位置进行定位
absolute(绝对定位):生成绝对定位的元素,相对于static定位以外的第一个父元素进行定位
fixed(固定定位):生成绝对定位的元素,相对于浏览器窗口进行定位
sticky 粘性定位 当前元素设置了粘性定位,滚动到顶部就会吸附顶部,往下滑还回到原来位置。
z-index规则
1、值可以是正整数、负整数或0,数值越大,盒子越靠上;
2、如果属性值相同,则按照书写顺序,后来居上;
3、数字后面不能加单位。
4、
z-index
只能应用于相对定位、绝对定位和固定定位的元素,其他标准流、浮动和静态定位无效
页面中使用CSS的方式主要有3种:行内添加定义style属性值,页面头部内嵌调用和外面链接调用,其中外面引用有两种:Link引入和@import导入,两者都是外部引用CSS的方式,但是存在一定的区别:
1、从属关系: link是标签,@import是css提供的.
2. 加载差异: link: 结构和样式同时加载;而@import 先加载结构,后加载样式
3. 兼容性:link没有兼容问题,@import不兼容ie5以下的浏览器.
4.可操作性: link可以通过js操作dom插入link标签改变样式,而@import不能
src用于替换当前元素
href用于在当前文档和引用资源之间确立联系.
扩展: src是source的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置 href是Hypertext Reference的缩写,指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接
1、相邻块元素垂直外边距的合并
解决方案:尽量给只给一个盒子添加margin值
2、嵌套块元素垂直外边距的合并(塌陷)
解决方案:
可以为父元素定义上边框。
可以为父元素定义上内边距
可以为父元素添加overflow:hidden。
1、不同浏览器margin和padding不同
2、ie6中,父级元素浮动以后,内部元素内容撑不开宽度
3、标签嵌套不规范,如p和h1-h6里面嵌套div
4、ie6小于19px,会当成19px处理,也就元素宽高小于19px的bug
5、图片3像素问题
6、IE8下给图片添加超链接时,图片会有蓝色边框
7、鼠标滑过时,不显示小手
1、浏览器的默认行为是把inline元素间的空白字符(空格换行tab)渲染成一个空格
解决方案:
为li设置左浮动
将li写在同一行 中间不要有空格
将ul内font-size:0,但是单独li设置文字大小
将ul的letter-spacing:-8px,但是单独li设置字符间距normal
头部标签:
导航标签:
内容区块表签:
页脚标签:
侧边栏:
页面内独立的内容区域:
区别:
1. 普通盒模型中的子元素分配其父元素的空间,而弹性盒模型中的子元素分配其父元素的可用空间。
2. 普通盒模型主要针对块级元素和行级元素的布局,而弹性盒是建立在弹性流上,也就是元素可以随着可视区域的变化而呈现流式布局。
弹性盒的优点:
能为盒模型提供最大的灵活性,即使是不定宽高的元素依然好用,可以简便、完整、响应式地实现各种页面布局。
传统布局的特点:兼容性好、布局繁琐、局限性,不能在移动端很好的布局
设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效
rem是一个相对单位,rem的是相对于html元素的字体大小,没有继承性
em是一个相对单位,是相对于父元素字体大小有继承性
px是一个“绝对单位”,就是css中定义的像素,利用px设置字体大小及元素的宽高等,比较稳定和精确。
在pc端中,视口指的是在pc端中浏览器的可视区域;
在移动端中,它涉及3个视口:1是布局视口,2是视觉视口,3是理想视口
移动端指的视口就是布局视口
媒体查询是CSS3新语法。
使用媒体查询,可以针对不同的媒体类型定义不同的样式
媒体查询可以针对不同的屏幕尺寸设置不同的样式
当你重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面
目前针对很多苹果手机、Android手机,平板等设备都用得到多媒体查询
1.百分比布局,但是无法对字体,边框等比例缩放
2.弹性盒子布局 display:flex
3.rem布局,1rem=html的font-size值的大小
4. css3媒体查询 @media screen and(max-width: 750px){}
5.vw+vh
6.使用一些框架(bootstrap,vant)
什么是响应式设计:响应式网站设计是一个网站能够兼容多个终端,智能地根据不同设备环境进行相对应的布局
响应式设计的基本原理:基本原理是通过媒体查询检测不同的设备屏幕尺寸设置不同的css样式 页面头部必须有meta声明的
1、什么是CSS盒模型?
在我们的HTML页面中,每一个元素都可以被看成一个盒子,而这个盒子由:内容(content)、内边距(padding)、 边框(border)、外边距(margin) 四部分组成.
2、有哪几种盒模型?
对于盒模型,分为标准盒模型和怪异盒模型一下两种
标准(W3C)盒模型的范围包括margin、border、padding、content,并且宽高只包含content,不包含其他部分
怪异(IE)盒模型的范围包括margin、border、padding、content,和标准盒模型不同的是,怪异盒模型的宽高包含了padding和 border
box-sizing作用
用来控制元素的盒子模型的解析模式,默认为content-box context-box标准盒模型 border-box怪异盒模型
文档声明;用于告知浏览器该以何种模式来渲染文档.
严格模式和混杂模式的区别:
严格模式:页面排版及 JS 解析是以该浏览器支持的最高标准来执行
混杂模式:不严格按照标准执行,主要用来兼容旧的浏览器,向后兼容
1、为什么会出现浮动:
由于浮动元素脱离了文档流,所以文档流的块框表现得就像浮动框不存在一样。浮动元素会漂浮在文档流的块框上.
2、浮动带来的问题:
父元素的高度无法被撑开,影响与父元素同级的元素
若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构.
与浮动元素同级的非浮动元素(内联元素)会跟随其后
3、清除浮动的方式:
父级div定义height
结尾处加空div标签clear:both
父级div定义伪类:after、before
父级div定义overflow:hidden
父级div定义overflow:auto。
父级div也浮动,需要定义宽度。
父级div定义display:table。
结尾处加br标签clear:both
比较好的是第3种,无需多余标签,方便维护,通过伪类就可以解决
不同点: 元素的alt是表示图片加载失败显示的文本内容,而title是表示鼠标悬停图片时显示的文本内容.
相同点: 在alt和title同时设置的时候,alt作为图片的替代文字出现,title是图片的解释文字
正常场景一般都适用div+CSS布局,Div+CSS优点:
开发中结构与样式分离,便于后期项目的维护和迭代
代码语义性好
更符合HTML标准规范
SEO友好
table缺点:
太深的嵌套,比如table>tr>td>h3,会导致搜索引擎读取困难,而且,最直接的损失就是大大增加了冗余代码量
灵活性差,比如要将tr设置border等属性,是不行的,得通过td
代码臃肿,当在table中套用table的时候,阅读代码会显得异常混乱
混乱的colspan与rowspan,用来布局时,频繁使用他们会造成整个文档顺序混乱,不够语义化。
单冒号(:)用于CSS3伪类,
双冒号(::)用于CSS3伪元素。伪元素和伪类之所以这么容易混淆,是因为他们的效果类似而且
伪类: 用于已有元素处于某种状态时为其添加对应的样式,这个状态是根据用户行为而动态变化的。
例如: 当用户悬停在指定元素时,可以通过:hover来描述这个元素的状态,虽然它和一般css相似,可以为已有元素添加样式,但是它只有处于DOM树无法描述的状态下才能为元素添加样式,所以称为伪类。
伪元素: 用于创建一些不在DOM树中的元素,并为其添加样式。
例如: 我们可以通过:before来在一个元素之前添加一些文本,并为这些文本添加样式,虽然用户可以看见这些文本,但是它实际上并不在 DOM文档中。
回流:
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree.
重绘:
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘.
区别:
回流必将引起重绘,而重绘不一定会引起回流。比如:只有颜色改变的时候就只会发生重绘而不会引起回流,当页面布局和几何属性改变时就需要回流
语义化标签:header、footer、section、nav、aside、article
增强型表单:input 的多个 type calendar、date、time、url、search、tel、file、number 新增表单属性:placehoder、required、min 和 max
音频视频:audio、video
canvas 画布
kan wa si 地理定位(Geolocation)
拖拽释放:拖拽是一种常见的特性,即抓取对象以后拖到另一个位置,在HTML5中,
本地存储: localStorage 没有时间限制的数据存储; sessionStorage, session 的数据存储,当用户关闭浏览器窗口后,
数据会被删除 新事件:onresize、ondrag、onscroll、onmousewheel、onerror、onplay、onpause
WebSocket:建立持久通信协议,新的技术:webworker、websocket、Geolocation
1、颜色:新增RGBA、HSLA模式
2、文字阴影:(text-shadow)
3、边框:圆角(border-radius)边框阴影:box-shadow
4、盒子模型:box-sizing
5、背景:background-size,background-origin background-clip(削弱)
6、渐变:linear-gradient(线性渐变):
eg: background-image: linear-gradient(100deg, #237b9f, #f2febd);
radial-gradient (径向渐变)
7、过渡:transition可实现动画
8、自定义动画:animate@keyfrom
9、媒体查询:多栏布局@media screen and (width:800px)
10、border-image
11、2D转换:transform:translate(x,y) rotate(x,y)旋转 skew(x,y)倾斜 scale(x,y)缩放
12、3D转换
13、字体图标:font-size
14、弹性布局:flex
浏览器将获取的HTML文档解析成DOM树。
处理CSS标记,构成层叠样式表模型CSSOM(CSS Object Model)。
将DOM和CSSOM合并为渲染树(
rendering tree
),代表一系列将被渲染的对象。渲染树的每个元素包含的内容都是计算过的,它被称之为布局
layout
。浏览器使用一种流式处理的方法,只需要一次绘制操作就可以布局所有的元素。将渲染树的各个节点绘制到屏幕上,这一步被称为绘制
painting
。
构建对象模型(DOM,CSSOM)
构建渲染树(RenderTree)
布局
渲染
方法一:给父元素设置成弹性盒子,子元素横向居中,纵向居中
方法二:父相子绝后,子部分向上移动本身宽度和高度的一半,也可以用transfrom:translate(-50%,-50%)(最常用方法)
方法三:父相子绝,子元素所有定位为0,margin设置auto自适应
两栏布局,左边定宽,右边自适应
三栏布局、圣杯布局、双飞翼布局
圣杯布局和双飞翼布局是前端工程师需要日常掌握的重要布局方式。两者的功能相同,都是为了实现一个两侧宽度固定,中间宽度自适应的三栏布局。(中间先加载渲染)
1. 首先要给两侧设置padding预留出相应的空间
2. 随后分别为三列设置宽度与浮动,同时对footer设置清除浮动
3. 根据浮动的特性,由于center的宽度为100%,即占据了第一行的所有空间,所以left和right被“挤”到了第二行。
4. 接下来的工作是将left放置到之前预留出的位置上,这里使用负外边距
5. 这里使用position: relative和right: 200px将left的位置在原有位置基础上左移200px,以完成left的放置
Flex 布局,可以简便、完整、响应式地实现各种页面布局,任何一个容器都可以指定为 Flex 布局,行内元素也可以使用 Flex 布局。
我在项目中常用到的有九宫格布局,列表布局等,都会经常用到。
flex的属性:
flex-direction :设置主轴的方向
justify-content :设置主轴上的子元素排列方式
flex-wrap :设置子元素是否换行
align-content :设置侧轴上的子元素排列方式(多行)
align-items :设置侧轴上的子元素排列方式(单行)
flex-flow :复合属性,相当于同时设置了flex-direction和flex-wrap
flex-grow:1;flex-shrink:1;flex-basis:auto; 这三个样式的和写
flex: 1 1 auto; 简写 flex:1;
flex-grow:0; 扩展比率
默认值为0,容器中项目没有占满时,不分配剩余空间。
flex-shrink:1; 收缩比率
默认值为1,容器中项目超出容器大小时,把项目平均压缩到容器内。
flex-basis:auto; 伸缩基准值
默认值为auto,定义容器中项目的占据空间,一般用百分数设值
在CSS中规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,比如div默认display属性值为“block”,成为“块级”元素;span默认display属性值为“inline”,是“行内”元素。
我们在平常的项目中经常使用到的有
· 行内元素有:span a b i img input select strong
· 块级元素有:div p h1-h6 ul table form ul ol li dl dt dd…
· 空元素(没有内容):
概念:CSS hack是通过在CSS样式中加入一些特殊的符号,让不同的浏览器识别不同的符号(什么样的浏览器识别什么样的符号是有标准的,CSS hack 就是让你记住这个标准),以达到应用不同的 CSS 样式的目的
1、 条件hack
条件注释只有在IE浏览器下才能执行,这个代码在非IE浏览下被当做注释视而不见。可以通过IE条件注释载入不同的CSS、JS、HTML和服务器代码等。
2、 选择符Hack
比如IE6能识别 html .class{},IE7能识别+html .class{}
3、属性Hack 比如IE6能识别下划线_和星号_,IE7能识别星号_,但不能识别下划线_,而firefox两个都不能认识。
1、写CSS hack需要遵循以下三条原则:
· 有效: 能够通过 Web 标准的验证
· 只针对太古老的/不再开发的/已被抛弃的浏览器, 而不是目前的主流浏览器
· 代码要丑陋。让人记住这是一个不得已而为之的 Hack, 时刻记住要想办法去掉它。现在很多hacks已经抛弃了最初的原则,而滥用hack会导致浏览器更新之后产生更多的兼容性问题。因此,并不推荐使用CSS hack来解决兼容性问题。
html被称为超文本标记语言, 是一种描述性语言,用html 可以创建能在互联网上传输的信息页,是构成网页文档的主要语言,它是由很多的标签组成
xml 即可扩展标记语言,是Internet环境中跨平台的、依赖于内容的技术,是当前处理结构化文档信息的有力工具,满足了Web内容发布与交换的需要,适合作为各种存储与共享的通用平台。
都可以通过DOM 变成方式来访问。
都可以通过CSS来改变外观。
html和xml 都是标记语言,都是基于文本编辑和修改的。
xml不是要来取代html的,是对html的补充,用来与html协同工作的语言,基于上面这些优势,xml将来成为所有的数据处理和数据传输的常用工具非常可观。
? ?background: rgba(244, 243, 244, 0.18);
? ?box-shadow: 0 0.3px 0.7px rgba(0, 0, 0, 0.126),
? ? ?0 0.9px 1.7px rgba(0, 0, 0, 0.179), 0 1.8px 3.5px rgba(0, 0, 0, 0.224),
? ? ?0 3.7px 7.3px rgba(0, 0, 0, 0.277), 0 10px 20px rgba(0, 0, 0, 0.4);
? ?backdrop-filter: blur(10px);
新开了一个子线程,而且子线程不受线程的影响
- 大数据处理,耗费时间较长的操作
常用API:
1. fillRect(x,y,width,height) 实心矩形;
2. strokeRect(x,y,width,height) 空心矩形;
3. fillText( "Hello world" , 200 , 200 ) 实心文字;
4. strokeText( "Hello world" , 200 , 300 ) 空心文字;
在我们开发中,语义化让,页面结构更加清晰,便于后期的维护,便于浏览器,搜索引擎解析
理由搜索引擎的爬取,利于seo
在不同的浏览器中,浏览器的内核都是不相同的,所以各个浏览器对网页的解析存在一定的差异。
简单来说就是写的代码在各个浏览器上显示不同的效果
解决:
1、css3新属性,加浏览器前缀兼容早期浏览
-moz- 火狐浏览器
-webkit- Safari, 谷歌浏览器等使用Webkit引擎的浏览器
-o- Opera浏览器(早期)
-ms- IE
2、css hack解决浏览器兼容性不同浏览器,识别不同的样式,css hack本身就是处理浏览器兼容的
3、图片默认有间距:
几个img标签放在一起的时候,有些浏览器会有默认的间距,通配符清除间距也不起作用。 可以通过使用float属性为img布局(所有图片左浮)
4、不同浏览器的标签默认的 margin 和 padding 不同
解决办法:可以通过设置全局样式来解决这个问题,这样所有的样式都会重置,初始值都会相同了。
基本数据类型
ES5的5种:Null,undefined,Boolean,Number,String, ES6新增:Symbol表示独一无二的值 ES10新增:BigInt 表示任意大的整数
一种引用数据类型:(本质上是由一组无序的键值对组成)
引用数据类型: Object。包含Object、Array、 function、Date、RegExp。 JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8中之一。
相同:
在 if 语句中 null 和 undefined 都会转为false两者用相等运算符比较也是相等
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
不同:
undefined 代表的含义是未定义,
定义了形参,没有传实参,显示undefined
一般变量声明了但还没有定义的时候会返回 undefined
对象属性名不存在时,显示undefined
函数没有写返回值,即没有写return,拿到的是undefined
null 代表的含义是空对象。也作为对象原型链的终点
null 主要用于赋值给一些可能会返回对象的变量,作为初始化。
BigInt
数据类型的目的是比Number
数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt
,整数溢出将不再是问题。此外,可以安全地使用更加准确时间戳,大整数ID等,而无需使用变通方法。 BigInt目前是第3阶段提案, 一旦添加到规范中,它就是JS 第二个数字数据类型,也将是 JS 第8种基本数据类型:
要创建BigInt,只需在整数的末尾追加n即可。比较:
console.log(9007199254740995n); ? // → 9007199254740995n
console.log(9007199254740995); ? ? // → 9007199254740996
?
或者,可以调用BigInt()构造函数
BigInt("9007199254740995"); ? // → 9007199254740995n
?
// 注意最后一位的数字
9007199254740992 === 9007199254740993; ? // → true
console.log(9999999999999999); ? // → 10000000000000000
基本数据类型:直接存储在栈内存中,占据空间小,大小固定,属于被频繁使用的数据。 引用数据类型:同时存储在栈内存与堆内存中,占据空间大,大小不固定。
引用数据:类型将指针存在栈中,将值存在堆中。 当我们把对象值赋值给另外一个变量时,复制的是对象的指针,指向同一块内存地址
基本类型值:指的是保存在栈内存中的简单数据段;number string 布尔
引用类型值:指的是那些保存在堆内存中的对象,意思是,变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值,数组 对象
引用类型存储把值存储在堆内存中,堆内存是从下往上存储。生成唯一内存地址。然后在栈内存中把地址赋值给变量。栈内存是从上往下存储的。之所以如此划分内存主要考虑到特别大的对象进行值传递时的效率问题
- 栈 是一种连续储存的数据结构,具有先进后出的性质。
通常的操作有入栈(压栈),出栈和栈顶元素。想要读取栈中的某个元素,就是将其之间的所有元素出栈才能完成。
- 堆 是一种非连续的树形储存数据结构,每个节点有一个值,整棵树是经过排序的。特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。常用来实现优先队列,存取随意。
所有基本类型中Boolean值是false的只有6个,分别是 : 0 NaN ’ ’ null undefined false 引用类型Boolean值全是true.
if条件是单个值时,如果是truly值,条件成立, 如果是falsely值,条件不成立
? ? ? && 逻辑与 ? 两边都是true,才返回true,否则返回false
? ? ? || 逻辑或 ? 两边只要有一个是true,就返回true,否则返回false
? ? ? ! 逻辑非 ? 用来取一个布尔值相反的值 ?
typeof 对于基本数据类型判断是没有问题的,但是遇到引用数据类型(如:Array)是不起作用
console.log(typeof 2); ? // number
console.log(typeof null); // object
`instanceof` 只能正确判断引用数据类型 而不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型
console.log([] instanceof Array); ? ? ? ? ? ? ? ? ? // true
console.log(function(){} instanceof Function); ? ? ? // true
console.log({} instanceof Object); ? ? ? ? ? ? ? ? ? // true
constructor 似乎完全可以应对基本数据类型和引用数据类型 但如果声明了一个构造函数,并且把他的原型指向了 Array 的原型,所以这种情况下,constructor 也显得力不从心
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
console.log((2).constructor === Number); // true
Object.prototype.toString.call() 完美的解决方案,可以通过toString() 来获取每个对象的类型,
?
`Object.prototype.toString.call()` 使用 Object 对象的原型方法 toString 来判断数据类型:
?
var a = Object.prototype.toString;
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));
补充:基本数据类型赋值的时候 赋的是具体的值 ? 引用数据类型传的是地址,一个变另一个跟着变
在JavaScript中类型转换有三种情况:
转换为数字(调用Number(),parseInt(),parseFloat()方法) 转换为字符串(调用.toString()或String()方法) 转换为布尔值(调用Boolean()方法) 还有隐式转换 注意:null、undefined没有.toString方法
?
转换为数字
Number():可以把任意值转换成数字,如果要转换的字符串中有不是数字的值,则会返回NaN
?
Number('1') ? // 1
Number(true) ?// 1
Number('123s') // NaN
Number({}) ?//NaN
?
?
parseInt(string,radix):解析一个字符串并返回指定基数的十进制整数,radix是2-36之间的整数,表示被解析字符串的基数。
parseInt('2') //2
parseInt('2',10) // 2
parseInt('2',2) ?// NaN
parseInt('a123') ?// NaN 如果第一个字符不是数字或者符号就返回NaN
parseInt('123a') ?// 123
?
?
parseFloat(string):解析一个参数并返回一个浮点数
?
parseFloat('123a')
//123
parseFloat('123a.01')
//123
parseFloat('123.01')
//123.01
parseFloat('123.01.1')
//123.01
?
隐式转换
let str = '123'
let res = str - 1 //122
str+1 // '1231'
+str+1 // 124
?
转换为字符串
.toString() ???注意:null,undefined不能调用
?
Number(123).toString()
//'123'
[].toString()
//''
true.toString()
//'true'
?
?
String() 都能转
String(123)
//'123'
String(true)
//'true'
String([])
//''
String(null)
//'null'
String(undefined)
//'undefined'
String({})
//'[object Object]'
?
?
隐式转换:当+两边有一个是字符串,另一个是其它类型时,会先把其它类型转换为字符串再进行字符串拼接,返回字符串
?
let a = 1
a+'' // '1'
转换为布尔值
0, ''(空字符串), null, undefined, NaN会转成false,其它都是true
Boolean()
Boolean('') //false
Boolean(0) //false
Boolean(1) //true
Boolean(null) //false
Boolean(undefined) //false
Boolean(NaN) //false
Boolean({}) //true
Boolean([]) //true
?
条件语句
?
let a
if(a) {
?//... ? //这里a为undefined,会转为false,所以该条件语句内部不会执行
}
?
隐式转换 !!
?
let str = '111'
console.log(!!str) // true
{}和[]的valueOf和toString的返回结果?
valueOf:返回指定对象的原始值
?
对象 返回值
Array 返回数组对象本身。
Boolean 布尔值。
Date 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
Function 函数本身。
Number 数字值。
Object 对象本身。这是默认情况。
String 字符串值。
Math 和 Error 对象没有 valueOf 方法。
?
toString:返回一个表示对象的字符串。默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,
toString() 返回 "[object type]",其中 type 是对象的类型。
?
({}).valueOf() ? //{}
({}).toString() ?//'[object Object]'
[].valueOf() ? ?//[]
[].toString() ? //''
?
=== 属于严格判断,直接判断两者类型是否相同,如果两边的类型不一致时,不会做强制类型准换,不同则返回false如果相同再比较大小,不会进行任何隐式转换对于引用类型来说,比较的都是引用内存地址,所以===这种方式的比较,除非两者存储的内存地址相同才相等,反之false
== 二等表示值相等。判断操作符两边对象或值是否相等类型可以不同,如果两边的类型不一致,则会进行强制类型转化后再进行比较,使用Number()转换成Number类型在进行判断。例外规则,null==undefined,null/undefined进行运算时不进行隐式类型转换。通常把值转为Boolean值,进行条件判断。Boolean(null)=Boolean(undefined)>false=false 结果为true
Object.is()在===基础上特别处理了NaN,-0,+0,保证-0与+0不相等,但NaN与NaN相等
==操作符的强制类型转换规则
?
字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true,否则,返回 false。
?
'1' == 1 // true
'1' === 1 // false
NaN == NaN //false
+0 == -0 //true
+0 === -0 // true
Object.is(+0,-0) //false
Object.is(NaN,NaN) //true
typeof null 的结果是Object。
在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:
000: object ? - 当前存储的数据指向一个对象。
?1: int ? ? ?- 当前存储的数据是一个 31 位的有符号整数。
010: double ? - 当前存储的数据指向一个双精度的浮点数。
100: string ? - 当前存储的数据指向一个字符串。
110: boolean ?- 当前存储的数据是布尔值。
如果最低位是 1,则类型标签标志位的长度只有一位;如果最低位是 0,则类型标签标志位的长度占三位,为存储其他四种数据类型提供了额外两个 bit 的长度。
有两种特殊数据类型:
undefined的值是 (-2)30(一个超出整数范围的数字);
null 的值是机器码 NULL 指针(null 指针的值全是 0)
那也就是说null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object。
事件是文档和浏览器窗口中发生的特定的交互瞬间,事件就发生了。一是直接在标签内直接添加执行语句,二是定义执行函数。 DOM事件分为两种类型
事件类型分两种:事件捕获、事件冒泡。 事件捕获就是:网景公司提出的事件流叫事件捕获流,由外往内,从事件发生的顶点开始,逐级往下查找,一直到目标元素。 事件冒泡:IE提出的事件流叫做事件冒泡就是由内往外,从具体的目标节点元素触发,逐级向上传递,直到根节点。 什么是事件流
事件流就是,页面接受事件的先后顺序就形成了事件流。
自定义事件
自定义事件,就是自己定义事件类型,自己定义事件处理函数。
**事件委托,**又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了
阻止事件冒泡
event.stopPropagation() .stop修饰符
addEventListener(‘click’,函数名,true/false) .captrue 事件委托
好处:提高性能,减少了事件绑定,从而减少内存占用
应用场景 在vue中事件委托:
我们经常遇到vue中v-for一个列表,列表的每一项都绑定了@click处理事件。我们都知道绑定这么多监听,从性能方面来说是不太好的。那我们我们可以通过把每个item的click事件委托给父元素的形式来实现
我们在封装这个函数的时候可以用addEventListener(事件监听)来实现 ,封装的函数有三个参数,第一个是要绑定事件的元素,第二个是要绑定的事件类型,第三个是事件的执行函数。 调用这个函数 就可以实现给某个元素绑定一个事件了。
作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。简单说:函数内部局部作用域,函数外面全局作用域。
作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域
全局作用域就是Js中最外层的作用域,在哪里都可以访问
函数作用域是js通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套
Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
防抖:所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
节流:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。
mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。
mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,之后只要是在被绑定元素节点区域内移动,都会被触发。
mouseenter 不支持事件冒泡 mouseover 会冒泡
Object.is() 是一种判断两个值是否相同的方法。
语法:Object.is(value1, value2);
参数:value1:要比较的第一个值。value2:要比较的第二个值。
返回值:一个布尔表达式,指示两个参数是否具有相同的值。
?
Object.assign() 方法用于将所有可枚举的自身属性从一个或多个源对象复制到目标对象。
语法:Object.assign(target, ...sources)
参数:target:目标对象——应用源属性的对象,修改后返回。sources:源对象——包含你要应用的属性的对象。
返回值:修改后的目标对象。
?
Object.entries() 方法返回给定对象自己的可枚举字符串键属性 [key, value] 对的数组。
它类似于使用 for...in 循环进行迭代,除了 for...in 循环还会枚举原型链中的属性。属性的顺序与通过手动循环对象的属性值给出的顺序相同。
语法:Object.entries(obj)
参数:obj:要返回其自己的可枚举字符串键属性 [key, value] 对的对象。返回值:给定对象自己的可枚举字符串键属性 [key, value] 对的数组。
?
Object.values() 方法返回给定对象自己的可枚举属性值的数组,其顺序与 for...in 循环提供的顺序相同。
语法:Object.values(obj)
参数:obj:要返回其可枚举自身属性值的对象。返回值:包含给定对象自己的可枚举属性值的数组。
?
Object.prototype.hasOwnProperty()
hasOwnProperty() 方法返回一个布尔值,指示对象是否具有指定的属性作为它自己的属性。
如果指定的属性是对象的直接属性,则该方法返回 true — 即使值为 null 或未定义。如果该属性是继承的或根本没有声明,则返回 false。
语法:hasOwnProperty(prop)
参数:prop:要测试的属性的字符串名称或符号。
返回值:如果对象将指定的属性作为自己的属性,则返回true;否则为false。
?
Object.keys()
Object.keys() 方法用于返回给定对象自己的可枚举属性名称的数组,以与普通循环相同的顺序迭代。
语法:Object.keys(obj)
参数:obj:要返回可枚举自身属性的对象。
返回值:表示给定对象的所有可枚举属性的字符串数组。
?
Object.prototype.toString()
toString() 方法返回一个表示对象的字符串。当对象将被表示为文本值或以期望字符串的方式引用对象时,将自动调用此方法 id。默认情况下,toString() 方法由从 Object 继承的每个对象继承。
语法:toString()
返回值:表示对象的字符串。
?
Object.freeze()
Object.freeze() 方法冻结一个对象,这意味着它不能再被更改。冻结对象可防止向其添加新属性,防止删除现有属性,防止更改现有属性的可枚举性、可配置性或可写性,并防止更改现有属性的值。它还可以防止其原型被更改。
语法:Object.freeze(obj)
参数:obj:要冻结的对象。返回值:传递给函数的对象。
对象:属性和方法的集合叫做对象(万物皆对象)。 面向对象:首先就是找对象,如果该对象不具备所需要的方法或属性,那就给它添加。 面向对象是一种编程思维的改变。通过原型的方式来实现面向对象编程。 创建对象的方式(4种):new Object、字面量、构造函数、原型。
① 首先比较两个对象的长度,如果长度不相等使flag为false,为不相等;
② 如果长度相等那就遍历对象1(对象2也可以),利用hasOwnProperty() 【哈斯欧尼PROpt】方法查看对象1中是否包含对象2中的属性或者方法,如果不包含则使flag为false,为不想等。
hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。 ? ? ? ? ? ? ? ? ? ? ? ?
③ 接下来判断两对象的内存地址是否相同,不同则为true
function compreObj(obj1, obj2) {
var flag = true;
function compre(obj1, obj2) {
if (Object.keys(obj1).length != Object.keys(obj2).length) {
flag = false;
} else {
for (let x in obj1) {
if (obj2.hasOwnProperty(x)) {
if (obj1[x] !== obj2[x]) {
compre(obj1[x], obj2[x]);
}
} else {
flag = false;
}
}
}
if (flag === false) {
return false;
} else {
return true;
}
}
return compre(obj1, obj2)
}
console.log(compreObj(对象1, 对象2));
深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。
1.浅拷贝:
将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,而不复制对象本身,新旧对象还是共享同一块内存
如果属性是一个基本数据类型,拷贝就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,
2.深拷贝:
创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”
深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象
3、赋值:
当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,而不是堆中的数据。也就是两个对象
浅拷贝的实现方式:
? 1、object.assign()
? 2、lodash 里面的 _.clone
? 3、...扩展运算符
? 4、 Array.prototype.concat
? 5、 Array.prototype.clice
?
? ?深拷贝的实现方式
?
? ?1、 JSON.parse(JSON.stringify())
? ?2、递归操作
? ?3、cloneDeep
? ?4、Jquery.extend() ?
?
深拷贝:
? ?function deepClone(arr = {}) {
? ? ? ?if (typeof arr !== 'object' || arr == null) {
? ? ? ? ? ?return arr
? ? ? }
? ? ? ?let result
? ? ? ?if (arr instanceof Array) {
? ? ? ? ? ?result = []
? ? ? } else {
? ? ? ? ? ?result = {}
? ? ? }
? ? ? ?for (const key in arr) {
? ? ? ? ? ?result[key] = arr[key]
? ? ? }
? ? ? ?return result
? }
我个人的理解是,如果一个对象的值等于父级(祖父级,曾祖父级…),则说明是循环引用了
定义一个空数组吗,且对于目标对象进行递归,每次都判断递归项是否为对象,是的话就放到数组里,而且每次判断属性值是否存在,在的话说明环引用了
function cycle(obj, parent) {
? ?//表示调用的父级数组
? ?var parentArr = parent || [obj];
? ?for (var i in obj) {
? ? ? ?if (typeof obj[i] === "object") {
? ? ? ? ? ?//判断是否有循环引用
? ? ? ? ? ?parentArr.forEach((pObj) => {
? ? ? ? ? ? ? ?if (pObj === obj[i]) {
? ? ? ? ? ? ? ? ? ?obj[i] = "[cycle]"
? ? ? ? ? ? ? }
? ? ? ? ? });
? ? ? ? ? ?cycle(obj[i], [...parentArr, obj[i]])
? ? ? }
? }
? ?return obj;
}
?
?
/**@2方法*/
?
function hasLoop(obj){
// 判断对象内部是否有和源相同的属性
function findLoop(target, src){
// 源数组,并将自身传入
const source = src.slice().concat([target])
for(const key in target){
// 如果是对象才需要判断
if(typeof target[key] === 'object'){
// 如果在源数组中找到 || 递归查找内部属性找到相同
if(source.indexOf(target[key]) > -1 || findLoop(target[key], source)){
return true
}
}
}
return false
}
// 如果传入值是对象,则执行判断,否则返回false
return typeof obj === 'object' ? findLoop(obj, []) : false
}
1、sort( ):sort 排序 如果下面参数的正反 控制 升序和降序 ,返回的是从新排序的原数组
2、splice( ):向数组的指定index处插入 返回的是被删除掉的元素的集合,会改变原有数组;截取类 没有参数,返回空数组,原数组不变;一个参数,从该参数表示的索引位开始截取,直至数组结束,返回截取的 数组,原数组改变;两个参数,第一个参数表示开始截取的索引位,第二个参数表示截取的长度,返回截取的 数组,原数组改变;三个或者更多参数,第三个及以后的参数表示要从截取位插入的值。会改变原数据
3、pop( ):从尾部删除一个元素 返回被删除掉的元素,改变原有数组。
4、push( ):向数组的末尾追加 返回值是添加数据后数组的新长度,改变原有数组。
5、unshift( ):向数组的开头添加 返回值是添加数据后数组的新长度,改变原有数组。
6、shift( ):从头部删除一个元素 返回被删除掉的元素,改变原有数组。
7、reverse( ): 原数组倒序 ?它的返回值是倒序之后的原数组
8、concat( ):数组合并。
9、slice( ):数组元素的截取,返回一个新数组,新数组是截取的元素,可以为负值。从数组中截取,如果不传参,会返回原数组。如果只传入一个参数,会从头部开始删除,直到数组结束,原数组不会改变;传入两个参数,第一个是开始截取的索引,第二个是结束截取的索引,不包含结束截取的这一项,原数组不会改变。最多可以接受两个参数。
10、join( ):讲数组进行分割成为字符串 ?这能分割一层在套一层就分隔不了了
11、toString( ):数组转字符串;
12、toLocaleString( ):将数组转换为本地数组。
13、forEach( ):数组进行遍历;
14、map( ):没有return时,对数组的遍历。有return时,返回一个新数组,该新数组的元素是经过过滤(逻辑处理)过的函数。
15、filter( ):对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组。
16、every( ):当数组中每一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
17、some( ):当数组中有一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
18、reduce( ):回调函数中有4个参数。prev(之前计算过的值),next(之前计算过的下一个的值),index,arr。把数组列表计算成一个
19.isArray() 判断是否是数组
20. indexOf ?找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
21. lastIndexOf 它是从最后一个值向前查找的 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
22. Array.of() 填充单个值
23. Array.from() 来源是类数组 ? ?
24.fill填充方法 可以传入3各参数 可以填充数组里的值也就是替换 如果一个值全部都替换掉 , ? ?第一个参数就是值 第二个参数 从起始第几个 第三个参数就是最后一个
find ?查找这一组数 符合条件的第一个数 给他返回出来
findIndex() 查找这一组数 符合条件的第一数的下标 给他返回出来 ? ? 没有返回 -1 ?
keys 属性名 ?values属性值 ?entries属性和属性值
forEach 循环便利 有3个参数 无法使用 break continue , 参数一就是每个元素 参数二就是每个下标 参数三就是每个一项包扩下标和元素
?
### 改变数组本身的api
1. `pop()` ?尾部弹出一个元素
2. `push()` 尾部插入一个元素
3. `shift()` ?头部弹出一个元素
4. `unshift()` ?头部插入一个元素
5. `sort([func])` 对数组进行排序,func有2各参数,其返回值小于0,那么参数1被排列到参数2之前,反之参数2排在参数1之前
6. `reverse()` 原位反转数组中的元素
7. `splice(pos,deleteCount,...item)` ?返回修改后的数组,从pos开始删除deleteCount个元素,并在当前位置插入items
8. `copyWithin(pos[, start[, end]])` 复制从start到end(不包括end)的元素,到pos开始的索引,返回改变后的数组,浅拷贝
9. `arr.fill(value[, start[, end]])` 从start到end默认到数组最后一个位置,不包括end,填充val,返回填充后的数组
其他数组api不改变原数组
?
?
map 映射关系的数组 ?map 主要就是有返回值可以return 数组 ? 判断的会返回boolean
1、map()方法返回一个新数组,新数组中的元素为原始数组中的每个元素调用函数处理后得到的值。
2、map()方法按照原始数组元素顺序依次处理元素。
?
注意:
map()不会对空数组进行检测。
map()不会改变原始数组。
map() 函数的作用是对数组中的每一个元素进行处理,返回新的元素。
filter 满足条件的都能返回 是一个数组
some返回boolean 循环数组 只要有一个成员通过了就会返回 true 反而 false
every返回boolean 循环数组 只有全部成员通过了就会返回 true 反而 false
reduce() 累加器 把上一次计算的值,给下一次计算进行相加
set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用 ?
delete [1] delete 可以删除数组中的一向
**Array.isArray()** 用于确定传递的值是否是一个 [`Array`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array)。
flat ?扁平化 将嵌套的数组 “拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。// 参数写的就是代表要扁平到第几层
?
?
?
//1、every()
var arr = [1,56,80,5];
var main = arr.every(n => n > 0);
console.log(main) ? //输出:true
?
//2、some()
var arr = [1,-56,80,-5];
var main = arr.some(n => n > 0);
console.log(main) ? ?//输出:true
?
//3、reducer()
var arr = [10,20,30,40]
let result = arr.reduce(function(prev,next,index,arr){
return prev + next;
})
console.log(result); ?//输出:100
?
// 4、filter 返回满足要求的数组项组成的新数组
var arr3 = [3,6,7,12,20,64,35]
var result3 = arr3.filter((item,index,arr)=>{
? ?return item > 3
})
console.log(result3) ?//[6,7,12,20,64,35]
?
// 5、map 返回每次函数调用的结果组成的数组
var arr4 = [1,2,3,4,5,6]
var result4 = arr4.map((item,index,arr)=>{
? ?return `${item}`
})
console.log(result4) ?
/*[ '1',
'2',
'3',
'4',
'5',
'6' ]*/
?
?
ES6数组的常用方法:
?
1、Array.from( ):将对象或字符串转成数组,注意得有length。
2、Array.of( ): 将一组值转换为数组。
3、copyWithin(target,start(可选),end(可选)):数组内数据的复制替换
target:从该位置开始替换数据;
start:从该位置开始读取数据,默认为0;
end:到该位置停止数据的读取,默认为数组的长度
4、find( ):用于找出第一个符合条件的数组成员。
5、findIndex( ):返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
6、fill(value,start,end):使用给定值,填充一个数组。
value:填充的值;
start:开始填充的位置;
end:填充结束的位置。
7、keys( ):对键名的遍历。
8、values( ):对键值的遍历。
9、entries( ):对键值对的遍历。
10、includes( ):数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。
11、flat( ):用于数组扁平,数组去除未定义。
12、flatMap( ):对原数组的每个成员执行一个函数。
13、Map( ):是一组键值对的结构,具有极快的查找速度。
14、Set( ):Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
?
?
//1、Array.from() -- ? Array.of()
var ?arrayLink = {
"0":"a",
"1":"b",
"2":"c",
length:3
}
var arr = Array.from(arrayLink)
console.log(arr) ? // 输出: [a,b,c]
console.log(Array.from("abcdefg")) ?//输出:["a", "b", "c", "d", "e", "f", "g"]
console.log(Array.of(1,2,3,4,5)) ?//输出: [1, 2, 3, 4, 5]
?
//2、copyWithin()
var arr = [1,2,3,4,5];
var main = arr.copyWithin(0,3);
console.log(main); ? //输出:[4,5,3,4,5]
?
//3、find()
var arr = [1,-5,2,9,-6];
var main = arr.find(n => ?n < 0);
console.log(main); ? //输出:-5
?
//4、fill()
var arr = ["a","b","c","d"];
console.log(arr.fill(7,1,2));//输出:["a",7,"c","d"] ?
?
//5、keys() values() entries()
var arr = ["a","b","c","d"];
for(let index of arr.keys()){
console.log(index);
}
for(let elem of arr.values()){
console.log(elem);
}
for(let [index,elem] of arr.entries()){
console.log(index,elem);
} ?
?
//6、includes()
let arr = [12,34,223,45,67]
console.log(arr.includes(45)) ? //输出:true
[1, 2, NaN].includes(NaN) ? ? // true
[1, 2, NaN].indexOf(NaN) ? ? ?// -1
?
//7、Map
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
//初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
//由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
var m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88
?
//8、Set
//要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
//重复元素在Set中自动被过滤:
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"} 注意:数字3和字符串'3'是不同的元素
//通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
//通过delete(key)方法可以删除元素:
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
1. 使用ES6中的 ?new Set是最简单的去重方法
?
2、定义一个空数组,通过forEach循环,indexOf判断值是否是-1,如果是push到新的数组中
? ? ? ?var arr = [1, 2, 3, 8, 8, 5, 2]
? ? ? ?var newArr = []
? ? ? ? for (let i = 0; i < arr.length; i++) {
? ? ? ? ? ?if (newArr.indexOf(arr[i]) == -1) {
? ? ? ? ? ?// 定义一个新数组 循环便利要去重的数组使用indexOf api
? ? ? ? ? ?// 新数组检测要去重的数组,== -1 说明没找到 找到了就放到新数组里,如果找到了就不执行,
? ? ? ? ? ? ? ? newArr.push(arr[i])
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? console.log(newArr);
?
? ? ? ? arr.forEach(item => {
? ? ? ? ? ? if (!newArr.includes(item)) {
? ? ? ? ? ? ? ? newArr.push(item)
? ? ? ? ? ? }
? ? ? ? })
? ? ? ?console.log(newArr);
?
? ? ? ?console.log([...new Set(arr)]);
reduce h和 concat 实现 遍历数组每一项,若值为数组则递归遍历,否则concat。
function flatten(arr) { ?
? ?return arr.reduce((result, item)=> {
? ? ? ?return result.concat(Array.isArray(item) ? flatten(item) : item);
? }, []);
}
console.log(flatten([12,14,[125,58,[1,2]]))
function deepClone(arr = {}) {
? 判断是不是object instanceof 是 array 返回数组 不是返回对象 ?fro in便利key 在用递归调用自己 传入key
? ? ? ?if (typeof arr !== 'object' || arr == null) {
? ? ? ? ? ?return arr
? ? ? }
? ? ? ?let result
? ? ? ?if (arr instanceof Array) {
? ? ? ? ? ?result = []
? ? ? } else {
? ? ? ? ? ?result = {}
? ? ? }
?
? ? ? ?for (const key in arr) {
? ? ? ? ? ?result[key] = deepClone(arr[key])
? ? ? }
?
? ? ? ?return result
? }
?
? ?let arr2 = deepClone(arr)
sort排序最简单的
[1,8,34,99].sort((a,b)=>{
return a - b
})
?
?
?
? ?优化冒泡 解构赋值
? ?function insert(arr) {
? ? ? ?// 外层循环控制的是比较的轮数,你要循环几轮
? ? ? ?for (let i = 1; i < arr.length; i++) {
? ? ? ? ? ?// 内层循环控制的每一轮交换的次数
? ? ? ? ? ?for (let j = 1; j < arr.length - i; j++) {
? ? ? ? ? ? ? ?// 若果当前一项 和 第二项 作比较 大于了第二项的换,做一下变量交换
? ? ? ? ? ? ? ?if (arr[j] > arr[j + 1]) {
? ? ? ? ? ? ? ? ? ?// 大于后一项 做一个交换变量
? ? ? ? ? ? ? ? ? [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }
? ? ? ?return arr
? }
? ?console.log(insert([2, 8, 5, 92, 52]))
? ?
? ?
?
? ?
?// 快速排序主要是利用递归来实现
? ?function quick(ary) {
? ? ? ?// 4、结束循环
? ? ? ?if (ary.length <= 1) {
? ? ? ? ? ?return ary
? ? ? }
? ? ? ?// 1、找到数组中的中间项然后把他取出来,用中间项进行对比,小的放左边,大的放右边
? ? ? ?// Math.floor(ary / 2) 数组的长度除以二 并且向下取整 得出中间项
? ? ? ?let f = Math.floor(ary.length / 2)
? ? ? ?// 得出中间的拿个数字, splice 返回删除后数组 所以要加上一个 [0]
? ? ? ?let cent = ary.splice(f, 1)[0]
? ? ? ?// 2、准备两个数组,循环剩下数组每一项,比较项小的放到左边,大的放到右边
? ? ? ?let aryLeft = []
? ? ? ?let aryRIght = []
? ? ? ?for (let i = 0; i < ary.length; i++) {
? ? ? ? ? ?let item = ary[i]
? ? ? ? ? ?item < cent ? aryLeft.push(item) : aryRIght.push(item)
? ? ? }
?
? ? ? ?// 3、递归让左右两边的数组持续的处理,一直到两边数组都排好序为止(最后让左右中间进行拼接)
? ? ? ?// f 一定要在中间
? ? ? ?return quick(aryLeft).concat(cent, quick(aryRIght))
? }
? ?
?
? ? ?function insert(arr) {
? ? ? ?// 1、准备一个数组这个数组就是新抓的牌,开始先抓一张牌进来
? ? ? ?let handle = []
? ? ? ?handle.push(arr[0])
? ? ? ?// 2、第二次开始按顺序抓牌一直把牌抓光了
? ? ? ?for (let i = 1; i < arr.length; i++) {
? ? ? ? ? ?// a 是新抓的牌
? ? ? ? ? ?let a = arr[i]
? ? ? ? ? ?// 和手里的牌进行比较(从后向前比)
? ? ? ? ? ?// handle.length - 1 因为下标是从 0 开始数的
? ? ? ? ? ?for (let j = handle.length - 1; j >= 0; j--) {
? ? ? ? ? ? ? ?// 每一次要比较手里的牌
? ? ? ? ? ? ? ?let b = handle[j]
? ? ? ? ? ? ? ?// 如果当前新牌a比b 大了,可就要放到 b 的后面
? ? ? ? ? ? ? ?// 如果 a > b 就是表示从小到大
? ? ? ? ? ? ? ?// a < b 送大到小
? ? ? ? ? ? ? ?if (a > b) {
? ? ? ? ? ? ? ? ? ?// 如果想要放到 j 的后面就要加上一个 1,
?
? ? ? ? ? ? ? ? ? ?/**
? ? ? ? ? ? ? ? ? ? * 比如说我要插队一般要插就是插到 a 的前面
? ? ? ? ? ? ? ? ? ? * 但是我想要插到 a 的后面怎么办
? ? ? ? ? ? ? ? ? ? * 那这个时候就要插到 a 下一个人的人前面就可以了
? ? ? ? ? ? ? ? ? ? *
? ? ? ? ? ? ? ? ? */
? ? ? ? ? ? ? ? ? ?handle.splice(j + 1, 0, a)
? ? ? ? ? ? ? ? ? ?break
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?// 还有一种情况就是如果你比较到第一项的还比较吗,不比较了
? ? ? ? ? ? ? ?// 我们吧新牌放到最前面即可
? ? ? ? ? ? ? ?if (j == 0) {
? ? ? ? ? ? ? ? ? ?handle.unshift(a)
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }
? ? ? ?return handle
? }
?
?
? ?console.log(insert([2, 8, 5, 92, 52, 4]))
1、chartAt( ):返回在指定位置的字符;
2、concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并
3、indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1
4、lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,没有则-1,fromIndex的值相对于从尾部开始的索引
5、split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
6、substr( ):从起始索引号提取字符串中指定数目的字符;
7、substring( ):提取字符串中两个指定的索引号之间的字符;
8、toLowerCase( ):字符串转小写;
9、toUpperCase( ):字符串转大写;
10、valueOf( ):返回某个字符串对象的原始值;
11、trim( ):删除字符串两边的空格;
12、trimeState 取出开始的空格
13、trimeEnd ?去除末尾空格
14、includes(searchString[, position])返回boolean,判断一个字符串是否包含在另一个字符串中,从postition索引开始搜寻,默认0
15、slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;
16、search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配
17、toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式
18、trim()返回去掉两端空白后的新字符串 还有trimend trimstart
19、replace() 把指定的字符串替换成为别的字符
大家都知道,
字符串
属于基础类型
,所以大家会觉得字符串
是存在栈内存
中的,但是大家要知道,V8默认栈内存是984Kib
,那如果一个超长字符串 > 984Kib
能装的进栈内存
吗?这也就是一个比较经典的问题——大象装箱问题,试问:一头大象能装进一个小箱子里吗?
字符串的内容存于堆内存中,指针存于栈内存中,且相同的字符串指向同一个堆内存地址
新增或者修改字符串后,如果是一个之前不存在的字符串,则新开辟内存空间,如果是已有的,则直接使用已有的内存空间
当我们新建一个字符串时,V8会从内存中查找一下是否已经有存在的一样的字符串,找到的话直接复用。如果找不到的话,则开辟一块新的内存空间来存这个字符串,并把地址赋给变量。
大家有没有想过,为什么字符串不能通过下标索引来进行修改呢?因为字符串的修改本质上只能是通过整个的修改,而不能局部修改。
var str = "10000000000";
?
第一种:把数字转换成字符串后,打散为数组,再从末尾开始,逐个把数组中的元素插入到新数组(result)的开头。 每插入一个元素,counter就计一次数(加1),
当counter为3的倍数时,就插入一个逗号,但是要注意开头(i为0时)不需要逗号。最后通过调用新数组的join方法得出结果。
?
?
String.prototype.toThousands = function(){
var num = this;
var result = [ ], counter = 0;
num = (num || 0).toString().split('');
for (var i = num.length - 1; i >= 0; i--) {
counter++;
result.unshift(num[i]);
if (!(counter % 3) && i != 0) { result.unshift(','); }
}
return result.join('');
}
console.log(str.toThousands());
?
?
第二种:通过正则表达式循环匹配末尾的三个数字,每匹配一次,就把逗号和匹配到的内容插入到结果字符串的开头, ?
然后把匹配目标(num)赋值为还没匹配的内(RegExp.leftContext)。如果数字的位数是3的倍数时,最后一次匹配到的内容肯定是三个数字,
但是最前面的三个数字前不需要加逗号;如果数字的位数不是3的倍数,那num变量最后肯定会剩下1到2个数字,循环过后,要把剩余的数字插入到结果字符串的开头。
?
function toThousands(num) {
var num = (num || 0).toString(), re = /d{3}$/, result = '';
while ( re.test(num) ) {
result = RegExp.lastMatch + result;
if (num !== RegExp.lastMatch) {
result = ',' + result;
num = RegExp.leftContext;
} else {
num = '';
break;
}
}
if (num) { result = num + result; }
return result;
}
console.log(toThousands(str)); ?
?
第三种:第二种的改良版
?
function toThousands(num) {
var num = (num || 0).toString(), result = '';
while (num.length > 3) {
result = ',' + num.slice(-3) + result;
num = num.slice(0, num.length - 3);
}
if (num) { result = num + result; }
return result;
}
console.log(toThousands(str)); ?
?
第四种:懒人版
?
function toThousands(num) {
return (num || 0).toString().replace(/(d)(?=(?:d{3})+$)/g, '$1,');
}
console.log(toThousands(str));
声明函数的几种方式
函数声明
function 函数名(参数1,参数2,…){ ? //要执行的语句 }
函数表达式
var func2=function(b){}//函数表达式
var func3=function func4©{}//命名式函数表达式
var func5=(function(n1,n2){})();//立即执行的函数表达式
return function(){ };//作为返回值的函数表达式
Function构造器
var 变量名 = new Function(“参数1”,“参数2”,…,“参数n”,“函数体”); ?
立即执行函数
var func5=(function(n1,n2){})();//立即执行的函数表达式 ()()
函数声明与函数表达式的区别
函数声明会将那个函数提升到最前面(即使你写代码的时候在代码块最后才写这个函数),成为全局函数。
函数声明要指定函数名,而函数表达式不用,可以用作匿名函数。
1.直接调用 函数名加上括号 ()
2.函数表达式 变量名()
函数的
length
属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真。
function fun1(a) { }
function fun2(a, b) { }
function fun3(a, b, c) { }
function fun4(a, b, c, d) { }
function fun5(...args) { }
function fun6(a = 1, b, c, d) { }
?
console.log(fun1.length) // 1
console.log(fun2.length) // 2
console.log(fun3.length) // 3
console.log(fun4.length) // 4
console.log(fun5.length) // 0
console.log(fun6.length) // 0
立即执行函数:( function( ){ })( ) 返回值可以为基本数据类型,也能返会任何类型的值。
写法原因:因为在 javascript 里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(), 然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。
作用:立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量。
使用场景: ①代码在页面加载完成之后,不得不执行一些设置工作,比如时间处理器,创建对象等等。 ②所有的这些工作只需要执行一次,比如只需要显示一个时间。 ③需要一些临时的变量,但是初始化过程结束之后,就再也不会被用到,我们可以用立即执行函数——去将我们所有的代码包裹在它的局部作用域中, 不会让任何变量泄露成全局变量。
arguments 当我们不知道有多少个参数传进来的时候就用 arguments 来接收,是一个类似于数组的对象,他有length属性,可以arguments[ i ]来访问对象中的元素, 但是它不能用数组的一些方法。 例如push、pop、slice等。arguments虽然不是一个数组,但是它可以转成一个真正的数组。取之可以用 展开运算符来 数组和类数组类数组: ①拥有length属性,其它属性(索引)为非负整数;箭头函数里没有arguments ②不具有数组所具有的方法; ③类数组是一个普通对象,而真实的数组是Array类型。
常见的类数组:arguments,document.querySelectorAll得到的列表,jQuery对象($(“div”));
在全局的环境下this是指向window 的
普通函数调用直接调用中的this 会指向 window, 严格模式下this会指向 undefined,自执行函数 this 指向 window,定时器中的 this 指向 window
在对象里调用的this,指向调用函数的那个对象,
在构造函数以及类中的this,构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象,所以构造函数中的 this 指向 当前实例化的对象
方法中的this谁调用就指向谁。
箭头函数没有自己的 this,箭头函数的this在定义的时候,会继承自外层第一个普通函数的this
函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数,避免共享状态、可变作用和副作用来构建软件的过程。 目的:使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。
1、闭包的概念就是:只有权利访问另一个函数作用域中的变量,一般就是函数包裹着函数。
3、闭包可以重用一个变量,且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中,不会被垃圾回收机制处理
4、闭包的缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
5、为什么要用闭包:使用场景 : 防抖、节流、函数套函数避免全局污染
闭包原理
函数执行分成两个阶段(预编译阶段和执行阶段)。
?
1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,
?如果已存在“闭包”,则只需要增加对应属性值即可。
2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,
?所以内部函数可以继续使用“外部函数”中的变量
?
利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,
但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。
都是来改变this指向和函数的调,实际上call与apply的功能是相同的,只是两者的传参方式不一样,
call法跟的是个参数列表,
apply跟个 数组作为参数,call法和apply使后就直接调
bind 传参后不会立即执行,而是返回一个改变了this指向的函数,这个函数可以继续传参,且执行,需要类似于bind()()两个括号才能调。
call 的性能要比apply好一点(尤其是当函数传递参数超过3个的时候)后期开发 call 多多一点
call 用扩展运算符就可以吧 apply 来代替了
bind 返回的函数可以作为构造函数吗?
不可以,会报错的哦, ERROR > Uncaught TypeError: s is not a constructor
?// 性能测试
? console.time('a')
? ?for (let i = 0; i < 1000; i++) {
? }
? ?console.timeEnd('a')
封装:
call函数
// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.mycall = function (context) {
?if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
?context = context || window
?context.fn = this
?let arg = [...arguments].slice(1)
?let result = context.fn(...arg)
?delete context.fn
?return result
}
?
apply函数
?
// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.myapply = function (context) {
?if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
?context = context || window
?context.fn = this
?let result
?if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
?delete context.fn
?return result
}
?
bind函数
?
// 思路:类似call,但返回的是函数
Function.prototype.mybind = function (context) {
?if (typeof this !== 'function') {
throw new TypeError('Error')
}
?let _this = this
?let arg = [...arguments].slice(1)
?return function F() {
// 处理函数使用new的情况
if (this instanceof F) {
?return new _this(...arg, ...arguments)
} else {
?return _this.apply(context, arg.concat(...arguments))
}
}
}
概念:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 容易理解的概念:Currying概念其实很简单,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数(主要是利用闭包实现的)。
特点: ①接收单一参数,将更多的参数通过回调函数来搞定; ②返回一个新函数,用于处理所有的想要传入的参数; ③需要利用call/apply与arguments对象收集参数; ④返回的这个函数正是用来处理收集起来的参数。
作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。
用途:我认为函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。
柯里化函数:把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。
我的理解就是将一个接受多个参数的函数,转化为接收一个参数,并且不改变输出结果的一种办法。我觉得这就是js的柯里化函数
// 简单的相加函数
var add = function (x,y) {
? ?return x + y
}
// 调用:
add(1,2)
?
// 柯里化以后
var add = function (x) { //柯里化函数(闭包)
? ?return function (y) {
? ? ? ?return x + y
? }
}
add(1)(2)
这样做有什么好处,我得理解是在需要的情况下生成一个中间工具,简化代码,并且清晰代码。
高阶函数只是,将函数作为参数 , 函数的返回值返回值是函数
function higherOrderFunction(param,callback){
? ?return callback(param);
}
new实际上是在堆内存中开辟一个空间。
①创建一个空对象,构造函数中的this指向这个空对象;
②这个新对象被执行[ [ 原型 ] ]连接;
③执行构造函数方法,属性和方法被添加到this引用的对象中;
④如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
?
function _new(){
let target = {}; ? //创建的新对象
let [constructor,...args] = [...arguments];
? //执行[[原型]]连接,target是constructor的实例
target.__proto__ = constructor.prototype;
//执行构造函数,将属性或方法添加到创建的空对象上
let result = constructor.prototype;
if(result && (typeof (result) == "object" || typeof (result) == "function")){
? //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
return result;
}
? //如果构造函数返回的不是一个对象,返回创建的对象
return target;
}
?
?
自己理解的new: ? ? ? ?
new实际上是在堆内存中开辟一个新的空间。首先创建一个空对象obj,然后呢,
把这个空对象的原型(__proto__)和构造函数的原型对象(constructor.prototype)连接(说白了就是等于);
然后执行函数中的代码,就是为这个新对象添加属性和方法。最后进行判断其返回值,如果构造函数返回的是一个对象,
那就返回这个对象,如果不是,那就返回我们创建的对象。
需要点击每个a,来。弹出他们的内容
// 封装通用的事件绑定函数
function bindEvent(elem, type, fn) {
? ?elem.addEventListener(type, fn)
}
//获取父元素
const fu = document.getElementById('div3')
bindEvent(fu, 'click', function (event) {
? ?// console.log(event.target) // 获取触发的元素
? ?let target=event.target
? ?event.preventDefault() // 阻止默认行为
? ?//过滤符合条件的子元素,主要是过滤掉 加载更多
? ?if(target.nodeName.toLowerCase()==="A"){
? ? ? ?alert(target.innerHTML;
? }
})
垃圾回收
浏览器的js具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不在继续使用的变量,然后释放内存。但是这个过程不是实时的,因为GC开销比较大并且时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
内存泄露
如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏
内存泄露其实就是我们的程序中已经动态分配的堆内存,由于某些原因没有得到释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。
比如说:
1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。
2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收
3、Times计时器泄露
1、作用域
作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域
全局作用域就是Js中最外层的作用域
函数作用域是js通过函数创建的一个独立作用域,函数可以嵌套,所以作用域也可以嵌套
Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
2、自由变量
当前作用域外的变量都是自由变量,一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域,一层一层依次查找,直至找到为止,如果全局作用域都没有找到这个变量就会报错。这个自由变量查找的过程就是作用域链。
3、变量提升
每个var声明的变量,function声明的函数存在变量提升。let const不存在变量提升
在js中声明之前未定义,会在js的最上方会形成一个预解析池,用来存储声明了但没有先定义的变量名
4、作用域链:
作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和 函数 , 简单来说:内部函数访问外部函数的变量这种链式查找的机制被称为作用域链
1. js单线程
JavaScript语言的一大特点就是单线程,即同一时间只能做一件事情。
2. js事件循环
js代码执行过程中会有很多任务,这些任务总的分成两类:
同步任务
异步任务
需要注意的是除了同步任务和异步任务,任务还可以更加细分为macrotask(宏任务)和microtask(微任务),js引擎会优先执行微任务
微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
?
宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲
染等。
首先js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务
当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。
当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
最后可以用下面一道题检测一下收获:
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(resolve, reject) {
console.log(2);
resolve()
}).then(function() {
console.log(3)
});
process.nextTick(function () {
console.log(4)
})
console.log(5)
第一轮:主线程开始执行,遇到setTimeout,将setTimeout的回调函数丢到宏任务队列中,在往下执行new Promise立即执行,输出2,then的回调函数丢到微任务队列中,再继续执行,遇到process.nextTick,同样将回调函数扔到为任务队列,再继续执行,输出5,当所有同步任务执行完成后看有没有可以执行的微任务,发现有then函数和nextTick两个微任务,先执行哪个呢?process.nextTick指定的异步任务总是发生在所有异步任务之前,因此先执行process.nextTick输出4然后执行then函数输出3,第一轮执行结束。
第二轮:从宏任务队列开始,发现setTimeout回调,输出1执行完毕,因此结果是25431
JavaScript会阻塞DOM的解析,因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。
1.把JS放在页面的最底部 2.script标签的defer属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。 3.是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行, 该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。 4.动态创建script标签,监听dom加载完毕再引入js文件
js中的一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue,然后在执行微任务。
宏任务:setTimeout,setInterval,Ajax,DOM事件
微任务:Promise async/await
想明白这个机制 就要理解js单线程。因为JS是单线程语言,只能同时做一件事儿。js任务需要排队顺序执行,如果一个任务时间过长,后边的任务也会等着。假如,我们在请求一个网址时,图片加载很慢,网页总不能一直卡不出来,
这个时候就可以用异步来解决了,异步的特点不会阻塞代码的执行 ,解决了单线程等待的这个问题
在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务
异步和单线程是相辅相成的,js是一门单线程语言,所以需要异步来辅助。
宏任务macrotask: 可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到
? 执行栈中执行)。
? 常见的宏任务:script, setTimeout, setInterval, setImmediate, I/O, UI rendering。
?
微任务microtask(异步): 可以理解是在当前task执行结束后立即执行的任务。
?
常见的微任务:process.nextTick(Nodejs),Promise.then(), MutationObserver。
?
线程,进程?
线程是最小的执行单元,进程是最小的资源管理单元一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程
内存泄露
如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏
比如说:
1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。
2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收
3、Times计时器泄露
JS代码在执行前,浏览器会对js代码进行扫描,默认的把所有带var和function声明的变量进行提前的声明或者定义,遵循先解析后使用的原则。 变量提升的表现是,在变量或函数声明之前访问变量或调用函数而不会报错。
原因 JavaScript引擎在代码执行前有一个解析的过程(预编译),创建执行上线文,初始化一些代码执行时需要用到的对象。 当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性, 它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。
首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。
1.在解析阶段 JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来, 变量先赋值为undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似, 不过函数执行上下文会多出this、arguments和函数的参数。
全局上下文:变量定义,函数声明 函数上下文:变量定义,函数声明,this,arguments
2.在执行阶段,就是按照代码的顺序依次执行。
那为什么会进行变量提升呢?主要有以下两个原因:
1、提高性能
2、容错性更好
?
(1)提高性能 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,
? ?那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、
不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),
并且因为代码压缩的原因,代码执行也更快了。
(2)容错性更好 变量提升可以在一定程度上提高JS的容错性,看下面的代码:
?
a = 1
var a
console.log(a) //1
如果没有变量提升,这段代码就会报错导致的问题
var tmp = new Date();
?
function fn(){
console.log(tmp);
if(false){
var tmp = 'hello nanjiu';
}
}
fn(); ?// undefined
在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部,
相当于覆盖了外层的tmp,所以打印结果为undefined。
var tmp = 'hello nan jiu';
for (var i = 0; i < tmp.length; i++) {
console.log(tmp[i]);
}
console.log(i); // 13
?
由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来13。
总结
解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行
函数是一等公民,当函数声明与变量声明冲突时,变量提升时函数优先级更高,会忽略同名的变量声明
定义:将组件或页面通过服务器生成html字符串,在发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。
?
解释:服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。
客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。
使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。
有了服务端渲染,当请求用户页面时,返回的body里已经有了首屏的html结构,之后结合css显示出来。
优点:
①首屏渲染快(关键性问题):相比于加载单页应用,我只需要加载当前页面的内容,而不需要像 React 或者 Vue 一样加载全部的 js 文件;
②SEO(搜索引擎)优化:不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本(Google除外,据说Googlebot可以运行javaScript)。
使用了React或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少。另外,
浏览器爬虫不会等待我们的数据完成之后再去抓取我们的页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,
网络爬中就可以抓取到完整页面的信息。
③可以生成缓存片段、节能;
?
缺点:用户体验较差,不容易维护、通常前端改了部分html或者css,后端也需要改;
?
使用场景:vue全家桶或者react全家桶,都是推荐通过服务端渲染来实现路由的。
浏览器内核是多线程,JavaScript是单线程;
?
JS单线程详解:因为 js 是面向客户端的一门语言,主要是用户交互,操作dom,渲染数据。试想一下。
如果是多线程,我们在一个线程删除了一个dom节点,另外一个线程添加了一个dom节点,以那个线程为主呢,
就会出现混乱的情况。当然你可以说我们在操作一个dom之后加上锁,只允许一个线程操作,这样其实增加了程序的复杂度,
并不是一个好办法。
?
单线程产生的问题:必须要等待前一个程序执行完毕才执行下一个,所以将程序分为了两类:同步任务和异步任务。
异步任务又可以分为宏任务和微任务。
?
栈:先进后出的数据结构,存储基本数据类型的变量。
?
堆:主要负责引用数据类型的存储。
?
任务队列:为什么会有任务队列呢,还是因为 javascript 单线程的原因,单线程,就意味着一个任务一个任务的执行,
执行完当前任务,执行下一个任务,这样也会遇到一个问题,就比如说,要向服务端通信,加载大量数据,如果是同步执行,
js 主线程就得等着这个通信完成,然后才能渲染数据,为了高效率的利用cpu, 就有了同步任务和异步任务之分。
?
同步任务: 指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
?
异步任务: 指的是不进入主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
同步:上一件事情没有完成,继续处理上一件事情,只有上一件事情完成了,才会做下一件事情
异步: 规划要做一件事情,如果是异步事情,不是当前立马去执行这件事情,需要等一定的时间,这样的话,我们不会等着他执行,而是继续执行下面的操作
对于写程序,同步往往会阻塞,没有数据过来,我就等着,异步则不会阻塞,没数据来我干别的事,有数据来去处理这些数据。
同步案例:for循环语句,alert(),console.log()等 js大部分都是同步编程
异步案例:所有定时器,ajax异步请求,所有的事件绑定都是异步;
举例子
同步,就是实时处理(如打电话),比如服务器一接收客户端请求,马上响应,这样客户端可以在最短的时间内得到结果,但是如果多个客户端,或者一个客户端发出的请求很频繁,服务器无法同步处理,就会造成涌塞。
同步如打电话,通信双方不能断(我们是同时进行,同步),你一句我一句,这样的好处是,对方想表达的信息我马上能收到,但是,我在打着电话,我无法做别的事情。
异步,就是分时处理(如收发短信),服务器接收到客户端请求后并不是立即处理,而是等待服务器比较空闲的时候加以处理,可以避免涌塞。
浏览器对象模型(BOM :Browser Object Model)是JavaScript的组成之一,它提供了独立于内容与浏览器窗口进行交互的对象,使用浏览器对象模型可以实现与HTML的交互。它的作用是将相关的元素组织包装起来,提供给程序设计人员使用,从而降低开发人员的劳动量,提高设计Web页面的能力。
window : alert() , prompt() , confirm() , setInterval() , clearInterval() , setTimeout() , clearTimeout() ;
history : go(参数) , back() , foward() ;
location : herf属性.
1、window.location.href = ‘你所要跳转到的页面’; 2、window.open('你所要跳转到的页面’); 3、window.history.back(-1):返回上一页 4、window.history.go(-1/1):返回上一页或下一页五、 5、history.go(“baidu.com”);
Math.PI 圆周率
Math.floor() 向下取整
Math.ceil() 向上取整
Math.round() 四舍五入版 就近取整
Math.abs() 绝对值
Math.max()/Math.min() 求最大和最小值
Math.random() 获取范围在[0,1)内的随机值
setTimeout()和setInterval()经常被用来处理延时和定时任务。
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式
setInterval()则可以在每隔指定的毫秒数循环调用函数或表达式,直到clearInterval把它清除。
机制:
因为js是单线程的。浏览器遇到setTimeout 和 setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的
待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码
window.onload:当一个资源及其依赖资源已完成加载时,将触发onload事件。 document.onDOMContentLoaded:当初始的HTML文档被完全加载和解析完成之后, DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架的完成加载。 区别: ①onload事件是DOM事件,onDOMContentLoaded是HTML5事件。 ②onload事件会被样式表、图像和子框架阻塞,而onDOMContentLoaded不会。 ③当加载的脚本内容并不包含立即执行DOM操作时,使用onDOMContentLoaded事件是个更好的选择,会比onload事件执行时间更早。
cookie:一个大小不超过4K的小型文本数据,一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器cookie失效,若设置了 时间,cookie就会存放在硬盘里,过期才失效,每次http请求,header都携带cookie
localStorage:5M或者更大,永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除或者js代码清除,因此用作持久数据,不参与和服务器的通信
sessionStorage关闭页面或浏览器后被清除。存 放数据大小为一般为 5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。
location为全局对象window的一个属性,且
window.location===document.location
,其中的属性都是可读写的,但是只有修改href和hash才有意义,href会重新定位到一个URL,hash会跳到当前页面中的anchor名字的标记(如果有),而且页面不会被重新加载
window.navigator`对象包含有关浏览器的信息,可以用它来查询一些关于运行当前脚本的应用程序的相关信息
navigator.appCodeName 只读,任何浏览器中,总是返回 ‘Gecko’。该属性仅仅是为了保持兼容性。
navigator.appName
只读,返回浏览器的官方名称。不要指望该属性返回正确的值。
navigator.appVersion
只读,返回一个字符串,表示浏览器的版本。不要指望该属性返回正确的值。
navigator.platform
只读,返回一个字符串,表示浏览器的所在系统平台。
navigator.product
只读,返回当前浏览器的产品名称(如,“Gecko”)。
navigator.userAgent
只读,返回当前浏览器的用户代理字符串(user agent string)
DOM是 document 用来表示文档中对象的标准模型,他是由节点和对象组成的结构集合。在浏览器解析HTML标签时,会构建一个DOM树结构。
操作说明书
拿到指定节点
var id = document.getElementById("id"); ?//返回带有指定id的元素
var name = document.getElementByTagName("li"); //返回带有指定标签的元素
var class = document.getElementByClassName("class"); //返回带有包含执行类名的所有元素节点列表。`
创建DOM节点
var node = document.createElement("div");
var attr = document.createAttribute("class");
var text = document.createTextNode("菜呀菜");`
插入DOM节点
node.appendChild(text) //插入新的子节点
node.insertBefore(pre,child) //在node元素内child前加入新元素`
删除DOM节点
node.removeChild(text) //从父元素删除子元素节点
修改DOM节点
node.setAttribute("class","name") //修改设置属性节点
node.replaceChild(pre,child) ?//父节点内新子节点替换旧子节点`
常用DOM属性
node.innerHtml ?//获取/替换元素内容
node.parentNode ?//元素节点的父节点
node.parentElement ?//元素节点的父元素节点(一般与Node节点相同)
node.firstChild ?//属性的第一个节点
node.lastChild ? //属性的最后一个节点
node.nextSibling //节点元素后的兄弟元素(包括回车,空格,换行)
node.nextElementSibling //节点元素后的兄弟元素节点
node.previousSibling //获取元素的上一个兄弟节点(元素,文本,注释)
node.previousElementSibling //获取元素的上一个兄弟节点(只包含元素节点)
node.childNodes ?//元素节点的子节点(空格,换行默认为文本节点)
node.children ? ?//返回当前元素的所有元素节点
node.nodeValue ? //获取节点值
node.nodeName ? ?//获取节点名字
node.attributes ?//元素节点的属性节点
node.getAttribute("name") ?//元素节点的某个属性节点
node.style.width = "200px" ?//设置css样式`
offset系列 经常用于获得元素位置 offsetLeft offsetTop
client经常用于获取元素大小 clientWidth clientHeight
scroll 经常用于获取滚动距离 scrollTop scrollLeft
声明式编程:专注于”做什么”而不是”如何去做”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。 如:css, 正则表达式,sql 语句,html, xml…
命令式编程(过程式编程) : 专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。
如: for()
函数式编程:把运算过程尽量写成一系列嵌套的函数调用。
如 : forEach()
优点:
①iframe能够原封不动的把嵌入的网页展现出来;
②如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
③网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
④如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。
?
缺点:
①会产生很多页面不易管理;
②iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
③代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化。
④很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
⑤iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。
本题延申:
frame框架:
优点:
①重载页面时不需要重载整个页面,只需要重载页面中的一个框架页(减少了数据的传输,加快了网页下载速度);
②技术易于掌握,使用方便,使用者众多,可主要应用于不需搜索引擎来搜索的页面;
③方便制作导航栏 ;
缺点:
①搜索引擎程序不能解读这种页面;
②不能打印全框架;
③浏览器的后退按钮无效;
④手机等终端设备无法显示全部框架内容;
?
iframe和frame区别:
①frame不能脱离frameSet单独使用,iframe可以;
②frame不能放在body中,否则不能正常显示,frame不能和body同时使用,iframe可以;
③嵌套在frameSet中的iframe必需放在body中,不嵌套在frameSet中的iframe可以随意使用;
④frame的高度只能通过frameSet控制;iframe可以自己控制,不能通过frameSet控制;
⑤iframe 可以放到表格里面。frame 则不行。
?
" == "操作符在左右数据不一致的时候,会先进行隐式转换,该值意味着不是基本数据类型,
因为如果a是null或者undefined、bool类型都不可能返回true;可以推测a是复杂数据类型。
?
方法一:数组的 toString 接口默认调用数组的 join 方法,重新 join 方法
?
let a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3) //true
?
?
方法二:利用数据劫持(Proxy/Object.definedProperty)
?
let i = 1;
let a = new Proxy({},{
i:1,
get:function(){
return () => this.i++
}
});
console.log(a == 1 && a == 2 && a == 3);
在开发过程中遇到类似这样的问题:
let n1 = 0.1, n2 = 0.2
console.log(n1 + n2) ?// 0.30000000000000004
这里得到的不是想要的结果,要想等于0.3,就要把它进行转化:
(n1 + n2).toFixed(2) // 注意,toFixed为四舍五入
复制代码
toFixed(num) 方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢?
计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和
新增了块级作用域(let,const)
提供了定义类的语法糖(class)
新增了一种基本数据类型(Symbol)
新增了变量的解构赋值
函数参数允许设置默认值,引入了 rest 参数,新增了箭头函数
数组新增了一些 API,如 isArray / from / of 方法;数组实例新增了entries(),keys() 和 values() 等方法
对象和数组新增了扩展运算符
ES6 新增了模块化(import/export)
ES6 新增了 Set 和 Map 数据结构
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
ES6 新增了生成器(Generator)和遍历器(Iterator)
1、import是ES6中的语法标准也是用来加载模块文件的,import函数可以读取并执行一个JavaScript文件,然后返回该模块的export命令指定输出的代码。export与export default均可用于导出常量、函数、文件、模块,export可以有多个,export default只能有一个。
2、require 定义模块:module变量代表当前模块,它的exports属性是对外的接口。通过exports可以将模块从模块中导出,其他文件加载该模块实际上就是读取module.exports变量,他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名。
require与import的区别
1,require是CommonJS规范的模块化语法,import是ECMAScript 6规范的模块化语法;
2,require是运行时加载,import是编译时加载;
3,require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;
4,require通过module.exports导出的值就不能再变化,import通过export导出的值可以改变;
5;require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码;
6,require运行时才引入模块的属性所以性能相对较低,import编译时引入模块的属性所所以性能稍高。
js中我们在调函数的时候经常会遇到this作域的问题,这个时候ES6给我们提箭头函数。
1、 箭头函数是匿名函数不能作为构造函数,不能使用new
2、 箭头函数不绑定arguments,取而代之用rest参数…解决,
3、 this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this 4、 箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响. 5、 箭头函数没有prototype(原型),所以箭头函数本身没有this 6、 箭头函数不能当做Generator函数,不能使用yield关键字、
7、 写法不同,箭头函数把function省略掉了 ()=> 也可以吧return 省略调 写法更简洁
8、箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。
1.var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
2、一个变量可多次声明,后面的声明会覆盖前面的声明
3、在函数中使用var声明变量的时候,该变量是局部的作用域只在函数内部,而如果在函数外部使用 var,该变量是全局的
1、不存在变量提升,let声明变量前,该变量不能使用。就是 let 声明存在暂时性死区 2、let命令所在的代码块内有效,在块级作用域内有效,作用域只是在花括号里面
3、let不允许在相同作用域中重复声明,注意是相同作用域,不同作用域有重复声明不会报错
1、const声明一个只读的常量,声明后,值就不能改变
2、let和const在同一作用域不允许重复声明变量const声明一个只读的常量。一旦声明,常量的值就不能改变,但对于对象和数据这种 引用类型,内存地址不能修改,可以修改里面的值。
3、let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错4、能用const的情况下尽量使用const,大多数情况使用let,避免使用var。 const > let > var const声明的好处,一让阅读代码的人知道该变量不可修改,二是防止在修改代码的过程中无意中修改了该变量导致报错,减少bug的产生
相同点
都是循环遍历数组中的每一项 forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组),需要用哪个的时候就写哪个 匿名函数中的this都是指向window 只能遍历数组
注意:forEach对于空数组是不会调用回调函数的。
不同点
map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。(原数组进行处理之后对应的一个新的数组。) map()方法不会改变原始数组 map()方法不会对空数组进行检测 forEach()方法用于调用数组的每个元素,将元素传给回调函数.(没有return,返回值是undefined)
1、Promise 是异步编程的一种解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱 的问题,自己身上有all、reject、resolve、race 等方法,原型上有then、catch等方法。
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作 promise,帮助我们处理队列
3、promise 有三个状态:pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败
4、Promise 对象状态改变:从
pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了5、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部,但是写了then 和 catch ,会被then的第二个参数 或 catch所捕获
promise 的then会返回一个新的 promise 对象,能保证 then 方 可以进行链式调用
Async 和 await 是一种同步的写法,但还是异步的操作,两个必须配合一起使用
函数前面的
async
关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise
对象。await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西,如果是promise则会等待promaise 返回结果,接普通函数直接进行链式调用.
await 能够获取promise执行的结果 await必须和async一起使用才行,async配合await使用是一个阻塞的异步方法
如果await后面不是Promise对象, 就直接返回对应的值,只能在async函数中出现, 普通函数直接使用会报错
await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行
使用场景:
我在项目中: 需求:执行第一步,将执行第一步的结果返回给第二步使用。在ajax中先拿到一个接口的返回数据,然后使用第一步返回的数据执行第 二步操作的接口调用,达到异步操作。
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值
常见的几种方式有
1.默认值
2.交换变量
3.将剩余数组赋给一个变量
结构数组和对象字符串区别
对象的解构与数组类似,但有所不同。数组的元素是按次序排列的,变量的取值由它的位置决定;
而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象.
我在项目中:就是从目标对象或数组中提取自己想要的变量。最常用的场景是:element-ui,vant-ui按需引入,请求接口返回数据,提取想要数据。
1、 推荐在循环对象属性的时候,使用 for…in,在遍历数组的时候的时候使用for…of。
2、 for in遍历的是数组的索引,而for of遍历的是数组元素值
3、for…of 不能循环普通的对象,需要通过和 Object.keys()搭配使用
4、for…in 便利顺序以数字为先 无法便利 symbol 属性 可以便利到公有中可枚举的
5、从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。
Generator 生成器 也是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同 function *(){}
Generator 函数是一个状态机,封装了多个内部状态,除了状态机,还是一个遍历器对象生成函数。
Generator 是分段执行的, yield (又得)可暂停,next方法可启动。每次返回的是yield后的表达式结果,这使得
Generator
函数非常适合将异步任务同步化Generator**
并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署
Interator`接口…)**
Generator
函数返回Iterator
对象,因此我们还可以通过for...of
进行遍历,原生对象没有遍历接口,通过Generator
函数为它加上这个接口,就能使用for...of
进行遍历了
promise、Generator、async/await进行比较:
promise和async/await是专门用于处理异步操作的
Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口…)
promise编写代码相比Generator、async更为复杂化,且可读性也稍差
Generator、async需要与promise对象搭配处理异步情况
async实质是Generator的语法糖,相当于会自动执行Generator函数
async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
js的构造函数(在别的后台语言上叫做类)上可以添加一些成员,可以在构造函数内部的this上添加,可以在构造函数本身上添加,通过这两种方式添加的成员,就分别称为实例成员和静态成员
实例成员:构造函数中this上添加的成员 静态成员:构造函数本身上添加的成员
实例成员,只能由实例化的对象来访问 静态成员,只能由构造函数本身来访问 实例化对象的proto指向构造函数的prototype属性指向的对象,实例化的对象可以访问到它后者身上的成员
构造函数生成实例的执行过程:使用面向对象编程时,new关键字做了什么?
新建了一个Object对象
修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数
为Object对象添加了一个proto属性,是其指向构造函数的prototype属性
将这个Object对象返回出去
set数据的特点是数据是唯一的
const set1 = new Set()
增加元素 使用 add
set2.add(4)
是否含有某个元素 使用 has
console.log(set2.has(2))
查看长度 使用 size
console.log(set2.size)
删除元素 使用 delete
set2.delete(2)
size: 返回Set实例的成员总数。
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值。
clear():清除所有成员,没有返回值。
Set
的不重复性
传入的数组中有重复项,会自动去重
const set2 = new Set([1, 2, '123', 3, 3, '123'])
Set`的不重复性中,要注意`引用数据类型和NaN
两个对象都是不用的指针,所以没法去重
const set1 = new Set([1, {name: '孙志豪'}, 2, {name: '孙志豪'}])
如果是两个对象是同一指针,则能去重
const obj = {name: '我们一样'}
const set2 = new Set([1, obj, 2, obj])
NaN !== NaN,NaN是自身不等于自身的,但是在Set中他还是会被去重
const set = new Set([1, NaN, 1, NaN])
map数据结构
Map`对比`object`最大的好处就是,key不受`类型限制
定义map
const map1 = new Map()
新增键值对 使用 set(key, value)
map1.set(true, 1)
判断map是否含有某个key 使用 has(key)
console.log(map1.has('哈哈'))
获取map中某个key对应的value
console.log(map1.get(true))
删除map中某个键值对 使用 delete(key)
map1.delete('哈哈')
定义map,也可传入键值对数组集合
const map2 = new Map([[true, 1], [1, 2], ['哈哈', '嘻嘻嘻']])
console.log(map2) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
symbol 是es6 加入的,是一个基本数据类型,它代表的是一个独一无二的值,SYMBOL 值是由 SYMBOL函数生成,也就是说现在我们定义对象的属性名字可以是原有的字符串 也可以是 symbol 类型的,symbol 可以保证不与其他属性名冲突,减少了bug的产生,
如果那 symbol 对比的话 就是会返回 false
symbol 他是一个原始类型的值就,不可以使用 new 关键字,symbol不是对象 没有迭代器的接口 不能去添加属性值,他是类似于字符串的一种类型
symbol 不能用来四则运算,否则会报错,只能用显示的方式转为字符串
symbol 参数里的 a 表示一种修饰符 对当前创建的 symbol 的一种修饰,作为区分 ,否则会混淆
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费。其实iteration == iterator 有三个作用:
为各种数据结构,提供一个统一的、简便的访问接口;
使得数据结构的成员能够按某种次序排列;
主要供
for...of
消费
Object.assign可以实现对象的合并。它的语法是这样的:
Object.assign(target, ...sources)
Object.assign
会将source里面的可枚举属性复制到target
。如果和target的已有属性重名,则会覆盖。同时后续的source会覆盖前面的source的同名属性。Object.assign复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题
Array.from()
方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象。
1、将类数组对象转换为真正数组:
let arrayLike = {
? 0: 'tom',
? 1: '65',
? 2: '男',
? 3: ['jane','john','Mary'],
? 'length': 4
}
let arr = Array.from(arrayLike)
console.log(arr) // ['tom','65','男',['jane','john','Mary']]
那么,如果将上面代码中
length
属性去掉呢?实践证明,答案会是一个长度为0的空数组。这里将代码再改一下,就是具有
length
属性,但是对象的属性名不再是数字类型的,而是其他字符串型的,代码如下:
let arrayLike = {
? 'name': 'tom',
? 'age': '65',
? 'sex': '男',
? 'friends': ['jane','john','Mary'],
? length: 4
}
let arr = Array.from(arrayLike)
console.log(arr) // [ undefined, undefined, undefined, undefined ]
会发现结果是长度为4,元素均为 undefined 的数组
由此可见,要将一个类数组对象转换为一个真正的数组,必须具备以下条件:
1、该类数组对象必须具有
length
属性,用于指定数组的长度。如果没有length
属性,那么转换后的数组是一个空数组。2、该类数组对象的属性名必须为数值型或字符串型的数字
我对模块的理解是,一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。
由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污 染,并且模块间没有联系。
后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所 有的所有的模块成员,外部代码可以修改内部属性的值。
现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。
js 中现在比较成熟的有四种模块加载方案:
第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。
加油快通关了,通关了你会有收获的。
beforeCreate() 创建前,这个时候data中的数据,还未定义,所以不能使用
created()创建后 最早开始使用 data和methods中数据的钩子函数
?
beforeMount()挂载前 指令已经解析完毕内存中已经生成dom树,还没有渲染到本地
mounted()挂载后 dom已经渲染完毕,最早可以操作DOM元素钩子函数
?
beforeUpdate()更新前 当data的数据发生改变会执行这个钩子 内存更新,但是DOM节点还未更新
updated()更新后 数据更新完成以后触发的方法,DOM节点已经更新
?
beforeDestroy()即将销毁 data和methods中的数据此时还是可以使用的,可以做一些释放内存的操作
destroyed()已经销毁完毕
其他三个:
activated 被 keep-alive 缓存的组件激活时调用。
deactivated 被 keep-alive 缓存的组件停用时调用。
errorCaptured 2.5.0+ 新增当捕获一个来自子孙组件的错误时被调用
?
Vue3.0对比vue2中的生命周期做了一些改动:
beforeCreate -> setup() 开始创建组件之前,创建的是data和method
created ? ? ? -> setup()
beforeMount ? -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted ? ? ? -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated ? ? ? -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed ? ? -> onUnmounted 组件卸载之前执行的函数。
深入扩展
- vue的实例加载完成是在哪个声明周期完成呢
beforeCreate
- vue的dom挂载完成是在哪个声命周期里呢
mounted
1、created mounted 的区别?
created 模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
2、怎么在created里面操作dom?
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
可以根据打印的顺序看到,在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this.$nextTick()可以等待dom生成以后再来获取dom对象,而通过this.$nextTick()获取到的值为dom更新之后的值
setTimeout(() => {
console.log(this.$refs.button);
});
3、那 setTimeout this.$nextTick 什么区别呢?
setTimeout 将同步转换为异步 this.$nextTick
this.$nextTick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,
4、this.$nextTick()是宏任务还是微任务啊?
微任务
5、a页面跳转到b页面周期执行
页面a----beforeCreate undefined
页面a----created 1
页面a----beforeMount 1
页面a----mounted 1
页面b----beforeCreate undefined
页面b----created 1
页面b----beforeMount 1
页面a----beforeDestroy 1
页面a----destroyed 1
页面b----mounted 1
6、组件 和 页面周期 的执行顺序
- 页面beforeCreate undefined
- 页面created 1
- 页面beforeMount 1
- 组件beforeCreate undefined
- 组件created 5555
- 组件beforeMount 5555
- 组件mounted 5555
- 页面mounted 1
7、父子组件生命周期执行顺序
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
代码更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
代码销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
代码常用钩子简易版
父create->子created->子mounted->父mounted
8、补充单一组件钩子执行顺序
activated, deactivated 是组件keep-alive时独有的钩子
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
errorCaptured
watch
仅仅是数据发生改变的时候会侦听到;
只是会检测到你写在watch里的那些属性,没写的就不会触发。
updated
执行到它的时候时候是数据发生变化且界面更新完毕;
不能监听到路由数据(例如网址中的参数);
所有的数据发生变化都会调用(消耗性能);
每次触发的代码都是同一个
computed
1、监控自己定义的变量,不用再data里面声明,函数名就是变量名
2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化,结果都会变化。
3、计算的结果具有缓存,依赖响应式属性变化,响应式属性没有变化,直接从缓存中读取结果。
4、在内部函数调用的时候不用加()。
5、必须用return返回
6、不要在computed 中对data中的数据进行赋值操作,这会形成一个死循环。
methods
用 methods 方法编写的逻辑运算,在调用时 add() 一定要加“()”,methods 里面写的多位方法,调用方法一定要有()。methods方法页面刚加载时调用一次,以后只有被调用的时候才会被调用。
在重新渲染的时候每次都会被重新的调用;
使用场景?
watch:
1、watch 函数是不需要调用的。
2、重点在于监控,监控数据发生变化的时候,执行回调函数操作。
3、当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch
4、函数名就是你要监听的数据名字
updated
有事先设置好的data变量如下arrData改变并且要在页面重新渲染{undefined{ arrData }}完成之后,才会进updated方法,
光改变arrData但不渲染页面是不会进的.
使用场景:
computed:
1、一个需要的结果受多个数据影响的时候,比如购物车结算金额(受到很多处的价格结算)。
2、操作某个属性,执行一些复杂的逻辑,并在多处使用这个结果。
3、内部函数中多处要使用到这个结果的。
watch :
1、监控一些input框值的特殊处理,适合一个数据影响多个数据。
2、数据变化时,执行一些异步操作,或开销比较大的操作
vuex 是一个状态管理工具,它采用集中式存储管理应用的所有组件的状态,当有多个组件共享数据时,如果需要构建是一个中大型单页应用,会考虑如何更好地在组件外部管理状态,就使用Vuex 。
好处:
①: 能够在 vuex 中集中管理共享的数据,易于开发和后期维护
②: 可以做状态管理、采用localstorage保存信息、数据一直存储在用户的客户端中
③: 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步,能够高效地实现组件之间的数据共享,提高开发效率
vuex核心:
state:vuex的基本数据,数据源存放地,用于定义共享的数据。
geeter:从基本数据派生的数据,相当于state的计算属性
mutation:提交更新数据的方法,唯一 一个可以操作state 中数据的方法,必须是同步的,第一个参数是state,第二个参数是cmmi传过来的数据
action:action是用来做异步操作的,一般用来发请求,在 action 中写入函数,然后在页面中用dispatch调用,然后在 action 中通过commit 去调用 mutation 通过 mutation 去操作state。
modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理
详述Vuex运行机制
(1) Vuex的状态存储是响应式的单页面
(2) 当vue组件从store中读取时,若store中状态发生改变,响应的组件也会更新状态
(3) 不能直接改变state,必须通过显示的提交(commit)mutations来追踪每个状态的变化
高级用法辅助函数(语法糖)
mapState,mapActions,mapMutations,mapGetters
辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作的目的
如何使用:
Import { mapActions, mapGetters, mapMutations, mapState } from ‘vuex’
computed(){mapActions([‘名字’])}
“key 值:用于管理可复用的元素。因为 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做使 Vue 变得非常快,但是这样也不总是符合实际需求。 2.2.0+ 的版本里,当在组件中使用 v-for 时,key 是必须的。”
更高效的对比虚拟DOM中每个节点是否是相同节点,相同就复用,不相同就删除旧的创建新的
key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点
全局: vue.directive:{"",{}} 局部:directives:{指令名:{钩子函数}}
bind(){} 只调用一次,指令第一次绑定到元素时调用
inserted(){} 被绑定元素插入父节点时调用
update(){} 被绑定元素所在的模板更新时调用,而不论绑定值是否变化
componentUpdated(){} 被绑定元素所在模板完成一次更新周期时调用
unbind(){}只调用一次, 指令与元素解绑时调用
参数:
el:指令所绑定的元素
binding:一个对象,
name:指令名,不包括 v- 前缀。
value:指令的绑定值
.stop 阻止事件冒泡
.cpture 设置事件捕获
.self 只有当事件作用在元素本身才会触发
.prevent 阻止默认事件,比如超链接跳转
.once 事件只能触发一次
.native 触发js原生的事件
.number 把文本框的内容转换为数字
.trim 去除文本框左右空格
指令
⑴v-bind:给元素绑定属性
⑵v-on:给元素绑定事件
⑶v-html:给元素绑定数据,且该指令可以解析 html 标签
⑷v-text:给元素绑定数据,不解析标签
⑸v-model:数据双向绑定
⑹v-for:遍历数组
⑺v-if:条件渲染指令,动态在 DOM 内添加或删除 DOM 元素
⑻v-else:条件渲染指令,必须跟 v-if 成对使用
⑼v-else-if:判断多层条件,必须跟 v-if 成对使用
⑽v-cloak:解决插值闪烁问题
⑾v-once:只渲染元素或组件一次
⑿v-pre:跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度
⒀v-show:条件渲染指令,将不符合条件的数据隐藏(display:none)
keep-alive是Vue提供给我们一个内置组件,会缓存不活动的组件实例,而不是销毁它们, 作为标签使用 包裹在需要缓存的组件外
在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性
作用: 比如列表页面进入详情,我们想保存列表滚动的位置,我们就可以使用keep-alive保存列表页面的滚动位置。
组件使用keep-alive以后会新增两个生命周期 actived() deactived()
activated(组件激活时使用) 与 deactivated(组价离开时调用)
有两个参数:
include - 包裹的组件名会被缓存
exclude 包裹的组件名都不会被缓存
Object.defineProperty怎么用, 三个参数?,有什么作用啊?
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, {})
obj:需要定义属性的对象
prop:需要定义的属性
{}:要定义或修改的属性描述符。
value: "18", // 设置默认值得
enumerable: true, //这一句控制属性可以枚举 enumerable 改为true 就可以参与遍历了 默认值false
writable: true, // 控制属性可以被修改 默认值false
configurable: true, // 控制属性可以被删除 默认值false
get // 当有人读取 prop 的时候 get函数就会调用,并且返回就是 sss 的值
set // 当有人修改 prop 的时候 set函数就会调用, 有个参数这个参数就是修改后的值
Object.defineProperty 能定义symbol类型吗?
在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty 是定 义key为Symbol的属性的方法之一。
虚拟dom 是根据模板生成一个js对象(使用createElement,方法),根据这个js对象再去生成真实的dom,对复杂的文档DOM结构,提供一种方便的工具,进行最小化的DOM操作 ,是可以快速的渲染和高效的更新元素,提高浏览器的性能,
例如,一个 ul 标签下很多个 li 标签,其中只有一个 li 有变化,这种情况下如果使用新的 ul 去替代旧的 ul,因为这些不必要的 DOM 操作而造成了性能上的浪费,但是如果直接使用虚拟节点覆盖旧节点的话,减少很多不必要的 DOM 操作。。
diff算法 当data发生改变 会根据新的数据生成一个新的虚拟dom ,新的虚拟dom和旧的虚拟dom进行对比,这个对比的过程就是diff算法,会找到不同地方,只去渲染不同的地方
总的来说就是减少DOM,重绘和回流。
props:会接收不同的数据类型,常用的数据类型的设置默认值的写法,Number, String, Boolean, Array, Function, Object
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级prop 的更新流动到子组件中,但是反过来则不行。这样防止子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。若果在子组件中直接修改prop传递的值,Vue会发出警告,
computed 是vue中的计算属性,具有缓存性,当他的依赖于值,发生改变的时候才会重新调用
methods 是没有缓存的,只要调用,就会执行,一般结合事件来使用
watch 没有缓存性 监听data中的属性 属性值只要发生变化就会执行 可以利用他的特性做一些异步的操作
是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图,实现数据和视图同步。
1、 observer 主要是负责对Vue数据进行递归便利,使其数据拥有get和set方法,当有数据给某个对象值赋值,就触发 setter 就监听到数据的变化了。( 如有变动可拿到最新值并通知订阅者 )
2、 compile (抗牌偶) 指令解析器负责绑定数据和指令解析。 将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数。一旦数据有变动,收到通知,更新视图
3、 watcher Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是 负责数据监听,当数据发生改变,能调用自身的update()方法,并触发Compile中绑定的回调
4、mvvm 入口函数,整合以上三者
在vue3 中
Vue3是通过Object.define.proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法
父传递子如何传递
(1)在父组件的子组件标签上绑定一个属性,挂载要传输的变量 (2)在子组件中通过props来接受数据,props可以是数组也可以是对象,接受的数据可以直接使用 props: [“属性名”] props:{属性名:数据类型}
子传递父如何传递
(1)在父组件的子组件标签上自定义一个事件,然后调用需要的方法 (2)在子组件的方法中通过 this.$emit(“事件”)来触发在父组件中定义的事件,数据是以参数的形式进行传递的
兄弟组件如何通信
(1)在src中新建一个Bus.js的文件,然后导出一个空的vue实例 (2)在传输数据的一方引入Bus.js 然后通过Bus. e m i t ( “ 事 件 名 ” , " 参 数 " ) 来 来 派 发 事 件 , 数 据 是 以 emit(“事件名”,"参数")来来派发事件,数据是以 emit(“事件名”,"参数")来来派发事件,数据是以emit()的参 数形式来传递 (3)在接受的数据的一方 引入 Bus.js 然后通过 Bus.$on(“事件名”,(data)=>{data是接受的数据})
12种组件通信了解一下吧:12种组件通信让你的数据飞起来
主要通过 query 和 params 来实现
(1) query可以使用name和path而params只能使用name
(2) 使用params传参刷新后不会保存,而query传参刷新后可以保存
(3) Params在地址栏中不会显示,query会显示
(4) Params可以和动态路由一起使用,query不可以
(5)to=”/goodsid=1001”this.然后在接收的页面通过 $route.query.id 来接收
(6)动态路由传参
一:全局的守卫
无论访问哪一个路径,都会触发全局的钩子函数,位置是调用router的方法 router/index.js
router.beforeEach 全局前置守卫 进入路由之前
router.beforeResolve 全局解析守卫,在beforeRouteEnter调用之后调用,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用
router.afterEach 全局后置钩子 进入路由之后,你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受
next
函数也不会改变导航本身:
二:组件级路由守卫 放在要守卫的组件里,跟data和methods同级
beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到zhis
beforeRouteUpdate (2.2) 路由复用同一个组件时
beforeRouteLeave 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等
三:单个路由规则独享的守卫 写在路由配置中,只有访问到这个路径,才能触发钩子函数
beforeEnter(){}
重定向用哪个属性?
redirect:”/路径”
路由的配置
export default new Router({
mode: 'history', //路由模式,取值为history与hash
base: '/', //打包路径,默认为/,可以修改
routes: [
{
path: string, //路径
ccomponent: Component; //页面组件
name: string; // 命名路由-路由名称
components: { [name: string]: Component }; // 命名视图组件
redirect: string | Location | Function; // 重定向
props: boolean | string | Function; // 路由组件传递参数
alias: string | Array; // 路由别名
children: Array; // 嵌套子路由
// 路由单独钩子
beforeEnter?: (to: Route, from: Route, next: Function) => void;
meta: any; // 自定义标签属性,比如:是否需要登录
icon: any; // 图标
// 2.6.0+
caseSensitive: boolean; // 匹配规则是否大小写敏感?(默认值:false)
pathToRegexpOptions: Object; // 编译正则的选项
}
]
})
路由的原理
路由就是用来解析URL以及调用对应的控制器,并返回从视图对象中提取好的网页代码给web服务器,最终返回给客户端。
Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。
如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。
然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。
vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所有需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。
模板编译又分三个阶段,解析parse,优化optimize,生成generate,最终生成可执行函数render。
问题原因:因为 vue 的检查机制在进行视图更新时无法监测 数组中的对象的某个属性值的变化。解决方案如下
方案一:利用 this.set(this.obj,key,val)
例:this.set(this.obj,‘k1’,‘v1’)
方案二:就利用 Object.assign({},this.obj)创建新对象 (额 晒恩)
如果是数组就 Object.assign([],this.obj)
如果是对象就 Object.assign({},this.obj)。
1.性能提升
更小巧,更快速;支持摇树优化。支持 Fragments 和跨组件渲染;支持自定义渲染器。
2.API 变动
vue2:optionsApi 使用传统api中,新增一个需求,要在data,methods,computed中修改
vue3:compositionApi 我们可以更加优雅的组织我们的代码,函数,让我们的代码更加有序的组合在一起
除渲染函数 API 和 scoped-slot 语法之外,其余均保持不变或者将通过另外构建一个兼容包 来兼容 2.x。
模板语法的 99% 将保持不变。除了 scoped slot 语法可能会有一些微调之外变动最大的部分将是渲染函数 (render) 中的虚拟 DOM 的格式。
3.重写虚拟 DOM (Virtual DOM Rewrite)
随着虚拟 DOM 重写,减少 运行时(runtime)开销。重写将包括更有效的代码来创建虚拟节点。
vue3 没有了过滤器
双向数据绑定 从 Object.defineProperty() 变成了 proxy,数据变化了试图数据没发生变化 this.$set() vue3不需
Object是引用数据类型,如果不用function返回,每个组件的data都是内存的同一个地址,一个数据改变了其他也改变了,这就造成了数据污染。
(1)MVC:是后台的框架模式 分为M:(model模型)、V(view试图)、C(controller控制器)
(2)MVVM是为了实现MVC中的V MVVM分为:M(model数据)、V(view试图)、VM(viewModel控制数据的改变和控制试图)
(3)MVP MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示,在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的
过滤器是对 即将显示的数据做进一步的筛选处理,然后显示,过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据
全局:
Vue.filter(‘过滤器名’,funciton(val){})
局部过滤器,定义在组件内部 filters 属性上.它只能在此组件内部使用.
filters:{过滤器名:funciton(参数){//逻辑代码}}
使用: 过滤时间,过滤金钱
主张最少——它是一个轻量级框架,只做了自己该做的事,没有做不该做的事
这里的vue数据驱动的是视图,也就是DOM元素,指的是让DOM的内容随着数据的改变而改变框架的理解
单页面:只有一个html页面,跳转方式是组件之间的切换
优点:跳转流畅、组件化开发、组件可复用、开发便捷
缺点:首屏加载过慢
多页面:有多个页面,跳转方式是页面之间的跳转
优点:首屏加载块
缺点:跳转速度慢
如何解决vue首屏加载过慢:
(1)路由懒加载
(2)使用异步组件,按需加载
(3)图片量多的时候可以进行分批的加载,图片懒加载
能够解决插值表达式闪烁问题,需要在style中设置样式[v-clock]{display:none}
(1) 特性:重用性、可指定性、互操作性、高内聚性、低耦合度
(2) 好处:组件可以扩展HTML元素、封装可重用代码
template 结构(html代码)
script行为
style样式
slot 插槽 用于决定将所携带的内容,插入到指定的某个位置
命名slot 使用slot标签的name属性为slot命名,这样就可以指定多个可区分的slot,在使用组件时灵活地进行插值。
Slot怎么使用? 用slot标签使用插槽
默认插槽就是把父组件中的数据,显示在子组件中,子组件通过一个slot插槽标签显示父组件中的数据
具名插槽是在父组件中通过slot属性,给插槽命名,在子组件中通过slot标签,根据定义好的名字填充到对应的位置。
作用域插槽是带数据的插槽,子组件提供给父组件的参数,父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签中通过slot-scope来接受数据。
快速开始一个vue项目,不用手动配置,直接开发
有两种模式 hash和history模式 默认是hash
1、hash ——即地址栏 URL 中的#符号,它的特点在 于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影 响,因此改变 hash 不会重新加载页面。
2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题
**history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,**如 地址后加上/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。
在vue开发中实现跨域:在vue项目根目录下找到vue.config.js文件(如果没有该文件则自己创建),在proxy中设置跨域
devServer: {
? proxy: { //配置跨域
? ? '/api': {
? ? ? target: 'http://121.121.67.254:8185/', //这里后台的地址模拟的;应该填写你们真实的后台接口
? ? ? changOrigin: true, //允许跨域
? ? ? pathRewrite: {
? ? ? ? /* 重写路径,当我们在浏览器中看到请求的地址为:http://localhost:8080/api/core/getData/userInfo 时
? ? ? ? ? 实际上访问的地址是:http://121.121.67.254:8185/core/getData/userInfo,因为重写了 /api
? ? ? ? ? */
? ? ? ? '^/api': ''
? ? ? }
? ? },
? }
},
在下次DOM更新循环结束后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
使用场景是:可以在created钩子函数中拿到dom节点
v-if 动态的创建或者销毁元素,为true的时候为显示,为false的时候不显示,要使用v-else必须和v-if紧挨着
v-show 是控制元素的显示或者隐藏,在我们的标签上会看到有display:block,none
v-if 有更高的切换消耗,而 v-show 有更高的初始化渲染消耗,一般推荐频繁切换的时候使用 v-show 更好,
当我们的判断分支比较多的时候,和首次渲染的时候 使用v-if
灵活的组件应用,高效的数据绑定
route 路由信息对象 信息对象里有参数和名字等信息
router 路由实例 有一些方法比如push
1、
2、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面
3、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面
4、this.$touter.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数
在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式
1、一个需要的结果受多个数据影响的时候,比如购物车结算金额(受到很多处的价格结算)。
2、操作某个属性,执行一些复杂的逻辑,并在多处使用这个结果。
3、内部函数中多处要使用到这个结果的。
created:dom渲染前调用,即通常初始化某些属性值
mounted:在dom渲染后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作
assets中的文件会经过webpack打包,重新编译,推荐在assets存放js等需要打包编译的文件。 static中的文件,不会打包编译。
static中的文件只是复制一遍。static中建议放一些外部第三方文件,自己的放assets里,别人的放static中。(图片推荐放在static里)
在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 route 对象的 params.id 获取
v-if 和 v-show 区分使用场景
computed 和 watch 区分使用场景
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
长列表性能优化
事件的销毁
图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表性能
服务端渲染 SSR or 预渲染
不会立即同步执行重新渲染。**Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。**Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环tick中,Vue 刷新队列并执行实际(已去重的)工作。
在vue 中的 component 中新建组件,定义好视图层,
首先是通过在根目录下创建.env.*(配置文件)文件,
development 本地开发环境配置、
staging 测试环境配置、
production 正式环境配置。
因为我在创建的文件中并没有定义很多变量,只定义了基础的env,所以需要在src目录下创建一个config文件夹,创建对应的环境变量文件,用来管理不同的环境。在config中创建对应的文件是为了后期修改起来方便,不需要重启项目,符合开发习惯。之后就是根据需要的环境,在封装的axios中通过解构赋值的方式导入,放在baseURL中就可以使用。
首先要安装axios,一般我会在项目的src目录中,新建一个network文件夹,作为我们的网络请求模块,**然后在里面新建一个http.js和一个api.js文件和一个reques.js。http.js文件用来封装我们的axios,api.js用来统一管理我们的接口url,**在request.js中添加请求拦截和响应拦截。
在请求拦截中,会给请求头添加token字段,还有loading动画的开启。在响应拦截中,可以做一些loading动画的关闭,还有可以根据后端返回的状态码,做一些检验token是否有效或者过期的操作。
接着就是做一些axios进行的api接口的封装,这里我用到了async,await封装请求接口函数,这样可以代将异步操作同步化操作,码更加友好,避免回调地域的出现。
打开element官网找到按需引入,首先安装按需引入的插件,在babel.config.js中添加按需引入的配置,创建一个plugin文件夹,定义一个js文件用来存放按需引入的代码,之后在建好的js文件中首先导入vue,再导入需要的vant-ui插件,通过vue.use()全局注入。修改样式可以用样式穿透 /deep/
详见这一片文章:elemnet的按需引入报错?,不会按需引入?
key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点
尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用 如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件 在更多的情况下,使用v-if替代v-show key保证唯一
使用路由懒加载、异步组件 防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载 SEO优化 预渲染 服务端渲染SSR 打包优化 压缩代码
骨架屏
新建了一个Object对象
修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数
为Object对象添加了一个proto属性,是其指向构造函数的prototype属性
将这个Object对象返回出去
原型链继承
父类的实例作为子类的原型,易于实现, 只能继承 原型上的方法,不能继承构造函数里的属性和方法
原型链继承
实现:
父类的实例作为子类的原型
优点:
简单,易实现 不限制调用方式
缺点:
只能继承 原型上的方法 不能继承构造函数里的属性和方法
function Person() {
this.a = true
}
Person.prototype.fn = function () {
console.log('我是父类')
}
function Son() {
this.b = false
}
这里是关键 创建 Person 的实例 然后赋值给 Son 的原型
Son.prototype = new Person()
let xiaoming = new Son()
xiaoming.fn()
console.log(xiaoming.a)
构造函数继承
不能继承原型属性/方法,可以实现多继承,可以传参,无法复用,
构造函数继承
实现:
Father.call(this),创建子类实例时调用Father构造函数,
于是SubType的每个实例都会将Father中的属性复制一份。
优点:
解决了子类构造函数向父类构造函数中传递参数
缺点:
无法实现复用,不能继承原型属性/方法,只能继承父类的实例属性和方法
function Father(age, name) {
this.a = '22'
}
function Children() {
Father.call(this)
}
let Class = new Children()
console.log(Class.a);
组合继承
通过call 对实例属性的继承,原型链对原型方法的继承, 调用多次
组合继承
实现:
组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造 函数技术来实现实例属性的继承。
优点:
既能调用父类实例属性,又能调用父类原型属性
调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用
可以继承属性和方法,并且可以继承原型的属性和方法
缺点:
缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。
function Father(name) { // 这里的name 就是 Son 传过来的
this.name = name
this.colors = [1, 2]
}
Father.prototype.sayName = function () {
alert(this.name)
}
function Son(name, age) {
//继承实例属性,第一次调用Father()
Father.call(this, name) // 这里通过 call 传过去 name 继承实例属性
this.age = age
}
Son.prototype = new Father() // 继承父类方法,第二次调用 Father
Son.prototype.ff = function () { // 子类的方法
console.log('666');
}
var aa = new Son('小明', 5)
aa.colors.push(3)
console.log(aa); // 打印了 父类的属性 和方法 以及子类的方法
var bb = new Son('小红', 50)
aa.colors.push(999999)
console.log(bb); // 打印了 父类的属性 和方法 以及子类的方法
Es6有class继承:
首先利用class构造一个父类,然后利用extends与super实现子类继承
ES6类继承extends
extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,使用例子如下。
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200
-----------------------------------------------------------------
// 继承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 输出 100
每个函数都有一个prototype属性,被称为显示原型
每个实例对象都会有
_ _proto_ _
属性,其被称为隐式原型每一个实例对象的隐式原型
_ _proto_ _
属性指向自身构造函数的显式原型prototype每个prototype原型都有一个constructor属性,指向它关联的构造函数。
原型链
获取对象属性时,如果对象本身没有这个属性,那就会去他的原型
__proto__
上去找,如果还查不到,就去找原型的原型,一直找到最 顶层(Object.prototype
)为止。Object.prototype对象也有proto属性值为null。链式查找机制叫原型链。
1、我们一般使用字面量的形式直接创建对象
(1)第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。
(2)第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。
(3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。
(4)第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。
(5)第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。
(6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,
概念:
设计模式****是一套被反复使用的代码,设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 设计模式让代码变得工程化,设计模式是软件工程的基石。
1、js工厂模式,去做同样的事情,实现同样的效果,解决多个相似的问题这时候需要使用工厂模式
2、发布订阅模式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
3、单例模式 单例模式 保证一个类仅有一个实例,并提供一个访问它的全局访问点
构造函数的prototype指向原型对象
实例对象的proto指向构造函数的prototype所指向原型对象
原型对象的constructor指向构造函数
一、面向过程:面向过程就是分析出实现需求所需要的步骤,通过函数一步一步实现这些步骤,接着依次调用即可。
二、面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程面向过程:
优点:性能上它是优于面向对象的,因为类在调用的时候需要实例化,开销过大。
缺点:不易维护、复用、扩展
用途:单片机、嵌入式开发、Linux/Unix等对性能要求较高的地方
面向对象:
面向对象有三大特性:封装,继承,多态。
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 。
缺点:性能比面向过程低
▍Angular
框架比较成熟完整,过于庞大,上手难;
指令以ng-xxx开头; 由谷歌开发和维护;
版本1比较适合PC端开发,版本2在往移动端靠;
不支持低版本浏览器; 内置指令和自定义指令;
内置过滤器和自定义过滤器; 支持双向数据绑定;
▍Vue
它是一个轻量级框架,其核心库只关注视图层,简单小巧、易学易上手;
指令以v-xxx开头; 个人维护项目; 适合于移动端开发; 不支持低版本浏览器;
内置指令和自定义指令; 内置过滤器和自定义过滤器; 支持双向数据绑定;
使用DOM模板。中心思想:一切都是组件,组件实例之间可以嵌套; 核心库不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载; 基于依赖追踪的观察系统,并且异步队列更新。
▍React
依赖虚拟DOM; 采用特殊的JSX语法; 中心思想:一切都是组件,组件实例之间可以嵌套; 核心库不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载。
Webpack Webpack 是一个项目打包工具
可以压缩代码和图片,把浏览器识别不了的代码转化为能识别的,可以启动一个热加载服务器
配置跨域、路径别名、打包分析、cdn映入、去掉console.log、单独打包第三方模块、ie兼容、eslint规范、图片压缩
最大的区别就是: Vue2使用 选项类型API(Options API) 对比Vue3 合成型API(Composition API)
- 双向数据绑定原理发生了改变,使用proxy替换Object.defineProerty,使用Proxy的优势:
可直接监听数组类型的数据变
监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升
可直接实现对象属性的新增/删除
- 默认使用懒加载
在2.x版本里。不管数据多大,都会在一开始就为其创建观察者,在数据很大时,就会造成性能的问题。在3.x中,只会对渲染出来的数据创建观察者,而且3.x的观察者更高效。
3.0新加入了TypeScript以及PWA支持
生命周期有了一定的区别
Vue2--------------vue3
beforeCreate -> setup() 开始创建组件之前,创建的是data和method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated
1. git init 初始化git仓库 (mac中Command+Shift+. 可以显示隐藏文件)
2. git status 查看文件状态
3. git add 文件列表 追踪文件
4. git commit -m 提交信息 向仓库中提交代码
5. git log 查看提交记录
1.分支明细
(1)主分支(master):第一次向 git 仓库中提交更新记录时自动产生的一个分支。
(2)开发分支(develop):作为开发的分支,基于 master 分支创建。
(3)功能分支(feature):作为开发具体功能的分支,基于开发分支创建
2.分支命令
(1)git branch 查看分支
(2)git branch 分支名称 创建分支
(3)git checkout 分支名称 切换分支
(4)git merge 来源分支 合并分支 (备注:必须在master分支上才能合并develop分支)
(5)git branch -d 分支名称 删除分支(分支被合并后才允许删除)(-D 强制删除)
3.暂时保存更改
(1)存储临时改动:git stash
(2)恢复改动:git stash pop
更加详细请看git常用命令
git怎么解决多人冲突?:
是当前修改是左箭头方向,传入的是右箭头的方向,中间用等于号分割,等号上边是当前修改,下边是传入的修改。
两人同时提交可能会出现冲突,解决办法是手动修改冲突
- 减少 HTTP请求数
- 从设计实现层面简化页面
- 合理设置 HTTP缓存
- 资源合并与压缩
- 合并 CSS图片,减少请求数的又一个好办法。
- 将外部脚本置底(将脚本内容在页面信息内容加载后再加载)
- 多图片网页使用图片懒加载。
- 在js中尽量减少闭包的使用
- 尽量合并css和js文件
- 尽量使用字体图标或者SVG图标,来代替传统的PNG等格式的图片
- 减少对DOM的操作
- 在JS中避免“嵌套循环”和 “死循环”
- 尽可能使用事件委托(事件代理)来处理事件绑定的操作
- 浏览器缓存
- 防抖、节流
- 资源懒加载、预加载
- 开启Nginx gzip压缩
三个方面来说明前端性能优化
一: webapck优化与开启gzip压缩
? ?1.babel-loader用 include 或 exclude 来帮我们避免不必要的转译,不转译node_moudules中的js文件
? ?其次在缓存当前转译的js文件,设置loader: 'babel-loader?cacheDirectory=true'
? ?2.文件采用按需加载等等
? ?3.具体的做法非常简单,只需要你在你的 request headers 中加上这么一句:
? ?accept-encoding:gzip
? ?4.图片优化,采用svg图片或者字体图标
? ?5.浏览器缓存机制,它又分为强缓存和协商缓存
二:本地存储——从 Cookie 到 Web Storage、IndexedDB
? ?说明一下SessionStorage和localStorage还有cookie的区别和优缺点
三:代码优化
? ?1.事件代理
? ?2.事件的节流和防抖
? ?3.页面的回流和重绘
? ?4.EventLoop事件循环机制
? ?5.代码优化等等
基于promise的http库,可以用在浏览器和node.js,支持promiseAPI,客户端支持防御xsrf
Node是一个基于Chrome V8引擎的JavaScript代码运行环境。
浏览器(软件)能够运行JavaScript代码,浏览器就是JavaScript代码的运行环境
Node(软件)能够运行JavaScript代码,Node就是JavaScript代码的运行环境
一句话:降低软件的复杂性。使其可控,可维护,可扩展。
一个功能就是一个模板,多个模板可以组成完整应用,抽离一个模板不会影响其他功能的运行
网站应用程序主要分为两大部分:客户端和服务器端。客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用HTML、CSS、JavaScript构建。服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。
简单强大,轻量可扩展。
简单体现在node使用的是javascript,json来进行编码,强大体现在非阻塞IO,可以适应分块传输数据,较慢的网络环境,尤其擅长高并发访问,轻量体现在node本身既是代码,又是服务器,前后端使用统一语言;可扩展体现在可以轻松应对多实例,多服务器架构,同时有海量的第三方应用组件。
node是单线程的,异步是通过一次次的循环事件队列来实现的.同步则是说阻塞式的IO,这在高并发环境会是一个很大的性能问题,所以同步一般只在基础框架的启动时使用,用来加载配置文件,初始化程序什么的.
NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题。
使用场景:
a. 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
b. 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
c. 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。
get是从服务器上获取数据,post是向服务器传送数据。
POST比GET安全,因为数据在地址栏上不可见。
get方式提交的数据最多只能有1024字节,而post则没有此限制。
GET使用URL或Cookie传参。而POST将数据放在request BODY中。
GET与POST都有自己的语义,不能随便混用。
据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基,本可以无视。而在网 络环境差的情况下,两次包的TCP在验证数据包完整 性上,有非常大的优点。post 发送两次,get 只发送一次。
并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。
ajax不是语言,ajax是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术
优点
1、最大的一点是页面无刷新,用户的体验非常好。
2、使用异步方式与服务器通信,具有更加迅速的响应能力。
缺点
1、ajax不支持浏览器back按钮。
2、安全问题 AJAX暴露了与服务器交互的细节。
3、对搜索引擎的支持比较弱。
4、破坏了程序的异常机制。
5、不容易调试
1.创建xhr 核心对象
var xhr=new XMLHttpRequest();
?
2.调用open 准备发送
参数一:请求方式
参数二: 请求地址
参数三:true异步,false 同步
xhr.open('post','http://www.baidu.com/api/search',true)
?
3.如果是post请求,必须设置请求头。
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
?
4.调用send 发送请求 (如果不需要参数,就写null)
xhr.send('user=tom&age=10&sex=女')
?
5.监听异步回调 onreadystatechange
判断readyState 为4 表示请求完成
判断status 状态码 为 200 表示接口请求成功
responeseText 为相应数据。字符串类型。
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if(xhr.status==200){
? ? ? ? ? console.log(xhr.responseText);
? ? ? ? ? var res=JSON.parse(xhr.responseText);
? ? ? ? ? console.log(res);
? ? ? ? ? if(res.code==1){
? ? ? ? ? modal.modal('hide');
? ? ? ? ? location.reload();
? ? ? }
? }
? ? ? ? ? ?
? ? ? ? ? ?
备注:如果是post请求,想要传json格式数据。
设置请求头
?
1.xhr.setRequestHeader('Content-Type', 'application/json')
?
open发送数据
2.xhr.open({_id:xxx,user:xxxx,age:xxxx})
**
XSS
(Cross-Site Scripting
,跨站脚本攻击)**是一种代码注入攻击。攻击者在目标网站上注入恶意代码,当被攻击者登陆网站时就会执行这些恶意代码,这些脚本可以读取**cookie,**session tokens
,或者其它敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。XSS避免方式:
url
参数使用encodeURIComponent
方法转义尽量不是有
InnerHtml
插入HTML
内容使用特殊符号、标签转义符。
CSRF
(Cross-site request forgery
)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
CSRF
避免方式:
添加验证码
使用token
服务端给用户生成一个token,加密后传递给用户
用户在提交请求时,需要携带这个token
服务端验证token是否正确
就是通过吧SQL命令插入到Web表单递交或输入域名,最终达到欺骗服务器执行恶意的SQL命令。
解决:表单输入时通过正则表达式将一些特殊字符进行转换
4、DDoS攻击
DDoS
又叫分布式拒绝服务,全称Distributed Denial of Service
,其原理就是利用大量的请求造成资源过载,导致服务不可用。解决:
限制单IP请求频率。
防火墙等防护设置禁止
ICMP
包等检查特权端口的开放
1. 客户端使用用户名跟密码请求登录
2. 服务端收到请求,去验证用户名与密码
3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
常见http状态码分类:
? ?200响应成功
? ?301永久重定向
? ?302临时重定向
? ?304资源缓存
? ?403服务器禁止访问
? ?404服务器资源未找到
? ?500 502服务器内部错误
? ?504 服务器繁忙
? ?1xx Informational(信息状态码) ?接受请求正在处理
? ?2xx Success(成功状态码) ? ? ? ? ? ?请求正常处理完毕
? ?3xx Redirection(重定向状态码) 需要附加操作已完成请求
? ?4xx Client Error(客户端错误状态码) 服务器无法处理请求
? ?5xx Server Error(服务器错误状态码) 服务器处理请求出错
浏览器的地址栏输入URL并按下回车,
查找当前的URL是否存在缓存,并比较缓存是否过期,
DNS解析URL对应的IP,
根据IP建立TCP连接(三次握手),
HTTP发起请求,服务器处理请求,浏览器接收HTTP响应,
渲染页面,构建DOM树,
关闭TCP连接(四次挥手)。
1.`TCP`向上层提供面向连接的可靠服务 ,`UDP`向上层提供无连接不可靠服务。
2.虽然 `UDP` 并没有 `TCP` 传输来的准确,但是也能在很多实时性要求高的地方有所作为
3.对数据准确性要求高,速度可以相对较慢的,可以选用`TCP`
区别
UDP
TCP
是否连接
无连接
面向连接
是否可靠
不可靠传输,不使用流量控制和拥塞控制
可靠传输,使用流量控制和拥塞控制
连接对象个数
支持一对一,一对多,多对一和多对多交互通信
只能是一对一通信
传输方式
面向报文
面向字节流
首部开销
首部开销小,仅8字节
首部最小20字节,最大60字节
适用场景
适用于实时应用(IP电话、视频会议、直播等)
适用于要求可靠传输的应用,例如文件传输
1.`HTTP` 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
2.`HTTP` 是不安全的,而 HTTPS 是安全的
3.`HTTP` 标准端口是80 ,而 HTTPS 的标准端口是443
4.`在OSI` 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层
5.`HTTP` 无法加密,而HTTPS 对传输的数据进行加密,证的网络协议,安全性高于HTTP协议。
6.`HTTP`无需证书,而HTTPS 需要CA机构wosign的颁发的SSL证书,一般免费证书少,因而需要一定费用。
1.GET在浏览器回退不会再次请求,POST会再次提交请求
2.GET请求会被浏览器主动缓存,POST不会,要手动设置
3.GET请求参数会被完整保留在浏览器历史记录里,POST中的参数不会
4.GET请求在URL中传送的参数是有长度限制的,而POST没有限制
5.GET参数通过URL传递,POST放在Request body中
6.GET参数暴露在地址栏不安全,POST放在报文内部更安全
7.GET一般用于查询信息,POST一般用于提交某种信息进行某些修改操作
8.GET产生一个TCP数据包;POST产生两个TCP数据包
Ge和post的选择:
1.私密性的信息请求使用post(如注册、登陆)。
2.查询信息使用get。
三次握手:
第一次:建立连接时,客户端发送syn包到服务器,等待服务端确认
第二次:服务器收到syn包,必须确认客户的syn,同时也发送一个syn包,即syn+ACK包
第三次:客户端收到服务器的syn和ack包,向服务器发送确认包ack,发送完毕,客户端和服务端连接成功,完成三次握手
四次挥手:
第一次:浏览器发送完数据后,发送fin请求断开连接
第二次:服务器发送ack到客户端,确认客户端的断开请求
第三次:服务器请求断开fin的请求
第四次:客户端确认服务器的断开ack
1和1.0相比,1.1可以一次传输多个文件
http1.x解析基于文本,
http2.0采用二进制格式,新增特性 多路复用、header压缩、服务端推送(静态html资源)
强缓存==>Expires(过期时间)/Cache-Control(no-cache)(优先级高) 协商缓存 ==>Last-Modified/Etag(优先级高)Etag适用于经常改变的小文件 Last-Modefied适用于不怎么经常改变的大文件
强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。
浏览器的本地存储主要分为
Cookie、WebStorage和IndexDB
, 其中WebStorage
又可以分为localStorage和sessionStorage
。共同点: 都是保存在浏览器端、且同源的
不同点:
cookie
数据始终在同源的http
请求中携带(即使不需要),即cookie
在浏览器和服务器间来回传递。cookie
数据还有路径(path
)的概念,可以限制cookie
只属于某个路径下sessionStorage
和localStorage
不会自动把数据发送给服务器,仅在本地保存。存储大小限制也不同,
cookie
数据不能超过4K,sessionStorage和localStorage
可以达到5M
sessionStorage
:仅在当前浏览器窗口关闭之前有效;
localStorage
:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据;
cookie
:只在设置的cookie
过期时间之前有效,即使窗口关闭或浏览器关闭
- 作用域不同
sessionStorage
:不在不同的浏览器窗口中共享,即使是同一个页面;
localstorage
:在所有同源窗口中都是共享的;也就是说只要浏览器不关闭,数据仍然存在
cookie
: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭,数据仍然存在