前端基础

前端基础这篇文章主要是对 html+css+JavaScript 三件套的一个深度知识总结,篇幅会较长,力求做到知识点不遗漏,方便以后有遗忘的知识点的时候,可以通过快速的回顾笔记而熟悉起来。

前端关注点:美观、功能、无障碍、性能、兼容、安全、用户体验

我会不断更新这个笔记,让它质量越来越高

基础知识

浏览器历史

  1. 1990 蒂姆 伯纳斯 李 发明超文本

  2. 1993 伊利诺大学 MOSIAC 浏览器 可以显示图片

  3. 1994 网景公司 Netscape navigator

  4. 1996 微软收购 spy glass -> IE1.0; 网景公司开发 livescript; Java火起来,livescript不温不火,livescript改名为JavaScript

  5. 2001 IE6 XP诞生,出现 js 引擎

  6. 2003 mozilla公司 firefox -> netscape

  7. 2008 google基于webkit blink gears 开发Chrome,

    Chrome —> V8引擎,速度非常快,一是可以直接翻译机器码,二是独立于浏览器运行;v8的诞生,直接催化了 nodejs的诞生

  8. 2009 甲骨文Oracle收购SUN,JS所有权给甲骨文

主流浏览器

5大主流浏览器 内核
IE trident
chrome webkit blink
safari webkit
firefox gocko
opera presto

浏览器由渲染引擎和 JS引擎组成。

渲染引擎

浏览器所采用的「渲染引擎」也称之为「浏览器内核」,用来解析 HTML与CSS,决定了浏览器如何显示网页的内容以及页面的格式信息。

渲染引擎是浏览器兼容性问题出现的根本原因。

JS引擎

浏览器本身并不会执行JS代码,而是通过内置 JavaScript 引擎(解释器) 来执行 JS 代码 。

JS 引擎执行代码时会逐行解释每一句源码(转换为机器语言),然后由计算机去执行。所以 JavaScript 语言归为脚本语言,会逐行解释执行。

html

html,hypertext markup language,超文本标记语言, 不是一种编程语言,是一种描述性的标记语言

编程语言是有编译过程的,而标记语言没有编译过程,HTML标签是直接由浏览器解析执行

要理解html标签的语义化特性。比如,面试的时候问你,<h1> 标签有什么作用?

  • 正确答案:给文本增加主标题的语义。
  • 错误答案:给文字加粗、加黑、变大。

HTML标签是分等级的,HTML将所有的标签分为两种:

  • 文本级标签:p、span、a、b、i、u、em。文本级标签里只能放文字、图片、表单元素。(a标签里不能放a和input)
  • 容器级标签:div、h系列、li、dt、dd。容器级标签里可以放置任何东西。

div的语义是division“分割”; span的语义就是span“范围、跨度”。

  • div标签:可以把标签中的内容分割为独立的区块。必须单独占据一行。
  • span标签:和div的作用一致,但不换行。

pre标签:保证标签内空格不被合并,真正排网页过程中,<pre>标签几乎用不着

实体字符

  • &nbsp 空格 (non-breaking spacing,不断打空格)
  • &lt 小于号< ; &gt 大于号>

超链接

  • title:鼠标悬停时呈现出的文本。

  • name:主要用于设置一个锚点的名称。

  • target:告诉浏览器用什么方式来打开目标页面

    • _self:在同一个网页中显示(默认值)
    • _blank在新的窗口中打开

锚链接:

  1. 一个完整的URL由 schme+host+path+query+hash组成,hash就是在页面不变的情况下,对于浏览数据的位置变动
  2. 视音频标签:video标签是html自带的视频标签,有src、controls、autoplay、muted等属性,track标签是视频字幕标签;audio是自带的音频标签
  3. 页面标签:一个页面可以分header、main、aside和footer四个页面标签,article 和section也是两个对应的内容标签
  4. 列表标签:<ul> unorder-list ,<ol> order-list,<li> list-item
  5. 导航标签:<nav>
  6. 表格标签:<thead>表头,<tbody>表格内容,<tr> 表行,<th> 表头单元格,<td> 表格数据单元格;rowspan属性,跨越多行,colspan属性,跨越多列
  7. 表单标签:form;input的type有text,password,number,date,file等;textarea,多行文本框,rows cols属性指定文本框最大行列数
  8. 表单单选框,input的type=radio,name属性决定了哪些互斥;checkbox多选框,checked属性是否默认选中
  9. 下拉选择框,select,下来选择项,option
  10. link 加载预先资源
  11. microdata

根据内容选择合适的标签!

html中的换行是会带来文本分隔符的,这也是占据位置的

css简介

css,cascading style sheets,用来定义页面元素的样式,页面中使用css主要分为外联<link>、嵌入<style>和内联<p style=’’> </p>三种

css工作方式

选择器基础

:root 根选择器;*是通配选择器; #id,id选择器,一个id只能出现一次;.class,class类名选择器;属性选择器【】,常用于input;

1
2
3
4
5
[input='text']{
width=200px
}

<input type='text' />

选择器优先级

内联样式 > 内部样式 > 外部样式,在样式后加 !important 优先级最高

css优先级:!important > id | class > 标签 > *

CSS权重,极少用。内联样式 1000 ; id 100 ; class,属性,伪类 10 ;标签,伪元素 1 ; * 0

  1. 标签[属性],属性选择器,带有该属性的都被选择中,eg a[title] {}
    1. a [ href= ‘https://www.baidu.com'] {}
    2. ^= 搜寻以什么开头的值 a [ href ^= ‘https’] {}
    3. $= 搜寻以什么结尾的值 a [ href $= ‘.com’] {}
    4. *= 搜寻包含关键字的值 a [ href *= ‘baidu’] {}

选择器进阶

  1. 伪类选择器,对于普通选择器加以修饰

    a:visited{} 浏览过的;a:hover {} 悬浮在上面的;a:disabled,禁用的;a:focus,元素聚焦;

    a:first-child{} 选取第一个子元素;a:last-child{} 选取最后一个子元素;a:nth-child(4) {} 选取第四个子元素;a:nth-child(odd) {} 选取奇数项子元素;a:nth-child(even) {} 选取偶数项子元素;

  2. 选择器可彼此组合

    1. 后代组合的查找顺序是 从下至上,从右至左
    2. 直接组合 AB,满足A且满足B;
    3. 后代组合A B,满足A的所有B;
    4. 亲子组合,A>B,只选择A子元素的B
      • A B 和 A>B 的区别在于 子子元素 是否被选择
    5. .container + div 意思为 选取 同container父级 紧相邻 在其之后的 div元素
    6. .container ~ div 意思为 选取 同container父级 之后所有的div元素,即使中间有其他元素隔开

样式基础

css会自动继承父元素计算值,除非另外设置

min-width:用于浏览器宽度过小时,会将内容自动换行,设置min-width可以保证 浏览器宽度过窄时 不换行,有横向滚动条出现

overflow:常用于模型溢出,hidden、scroll、auto

字体相关:font-weight,normal、bold、lighter; fontsize:字体高度,宽度根据字体自动缩放;font-style 设置斜体;font-family设置字体类

边框:border: 1px solid #000 宽度、样式、颜色,transparent 透明,也是颜色的一种

文本水平居中:text-align:center;文本行高,line-height:默认22px;文本垂直居中,可以将文本行高设置为盒子的高度

输入距离,text-indent:2em,设置开始输入位置有两个空格;取消文本下划线,text-decoration: none

光标手势,cursor:pointer,not-allowed

display:inline(无边距无换行)、inline-block(有边距 无换行)、block(有边距,有换行)很重要

隐藏元素: visibility: hidden 保存位置;display:none不保留位置

行内块元素和行内元素文本对齐,vertical-align垂直方向对齐,top,middle,~px

background-colorbackground-imagebackground-repeat repeat默认,no-repeat;background-attachment 背景图片是否可滑动,默认scroll,fixed;background-positionbackground-size cover占满,多的裁掉,contain 自身宽高比不变,保证图片全显示,多的部分该留白留白;

background综合写法,color img repeat attachment position/ size

伪类和伪元素,一个冒号伪类,两个冒号即伪元素,没有太大的区别。::before 和 ::after必须加content属性,否则不生效;

如何画三角形

边框内部宽高设为0,边框三边取透明,只留一边即为三角形

image-20221123212615725

单行文本截断和显示省略号

white-space:nowrap 不换行;overflow:hidden;text-overflow:ellipsis 隐藏部分加省略号

兼容性写法

-moz-box-sizing firefox;

-webkit-box-sizing chrome safari;

-o-box-sizing presto opera;

-ms-box-sizing IE8以下;

盒模型基础

盒子有块级盒子和行级盒子之分

  • 块级盒子block 不和其他盒子并列摆放
  • 行级盒子 inline 和其他行盒放在一行或拆开成多行,盒模型中的width、height不适用于行盒
  1. 块级元素生成块盒,body、article、div、main、section、h1-6…
  2. 行级元素生成行盒,span、em、strong、cite、code….

display属性分为block、inline和inline-block,其中inline-block被放在行盒,但可以设置宽度,且不被拆分

盒子阴影: box-shadow: 水平偏移,垂直偏移,模糊距离,阴影距离,inset向内加阴影。这里是先水平后垂直,和margin的先上下,后左右不一样

盒子圆角,border-radius 可以设置百分比和px;当盒子内部元素溢出,记得使用 overflow: hidden

overflow属性设置content溢出部分, visible可见、hidden不可见、scroll滑动可见

box-sizing设置元素的宽高度是否包含内外边距

  1. content-box 是默认值,不包含内外边距
  2. 为border-box时,长宽则会是包含margin和padding的大小,内容区实际宽度为 width 减去 (border + padding)

background-clip设置背景生效位置;border-box全盒生效;padding-box,内边距和内容生效;content-box,仅在内容生效

行内元素垂直对齐方式,利用表格元素table+vertical-align实现,父级给table,子级给table-cell,vertical-align: middle

margin塌陷

margin塌陷:margin 外边距在垂直方向上会采取margin collapse策略,即不合并,而取最大值;常见的如 子盒子想在父盒子垂直居中,设置margin,不会起作用,反而会让父级盒子跟着下移

解决办法:把父级盒子变成BFC

触发办法:

浮动元素, float 属性设置除 none 以外的值;
定位元素, position 属性设置绝对定位 absolute 或固定定位 fixed;
display 属性设置为其中之一的值 inline-block, table-cell, table-caption;
overflow 属性除 visible 以外的值, 即 hidden,auto,scroll;

如何设置内部盒子居中

外部盒子设置padding,内部盒子宽高都100%即可

css单位

image-20220922215343554
  1. px是绝对单位;em是相对单元,基于目前容器的大小进行设定(默认16px,文本字符默认21px),可以理解为倍数

  2. rem和em类似,但是它是基于root的html元素的大小设定去计算

  3. vw & vh,全程是 viewport width和viewport height,即用户看到的窗口,值在0 ~100之间

  4. vmin & vmax,vmin代表屏幕较短的一边,vmax代表屏幕较长的一边,概念与vw vh类似,常用于手机旋转屏设备

css布局方式

  1. 常规流:行级、块级、flexbox、grid布局;2. 浮动流;3. 绝对定位

常规流 normal flow

除了根元素、浮动元素和绝对定位元素不在,其他都在常规流之内

行级排版上下文,Inline Formatting Context IFC,水平排版

块级排版上下文,Block Formatting Context BFC

flexible box

  1. display: flex 生成块级flex容器;display: inline-flex 生成行级flex容器;

  2. flex-diretion控制流向,默认是从左到右,flex-diretion的方向称为主轴,与其垂直的方向称为侧轴,默认是row

    • row,column,row-reverse,column-reverse
  3. justify-content 控制元素主轴方向排版

    • flex-start、flex-end、center、space-between、space-around、space-evenly
  4. align-items 控制元素侧轴方向排版

    • flex-start、flex-end、center、stretch、baseline
  5. align-content 控制 **多行内容 **在侧轴方向排版

    • flex-start、flex-end、center、stretch(默认值,拉伸到父容器)、baseline(元素统一基准线)
  6. flex-wrap 控制是否允许换行,默认是nowrap不换行

  7. flex-flow 是flex-direction和flex-wrap的缩写,flex-flow:row nowrap

  8. flex容器有收缩的能力

    • flex = flex-grow + flex-shrink + flex-basis
    • flex-grow 控制有剩余空间时flex-item的放大比例;0表示默认大小
    • flex-shrink 控制空间不足时flex-item的缩小比例;
    • flex-basis 主轴方向的基础长度,auto刚好包裹元素
  9. order 控制 DOM元素出现顺序,从小到大,默认都是0

  10. align-self 控制覆写单个元素 的侧轴排版

    • flex-start、flex-end、center、stretch、baseline

grid box

flex将元素按照单向进行摆放,grid则可以将元素按照两个方向进行摆放

  1. display:grid生成块级容器;grid-template将容器划分为网格,再设置每一个子项占据哪些行、列
  2. grid-template-columns 控制网格列划分,grid-template-rows 控制网格行划分
    • grid-template-columns: 100px 100px 100px,也可以写为repeat(3,100px)
    • grid-template-columns: 30% 30% auto
    • grid-template-columns: 100px 1fr 1fr 说明:fr,fraction 份数,即剩下的均分比例
  3. grid-area 控制元素在网格中的位置,grid-area:1/1/3/3 左上角1,1和右下角3,3
  4. grid-areas 控制各个网格的名字,grid-areas: ‘header header header header’,repeat不适用于此
  5. grid-gap 控制网格间距
  6. justify-items 控制元素在网格内水平方向上的布局;align-items 控制垂直方向上的布局
  7. align-self,justify-self 控制单个元素的布局
  8. justify-content 控制水平方向 网格总体布局,align-content 控制垂直方向 网格总体布局

浮动流 float

浮动元素脱离常规流,漂浮在容器左边或右边,是inline-block;浮动元素后面的行盒会变短以避开浮动元素

内联、内联块、浮动、溢出隐藏、纯文本都可以识别浮动元素的位置,但是块级元素无法识别

clear 属性控制元素受不受浮动元素影响

清除浮动:(如果父元素里的子元素设置了浮动,那么父元素的高就不会自动被撑开的,也就没有高度值),此时就需要清除浮动

1
2
3
4
5
.clearfix::after{
content: "";
display: block;
clear: both;
}

绝对定位 position

  1. position有四个取值:
    • static、默认值,非定位元素;
    • relative,相对自身原本位置偏移,原来的位置不腾出,不脱离文档流;
    • absolute,相对于最近的 relative或absolute进行绝对定位,腾出原来的位置,脱离文档流;
    • fixed,相对于浏览器窗口的绝对定位,会变成 内联块元素,需要额外赋宽度
  2. 使用top、left、bottom、right设置偏移长度,流内其他元素当它没有偏移一样进行布局,即使会有遮挡
  3. position:absolute,脱离常规流,不影响流内元素布局
  4. position:fixed,相对于窗口定位,不随页面滚动而变化,常用于导航栏

堆叠层级

z-index,数值越大,越靠近用户

css transform 变形

对元素进行平移 translate、旋转 rotate、缩放 scale、倾斜 skew,transform不会对其他元素布局产生影响

  • transform :translate(100px, 100px)
  • transform :rotate(90deg) 顺时针旋转90度

css transition 过渡

transition-property 需要过渡效果的属性;transition-duration 过渡耗时;transition-timing-funtion 过渡效果;transition-delay 过渡延迟

  • transition:height 500ms linear 1s
  • transition-timing-funtion 有以下几种:linear、ease、ease-in、ease-out、ease-in-out、steps(4)

动画效果

css animation 动画

响应式设计,同一个页面适应不同屏幕大小设备的方案

  1. 设置viewport,<meta name=’viewport’ content=’width=device-width’, initial-scale=1.0’>
  2. contain 和 cover 的区别:contain,不造成裁剪,可能有留白;cover,可能会造成裁剪,不留白
  3. media query 针对不同屏幕,应用不同样式,@media screen and (min-width:480px){ } 在至少480px的屏幕上才使用该样式
    • 可以查询的media,width/
    • height,device-width/device-height,device-pixel-ratio、orientation

@keyframes

通过 @keyframes 规则创建动画。原理是将一套 CSS 样式逐渐变化为另一套样式。在动画过程中,可以多次改变这套 CSS 样式。

以百分比来规定改变发生的时间,或者通过关键词 “from” 和 “to”,等价于 0% 和 100%。

1
@keyframes animationname {keyframes-selector {css-styles;}}
描述
animationname 必需。定义动画的名称。
keyframes-selector 必需。动画时长的百分比。合法的值:0-100%from(与 0% 相同)to(与 100% 相同)
css-styles 必需。一个或多个合法的 CSS 样式属性。

处理css兼容性

了解浏览器支持情况

浏览器hack层叠原理

  1. 同一个属性,后书写的值会覆盖前面书写的值
  2. 对浏览器无效的属性会被忽略

JavaScript基础

基础知识

ECMA

ECMA, 欧洲计算机制造联合会,评估、开发计算机各项标准

其中ECMA -262是脚本语言的规范,ECMAScript

ES5,ES6都是从这里来的

其他知识

字节码

ASCII码, 表1 0-127 表2 128-255 ,1个字节;UNICODE码 涵盖ASCII码,2个字节

charCodeAt() > 255,检查每个字符是否大于255,大于返回两个字节,小于等于返回一个字节

JS单线程引擎

JS引擎,是单线程,但是可以通过轮转时间片来模拟多线程

动态语言 -> 脚本语言 -> 解释型语言 -> 弱类型语言

静态语言 -> 编译型语言 -> 强类型语言

堆内存和栈内存

JS变量都存放在内存中,而内存给变量开辟了两块区域,分别为栈区域和堆区域

栈像个容器,容量小速度快
堆像个房间,容量较大

是一种 先进后出 的数据结构,存取速度快,但不灵活,变量使用完成后就可以将其释放,内存回收容易实现。

堆内存,使用灵活,可以动态增加或删除空间,但是存取比较慢,存储变量时没有什么规律可言。

基本类型存放在栈内存,引用类型存放在堆内存中的,但是引用类型的引用还是存在栈内存

  • 基本类型 声明一个变量,多次赋值就会取取最后一个值
    基本类型 可以直接复制,复制之后的内容和原内容没有什么联系,类似于开辟了一个新的空间

    基本类型 不能添加属性或者方法

  • 引用类型 直接赋值给另一个变量以后相互之间的修改会互相影响对方,进而引出浅拷贝与深拷贝的问题

基本语法

原始值,五种基本类型

Number、String、Boolean、undefined、null

  • JS的Number包含了浮点和整数型这些,根据具体的值来分配具体的类型,即为弱类型语言
  • 原始值没有自己的方法和属性,但是Boolean、Number、String有自己的包装类,undefined和null没有
  • 原始值初始化赋值是发生在栈内存stack中,实际内容确实存放在栈内存中

引用值

object、array、function、date、RegExp

  • 引用值有自己的方法和属性,如 length、push等
  • 引用值初始化赋值发生在堆内存heap中,实际内存存放在堆内存中,但将内存地址存放在栈内存中
1
2
3
4
5
6
7
1. {} == {}  return false
// 对象是引用值,引用值对比的是地址,两个空对象存储在不同的地址,自然就不相等

2. 如何让两个对象相等
var obj = {}
obj1 = obj
obj1 == obj //return true

返回类型typeof

typeof() 可以反映出的类型有:number string boolean undefined object function

  • object 在这里理解为 引用类型,具体包括对象Object {} 、数组Array []、null(历史遗留问题)
1
2
3
4
5
 function test(){
console.log(typeof(a)) //return 字符串的'undefined',返回为真
console.log(a) //直接报错,因为a没有被定义
}
test()

加减乘除

  • 任何数据类型 + 字符串 都是字符串

  • NaN,not a number,非数,是一个数字类型,用于一些没有意义的情况

    • Number(undefined)、Number(null) 得到的都是NaN
  • < > <= >= === != !==

  • 逻辑运算

    • undefined、null、NaN、“”、0、false,除上述以外全部都是真
    • console.log(1 && 2 && undefined && 10)
      • return undefined
      • 遇到真就往后走,遇到假或走到最后就返回当前的值
    • console.log(1 || 2 || undefined || 10)
      • return 1
      • 遇到假就往后走,遇到真或走到最后就返回当前的值

类型转换

显式类型转换
  • Number,转数字类型;parseInt,转整数类型(向下取整);parseFloat,转浮点类型(四舍五入);String,转字符串;Boolean(),转布尔
  • toFixed(2),取小数点两位
  • parseInt(),从前往后看整数
1
2
3
4
parseInt('123a')  return 123
parseInt('12a123') return 12

"89" > '9' return false 字符串比较大小 从第一位的ASCII码开始
隐式类型转换
  • === 不进行隐式转换
  • 加减乘除 > < >= <= ++ – ,都会做隐式转number
  • NaN == NaN,返回 false,NaN不等于任何东西
  • undefined 不大于零 不等于零 不小于零;null 不大于零 不等于零 不小于零;故 undefined == null ,return true
  • isNaN有隐式类型转换,new Number()
1
2
3
4
isNaN('123')  return false
undefined == null return true
undefined === null return false
parseInt('123a') == null return false

函数

  • 函数在传递参数时,多传递的参数会被忽视,少传递的参数会被视为 undefined

  • 实参和形参存储在不同的地方,数量匹配的 会存在一一对应的绑定关系;不匹配的,多传递或少传递的,不会有绑定关系

    • 实参和形参在取值时,默认取 不是undefined的值
    • 每一个函数的形参都是函数的临时变量
  • 函数本身是有length的,test.length 返回实参的长度

  • 函数默认返回值undefined

  • 实参求和

1
2
3
4
5
6
7
8
9
function sum(){
var a = 0;
for(var i = 0; i < arguments.length; i++){
​ a += arguments[i]
​ }
return a
}

sum(1,2,3,4,5,6,7,8, .....)
  • 实参是可被修改的
1
2
3
4
5
6
7
8
funtion a(x,y,z){
z = 10;
console.log(arguments[2])
}
a(1,2,3)

// return 10
// reason undefined -> 3 -> 10

列表

  • arr.push 增
  • arr.splice(序号,需要删去的数量)
  • arr.indexOf(值) 根据值来寻找index

对象

  • 三种声明方式,var arr = {} 对象字面量;var arr = new Object() ;var arr = Object() ; 所有对象都继承于 Object.prototype
  • 对象里的函数 叫 方法 method;在对象中,this就指代对象主体
  • 字面量声明 var obj = { } ;
1
2
3
4
5
6
7
8
var teacher = {
name: 'cyc',
weight: 130,
eat: function(){
this.weight--
}
}
teacher.name teacher['name'] 两个效果一样

数组

三种声明方式,var arr = [] 数组字面量;var arr = new Array() 不推荐;var arr = Array() 用得少; 所有数组都继承于 Array.prototype

数组内最后一位如果是 逗号, 系统会忽略,其余位置的逗号会认为 是empty元素

修改原数组的方法
  • 修改原数组的方法,push、unshift,pop,shift,splice,sort
  • push在最后加元素,unshift在最前面加元素,这两个方法都是继承Array原型的方法
  • push、unshift方法的返回值 是执行方法之后的数组长度

push方法的实现原理

1
2
3
4
Array.prototype.push = function(elem){
this[this.length] = elem;
this.length ++;
}
  • pop 方法 剪切数组最后一位 并返回出来;shift方法 剪切数组第一位 并返回出来

  • reverse方法 ,倒序数组

  • splice方法:arr.splice(开始项的下标,剪切长度,在剪切后的位置添加新数据)

  • sort,返回排序后的数组结果,默认按照ASCII码进行升序排序

    1
    2
    var arr = [27, 49, 5, 7]
    console.log(arr.sort()) //return [27, 49, 5, 7]
    • 可以通过设置系统内置方法(a 与 b进行比较,小于就不动,大于就交换位置)来达到不同的排序效果
    • 按照数字大小进行升序排序
    1
    2
    3
    4
    5
    6
    7
    arr.sort(function(a, b){
    return a-b; // 返回升序排序
    return b-a; // 返回降序排序

    var rand = Math.random();
    return rand-0.5; // 打乱一个数组
    }
    • 根据字符串长度排序
    1
    2
    3
    4
    5
    6
    7
    8
    var arr = ['12312','123','1231','1231231']
    arr.sort(function(a,b){
    if(a.length > b.length){
    return 1;
    } else{
    return -1;
    }
    })
新建一个数组的方法

concat,toString(数组转字符串),slice

  • arr.slice(开始项的下标,截止项的下标),要区分开slice和splice
  • arr.join(),把数组内的元素取出来用参数进行连接,不填参数默认用,逗号隔开
  • arr.split(‘分割参数‘ ,数组长度),把字符串用分割参数分割后放入数组,和join对应使用
ES5新增方法

5个迭代方法(循环操作数组中的各个项):forEach(),map(),filter(),every()和some()
2个归并方法(迭代数组所有项,最终返回一个值):reduce()和reduceRight()
2个索引方法:indexOf() 和 lastIndexOf();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// forEach: 遍历数组,参数为一个回调函数
// e 当前元素 ; index当前元素索引值 ; array整个数组,第二个参数改变this指向
.forEach(function(e,index,arr){})

// map: 返回一个被操作后的新数组,不会改变原数组
// 新数组的元素值是每次函数return的返回值 ; 不写return,接收的新数组的元素值将全为空
var b = [1,2,3].map(function(x){
return x*x; //b得值是[1,4,9]
});

// filter: 过滤不符合条件的元素,返回true则保留,返回false则过滤掉
var brr = [1,2,3].filter(function(item)){
return item%2; //返回值为奇数的元素
}

//some: 判断数组内是否有指定元素,如果只要有一个则返回true,如果一个都没有则返回false
var brr = [20,13,11,8,0,11].some(function(item){
return item>10;
}) // true

// every: 判断数组内是否都有指定元素,如果全部都有则返回true
var brr = [20,13,11,8,0,11].some(function(item){
return item>10;
}) // false

reduce: 为数组中的每一个元素依次执行回调函数

回调函数四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,原始数组

  • 如果没有提供initialValue,reduce 从索引1的地方开始执行 ,跳过第一个索引
  • 如果提供initialValue,从索引0开始
  • 空数组使用reduce不提供初始值,会报错,因为没有索引1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
})
1 2 1
3 3 2
6 4 3

var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0)

0 1 0
1 2 1
3 3 2
6 4 3

reduceRight()
reduceRight()和reduce()类似,只是执行方向不同,reduceRight()从数组的末尾往前递归

indexOf()
跟字符串查找一样,查找指定元素是否存在,如果存在,返回下标,如果不存在返回-1

lastIndexOf()
lastIndexOf()是从最后一个查找,如果存在,返回下标,如果不存在返回-1

类数组

  • 类数组是结构与数组十分相似,但没有数组那么丰富的内建方法,通常类数组可能还拥有一些别的属性。
  • 类数组一定要有 数组形式下标 的值 和 length属性,length属性值为数组形式下标值数量,其他的属性数量不计入
  • JavaScript 常见的类数组:arguments,类数组的原型继承于Object,不继承Array,所以很多array原生方法用不了,需要额外添加
1
2
3
4
5
6
7
8
9
var obj = {
'2':3,
'3':4,
'length':2,
'push':Array.prototype.push
}
obj.push(1);
obj.push(2); // 返回4 因为obj.length为4
console.log(obj) // return {2: 1,3: 2,length: 4,push: f...} 结合push的实现方法来思考

进阶知识

预编译

  • 预编译过程中 只看 有无变量声明和函数声明,最后再执行(if,while都算执行里)
  • 暗示全局变量 imply global,任何变量如果变量未经声明赋值,此变量就为全局对象(window)所有;window是一个全局作用域对象
  • 函数未被实例化前,this指向window,this.a=1 实际上为 a=1,都保存在全局中
  • GO global object,全局上下文 的预编译流程
    1. 找变量声明,初始都为undefined
    2. 找函数声明
    3. 执行 -> 该赋值就赋值 该执行就执行
  • AO 函数上下文的预编译流程
    1. 寻找形参和变量声明,初始都为undefined,this 指向window
    2. 实参值赋值给形参
    3. 找函数声明、赋值
    4. 执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var x = 1;
y = z = 0;

function add(n){
return n += 1
}
y = add(x);

function add(n){
return n += 3
}
z = add(x);

console.log(x,y,z);
# return 1,4,4
# reason: 预编译过程中add函数会被后面的覆盖

作用域和作用域链

  • 作用域:限定这个变量的可用性的代码范围就是这个变量的作用域。
  • 作用域链(scope chain):由多个作用域对象连续引用形成的链式结构。
  • 函数在被定义的时候,生成自己的作用域和作用域链[[scope]],放入自己的GO;在被执行的时候,生成AO;自己的AO排在GO前面

闭包

  • return 返回函数的时候,会将当前AO抛出去,变成GO里的全局变量,从而产生闭包,
  • 当内部函数被返回到外部并保存时,一定会产生闭包
  • 产生闭包后,在当前AO定义的变量,称为该函数的私有变量,其他地方不能访问
  • 闭包会产生原来的作用域链不释放,过度的闭包可能会导致内存泄漏或加载过慢
  • window. = function(…) window也可以产生闭包的效果

立即执行函数

  • IIFE,immediately-invoked function,也可被称为初始化函数
  • 立即自动执行,执行一次完成后立即释放,不需要去主动调用独立作用域,常被用于开发模块
  • 立即执行函数 + 构造函数 + window闭包,非常适合写js插件
  • 写法如下: 匿名函数+执行小括号;括号包起来的任何东西都变成表达式
1
2
3
;(function(a,b){
return a+b
})(1,2);
  • 函数后加括号 不执行函数!
1
2
3
4
5
function test(x){
console.log(arguments);
return x;
}(1,2,3,4,5)
// return 5

函数声明变成表达式的方法

  • 函数变成表达式后,函数名即被忽略;一定是表达式 才能被执行符号()执行
  • 在函数前加上 + - ! || && 等可以让函数变成表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function test(){
var arr = []
for( i = 0; i<10; i++){
arr[i] = function(){
console.log(i)
}
}
return arr;
}

var myArr = test();
for(var j = 0; j<10; j++){
myArr[j]();
}
# return 打印出来 1010
# reason: i<10时,function里面的内容都被忽略,i=10时,arr被返回出全局,产生闭包,此时的i为全局变量,且等于10,故打印的全部都是10

逗号分隔符

逗号分隔符, 返回最后一个值

1
2
3
var num = (1,2,3,4,5)
console.log(num)
// return 5
1
2
3
4
5
6
7
8
9
10
var fn= (
function test1(){
return 1;
}
function test2(){
return '2';
}
);
console.log(typeof(fn));
// return function
1
2
3
4
5
6
7
8
9
10
var fn= (
function test1(){
return 1;
}
function test2(){
return '2';
}
)();
console.log(typeof(fn));
// return string
1
2
3
4
5
6
7
var a = 10
if(function b(){}){
a += typeof(b);
}
console.log(a)
// return 10undefined
// reason:(function b(){})通过,且忽视函数名,b未被定义,即undefined

构造函数实例化

  • 在实例化之前,构造函数就是一个函数
  • 构造函数上的方法指向构造函数,并不指向实例对象;构造函数原型上的方法指向实例本身
  • 函数默认返回值是undefined,构造函数实例化后默认返回值是this,this后跟函数会产生隐式闭包
  • 实例化后,原本指向window的this 指向 一个实例化新对象
1
2
3
4
5
AO = {
this: {
__proto__: 函数名.prototype
}
}
  • 实例化后,相当于创建了一个对象object,this指向这个实例化对象,不指向构造函数
1
2
3
4
5
6
7
8
9
10
11
function Teacher(opt){
this.name = opt.name;
this.eat = function(){
...
// 隐式做了这件事情,实例化后产生一个全局闭包
// return this
}
}
var teacher = new Teacher({
name:'123'
});

包装类

  • 为了便于操作基本类型值,JS 提供了3个特殊的引用类型:Boolean、Number、String(undefined和null没有自己的包装类)
  • 每当读取一个基本类型值的时候,引擎就会创建一个对应的基本包装类型的对象”,能够调用一些方法来操作这些数据。
  • 包装类 只能访问,不能做保存,如果需要保存,就使用实例化生成对象
1
2
3
4
5
6
7
8
9
var name = '123123123'   
name += 10; //12312312310
var type = typeof(name); //string
if(type.length === 6){ //true
type.text = 'string' // new String(type).text = 'string' delete
}
console.log(type.text) // undefined
// return undefined
// reason: 包装类 只能访问,不能做保存

原型,prototype

  • constructor —> 构造函数;prototype —> 原型;_proto_ —> 原型容器
  • prototype 是属于每个实例化对象,并不属于构造函数,_proto_ 是每个实例化对象的原型的容器
  • prototype 定义构造函数 构造出的每个对象的公共祖先,每个实例化对象都可以继承原型上的属性和方法
  • 写死的值和方法 写到 prototype,需要传参的 写到构造函数里,传参值的优先级高于prototype
  • 实例化生成的对象一般无法对 原型 进行 增加改, 通过 ._proto_ 这种方式可以

原型链

  • 沿着__proto__一直向上找
  • 子类可以修改父类的 引用值,不能修改父类的原始值;子类虽然不能修改父类的原始值,但是可以copy一份父类原始值后再修改自己
  • 原型链的顶端是 Object.prototype,不是Object
  • Object.create( 填对象或者null) ,可以自定义原型 prototype

原型方法的重写

call/apply

  • call/apply可以更改 构造函数的this指向,只能借用指定的属性,不能继承上级全部的属性和方法
  • 两者区别是 传参不同,call(this, 1, 2, 3, …) ; apply(this, [1,2,3,…])

通过 call(),可以使用属于另一个对象的方法

1
2
3
4
5
6
7
8
9
10
var person = {
fullName: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"Bill",
lastName: "Gates"
}
person.fullName.call(person1, "Seattle", "USA");

圣杯模式

原型并不可以随便赋值,因为原型是引用对象,随便赋值会导致 子类轻易修改父类原型,如果需要 引用父类原型加以修改 且不产生影响,则可以引入第三个空构造函数实例化后做中转使用

1
2
3
4
5
6
7
8
9
10
11
12
//企业级写法!不会污染全局变量  Origin被继承 Target欲继承 
var inherit = (function(Target,Origin){
function Buffer(){}
return function(Target,Origin){
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
//还原构造器
Target.prototype.constructor = Target;
//说明继承源
Target.prototype.super_class = Origin;
}
})();

链式操作

方法里每次最后都return this,可以达到连续调用的效果

1
2
3
4
5
6
7
8
9
10
11
var sched = {
morning: function(){
...
return this
},
evening: function(){
...
return this
}
}
sched.morning().evening()

插件的写法

1
2
3
4
5
6
7
8
9
;(function(){
var Test = function(){

}
Test.prototype = {

}
window.Test = Test
})();

克隆/浅拷贝/深拷贝

  • 克隆,只拷贝的地址,var a = b =

  • 浅拷贝,只拷贝第一层属性信息,深层信息如一个变,其他跟着变

    • 对于引用值,浅复制仅仅复制引用值的存放地址,而不复制实际的存放对象。
1
2
3
4
5
6
7
8
9
function clone(origin, target){
var tar = target || {};
for (var key in origin){
if (origin.hasOwnProperty(key)){
tar[key] = origin[key]
}
}
return tar;
}
  • 深拷贝,全部完整的拷贝下来,深复制把要复制的对象所引用的对象都复制了一遍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function deepClone(origin, target){
var target = target || {},
toStr = Object.prototype.toString,
arrType = '[object Array]';

for (var key in origin){
if (origin.hasOwnProperty(key)){
if(typeof(origin[key] === 'object' && origin[key] !== null){
toStr.call(origin[key]) === arrType ? target[key] = [] : target[key] = {};
deepClone(origin[key], target[key]);
} else {
target[key] = origin[key];
})
}
}
return tar;
}

this指向

一般原则:谁调用this的宿主,this就指向谁

全局this

  • this 是JavaScript关键字,是当前环境执行期上下文对象的一个属性,在不同环境下表现不同
  • 全局作用域下的this 和window的关系是相同的
  • 不同环境下的全局对象获取方式并不相同
    • web:window、self、frames、this
    • node:global
    • web workers:self
    • 通用获取方式:globalThis (Web Worker为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程)
  • 严格模式下,谁调用函数,函数内部的执行默认就是谁
1
console.log(this === window ) // return true

class this

类中的this都是指向的这个类,也可以说是指向的通过类实例化的对象

call、apply、bind

call、apply是立即执行、bind是返回一个新的函数

1
2
// test.bind 只会生效一次,下式等同于  t = test.bind(obj)
t = test.bind(obj).bind(obj2)

箭头函数

  • 箭头函数严格模式下this也指向window
  • 箭头函数 忽略任何形式(call、apply、bind)的this指向改变,静态this指向
  • 箭头函数 不是个 构造器,不能new
  • 箭头函数中的this找上层非箭头函数的作用域,不是谁绑定就指向谁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'use strict';
const test = () =>{
console.log(this) // return window
}
function test1(){
console.log(this) // return undefined
}
test();
test1();

var obj = {
a:3,
b:4,
test1: function(){
console.log(this.a) // return 3
},
test2: () => {
console.log(this.a) // return undefined
}
}

对象

  • 对象方法内部的this 指向 最近一层引用
1
2
3
4
5
6
7
8
9
10
var obj = {
a: 1,
test: function(){
console.log(this.a); // return 1
function t(){
// t函数 最近的引用并不是 obj,不属于任何容器
console.log(this.a); // return undefined
}
}
}

defineProperty

this指向调用对象

函数 和 构造函数

构造函数里 默认隐式返回this,如果手动返回了一个新对象,this指向的对象就被忽略了(不生效)

1
2
3
4
5
function Test(){
console.log(this)
}
Test() // return window
new Test() // return Test{}

事件处理函数

事件处理函数内部的this总是指向被绑定的DOM元素,比如 谁调用onclick,函数就绑定哪个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
;(function(doc){
var oBtn = doc.getElementById('..');
function Plus(a, b){
this.a = a;
this.b = b;

this.init();
}
Plus.prototype.init = function (){
this.bindEvent();
}
// 第1种情况,会出问题
Plus.prototype.bindEvent = function (){
oBtn.addEventListener('click',this.handleBtnClick,false);
}

Plus.prototype.handleBtnClick = function (){
console.log(this) // return <button....>
console.log(this.a,this.b); // return undefined undefined
}

// 第2种情况,正确
Plus.prototype.bindEvent = function (){
//把plus实例绑入handleBtnClick中
oBtn.addEventListener('click',this.handleBtnClick.bind(this),false);
}

Plus.prototype.handleBtnClick = function (){
console.log(this.a,this.b); // return 3 4
}

// 第2种情况,正确
Plus.prototype.bindEvent = function (){
var _self = this;
//把plus实例绑入handleBtnClick中
oBtn.addEventListener('click',_self.handleBtnClick(),false);
}

Plus.prototype.handleBtnClick = function (){
console.log(this.a,this.b); // return 3 4
}

window.Plus = Plus;

})(document)

new Plus(3,4)

几个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var name = '222';
var a = {
name : '111',
say: function(){
console.log(this.name);
}
}
var fun = a.say; // var fun = function(){ console.log(this.name);}
fun(); // 222
a.say(); // 111
var b = {
name: '333',
say: function(fun){
fun();
}
}
b.say(a.say); // 222 这个地方要多注意!
b.say = a.say; // 333
b.say()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Foo(){
getName = function(){
console.log(1);
}
return this;
}
Foo.getName = function(){
console.log(2);
}
Foo.prototype.getName = function(){
console.log(3);
}
var getName = function(){
console.log(4);
}
function getName(){
console.log(5);
}

Foo.getName(); // 2 Foo.getName()理解为Foo对象的一个属性,并不涉及Foo内部
getName(); // 4 undefined -> console.log(5); -> console.log(4);
Foo().getName(); // 1 Foo()执行后,内部的getName被提升至全局,全局的console.log(4) -> console.log(1)
getName(); // 1 Foo()执行后,内部的getName被提升至全局,全局的console.log(4) -> console.log(1)
new Foo.getName(); // 2 Foo.getName()理解为Foo对象的一个属性,并不涉及Foo内部
new Foo().getName(); // 3 实例化Foo()后,找Foo内部的this.getName,但是找不到,因为getName是window的,只能去原型里找,故返回3
new new Foo().getName(); // 3 原因同上

常用的函数

hasOwnProperty

找对象自身的属性,排除原型上的所有属性,返回布尔值

检查数据类型的三种方法

  • A instanceof B,A对象的原型链上到底有无B原型
  • 第三种是最实用的
1
2
3
4
5
var a = []

1. a.construtor
2. a instanceof Array
3. Object.prototype.toString.call(a); return [object Array]

defineProperty

Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

1
Object.defineProperty(obj, prop, descriptor)
  • obj 需要定义属性的对象。
  • prop 需被定义或修改的属性名。
  • descriptor 需被定义或修改的属性的描述符。

数据描述符和存取描述符均具有以下可选键值:

  • configurable: 仅当该属性的 configurable 为 true 时,该属性才能够被改变,也能够被删除。默认为 false
  • enumerable: 仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false
  • value: 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
  • writable: 仅当仅当该属性的writable为 true 时,该属性才能被赋值运算符改变。默认为 false
  • get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。undefined
  • set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为undefined

callee 和 caller

caller 直接翻译为调用者,callee 翻译为被召者

  • arguments.callee返回当前正执行的函数

    • 适用于自启动函数中递归

      1
      2
      3
      4
      5
      6
      var sum =(function(n){
      if(n<1){
      return 1;
      }
      return n + arguments.callee(n-1)
      })(10);
  • caller 指向调用当前函数的函数。

    1
    2
    3
    4
    5
    6
    7
    function myq() {
    if (myq.caller === null) {
    console.log("该函数在全局作用域内被调用!");
    } else console.log("调用我的是函数是" + myq.caller);
    }
    myq();
    // return 该函数在全局作用域内被调用!
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function myq() {
    if (myq.caller == null) {
    console.log("该函数在全局作用域内被调用!");
    } else console.log("调用我的是函数是" + myq.caller);
    }
    function callQ() {
    myq();
    }
    callQ();
    /* return
    调用我的是函数是function callQ() {
    myq()
    }
    */

eval

eval() 函数计算或执行参数,如果参数是表达式,则 eval() 计算表达式。如果参数是一个或多个 JavaScript 语句,则 eval() 执行这些语句

1
2
3
4
5
6
7
var x = 10;
var y = 20;
var a = eval("x * y") + "<br>";
var b = eval("2 + 2") + "<br>";
var c = eval("x + 17") + "<br>";

var res = a + b + c; // return 200 4 27

DOM

  • DOM:document object model 文档对象模型,通过浏览器提供的这一套方法去表示和操作HTML和XML,XML(标签自定义) –> XHTML –> HTML(标签由浏览器定义)
  • js本身无法直接操作CSS样式的,但是可以通过设置元素的style内联样式,达到覆盖原样式生成新样式的效果

document方法

  • document 是整个html文档的父级节点,html文档没有父级元素;document 的父级元素规定为null,document 实质上是一个对象
    • document下属get方法一般获取的是 类数组
    • 在IE8及以下,document下属方法参数不区分大小写,要注意兼容性!
    • id属性一般对接后端使用,能不用就不用
  • document.getElementsByTagName
  • document.getElementsByClassName,不兼容IE8及以下
  • querySelector、querySelectorAll,兼容IE7及以上
    • 这两个方法的参数使用 和 css 选择器一样;querySelector(),返回第一个符合的;querySelectorAll(),返回满足条件的类数组
    • 一般不使用,因为性能不好,和不实时性

DOM结构树

  • Document下属HTMLDocument和XMLDocument,Element同理
  • 节点不是元素,节点包含元素,元素的名字为元素节点,也可被称为DOM元素;document的原型是 HTMLDocument,HTMLDocument的原型是 Document

image-20221012215124687

节点分类和属性

  • 元素节点 —> 1 ; 属性节点 —> 2 ; 文本节点 —> 3,换行也算文本节点

    注释节点 —> 8 ; document —> 9 ;DocumentFragment —> 11

  • nodeName,只读不可改 ; nodeValue,可读可改 ; nodeType,1 2 3 8 9 11; attributes、getAttributeNode、可读可改

遍历节点树的方法

  • parentNodes、childNodes
  • firstChild、lastChild
  • nextSibling、previousSibling

遍历元素节点的方法

  • parentElement IE9及以下不支持

  • children IE7及以下不支持

  • ChildElementCount = children.length IE9及以下不支持

    firstElementChild、lastElementChild IE9及以下不支持

    nextElementSibling、previousElementSibling IE9及以下不支持

以上方法注意点

  • var div = document.getElementById(‘div’)[0],经历了两个过程,选元素,再实例化
  • getElementById()、getElementByName这两个方法,只有Dodument有,元素节点使用该方法会报错
  • getElementByTagName、getElementByClassName、querySelector、querySelectorAll 这四个方法Document和Element原型里都有
  • body和head的两种获取方式
    • var body = document.getElementsByTagName(‘body’)[0]
    • 特殊的常用方式:var body = document.head;
  • document.title 获取的是 title值,不是title元素
  • document.documentElement 获取 HTML元素

创建节点

  • appendChild 这是个节点 node方法,在父级元素的最下边添加,类似于push,appendChild 不是复制节点,而是剪切节点
1
2
var div = document.createElement('div'); //在堆内存里创建一个div对象,
document.body.appendChild(div) // 在节点树中添加该元素

插入节点

c.insertBefore(a, b),在父级c节点下的子节点b之前插入a节点

删除节点

  • 父删除子,父节点.removeChild(子节点),返回值是子节点;该方法并不是销毁该节点,而是从节点树中取出

  • 子自己删除自己,remove() ,直接销毁自己,返回undefined

节点替换

parent.replaceChild(new, origin)

增加节点内容

innerHTML 可以取值赋值追加值,可以加HTML字符串; innerText 可以取值赋值追加值,不可以加HTML字符串,因为字符实体

元素节点的方法

setAttribute(属性名,属性值) 给节点赋值属性

自定义属性

HTML5 给元素增加了一个data-*属性,两种方式访问

  1. 节点.dataset(*)方法去访问自定义属性
  2. 用getAttribute方法访问时,需要带上前面的data-

创建文档片段

DOM树节点每次有变动时,会引起页面回流,即重新计算几何数据再渲染。当频繁对DOM树进行操作时,极大占用了资源开销,此时就需要用到DocumentFragment

DocumentFragment不在节点树里,可将需要添加的元素节点放到DocumentFragment中,最后统一加到节点树中

浏览器相关设置

浏览器的怪异模式和标准模式

  • document.compatMode

  • 怪异模式,浏览器厂商开发,向后兼容规范,BackCompat

  • 标准模式,w3c指定兼容规范,CSS1Compat

    • <!DOCTYPE html> html 文件第一行代码如果是这个,即标准模式

查看滚动条的距离

image-20221015162934492

多平台兼容性写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
function getScrollOffset(){
if(window.pageXOffset){
return {
left: window.pageXOffset,
top: window.pageYOffset
}
} else {
return {
left: document.body.scrollLeft + document.documentElement.scrollLeft,
top: document.body.scrollTop + document.documentElement.scrollTop
}
}
}

浏览器可视区域

常规:window.innerWidth/ innerHeight

IE9及以下:

标准模式:document.documentElement.clientWidth/clientHeight

怪异模式:document.body.clientWidth/clientHeight

多平台兼容性写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getViewportSize(){
if(window.innerWidth){
return{
width: window.innerWidth,
height: window.innerHeight
}
} else {
if(document.compatMode === 'BackCompat'){
return{
width: document.body.clientWidth,
height: document.body.clientHeight
}
} else{
return{
width: document.documentElement.clientWidth,
height: document.body.clientHeight
}
}
}
}

整个页面的宽高

  • 整个页面的宽高 = document.body.scrollHeight/scrollWidth OR document.documentElement.scrollHeight/scrollWidth = window.innerWidth + window.pageXOffset

多平台兼容性写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
function getScrollSize(){
if(document.body.scrollHeight){
return {
width: document.body.scrollWidth,
height: document.body.scrollHeight
}
} else {
return {
width: document.documentElement.scrollWidth,
height: document.documentElement.scrollHeight
}
}
}

其他问题

  • offsetLeft,offsetTop 找上一个父级定位元素的距离

  • offsetParent,返回上一个父级定位元素

  • 返回元素到左上角的距离(不管是否有定位元素)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function getElementDocPosition(el){
    var parent = el.offsetParent,
    offsetLeft = el.offsetLeft,
    offsetTop = el.offsetTop;
    while(parent){
    offsetLeft += parent.offsetLeft;
    offsetTop += parent.offsetTop;
    parent = parent.offsetParent;
    }
    return {
    left: offsetLeft,
    top: offsetTop
    }
    }

操作滚动条

  • window.scroll(x, y) 和window.scrollTo(x, y) 等效,滚动到绝对位置
  • window.scrollBy(x, y) 滚动相对距离

DOM/CSS/渲染树

DOM tree

解析是生成DOM树,加载是加载内部资源,比如img等,解析和加载异步完成

DOM树解析过程是将节点挂载到DOM树上,遵循深度优先解析原则

加载,当前节点若解析完成(并不是页面解析完成),就开始加载资源

CSS tree

类似于DOM tree的生成过程

render tree

render tree = DOM tree + CSS tree,渲染树构建完毕后,浏览器根据它绘制页面。

  • 渲染树每个节点都有自己的样式
  • 渲染树不包含隐藏节点,display:none head类等不需要绘制的节点
    • display:none 不进行绘制;visibility: hidden 绘制了只是看不见
  • 渲染树的每个节点都会被当成一个盒子box,具有内容填充、边距、边框、位置、大小等其他样式

回流与重绘

当JS对页面的节点操作时,就会产生回流或者重绘。回流一定重绘,重绘不一定回流

回流,reflow:

  • 回流是相当消耗性能的,开发中应该减少回流次数
  • 回流会引起 渲染树中的一部分或全部重新构建
  • 引起回流的因素:
    • DOM节点增加 删除
    • DOM节点位置变化
    • 元素的尺寸、边距、填充、边框、显示(display:none block)改变
    • 页面初始化,即(至少有一次回流)
    • 浏览器窗口尺寸变化,resize
    • 向浏览器请求某些样式信息 offset client scroll width

重绘,repaint:

  • 不会改变节点的尺寸、布局、显示,就只重绘
  • 若发生回流,浏览器根据新的渲染树重新绘制,这个过程称为 重绘

优化策略:

  • 浏览器队列处理机制
  • 增添样式 放在 class中,批量处理,减少回流次数
  • 或者 this.style.cssText 做动态值变化
  • documentFragment
  • 动画一定要做绝对定位,相对定位每次变化都会引起父级的回流重绘

DOM控制CSS样式

  • css样式表里的信息,控制台是无法访问到的

  • DOM无法直接控制css样式,可以通过 标签style属性进行设置

  • window.getComputedStyle(elem, null)[prop] 查看计算样式,IE8及以下不支持

    获取样式的多平台兼容性写法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function getStyles(elem, prop){
    if(window.getComputedStyle){
    if(prop){
    return window.getComputedStyle(elem,null)[prop];
    } else {
    return window.getComputedStyle(elem,null);
    }
    } else {
    if(prop){
    return elem.currentStyle[prop];
    } else {
    return elem.currentStyle;
    }
    }
    }
  • offsetWidth offsetHeight计算元素宽高,但是算入了padding,在企业中不常用,用上面封装的函数获取宽高会更合适

  • 如何获取伪元素的属性,eg: div:: after{ … }

    • width.getCoumputedStyle(div, ‘after’).width
  • 操作伪元素的最好方法就是 加一个类

时间线

在浏览器加载页面开始到页面加载完成,按顺序发生的每一件事情

  1. 生成document对象,以供DOM操作

  2. 解析文档,构建DOM树

    document.readyState = ‘loading’

  3. 遇到link开始异步加载css外部文件的新线程,遇到style开始异步构建CSSOM的新线程

  4. 没有设置异步加载的script,阻塞文档解析,等到JS脚本加载并执行完成后,继续解析文档

  5. 异步加载script,异步加载JS脚本,不阻塞解析文档(不能使用documen.write)

  6. 解析文档遇到img,先解析节点。创建加载线程,异步加载图片资源,不阻塞解析文档

  7. 文档解析完成,document.readyState = ‘interactive’

  8. defer script,JS脚本按照顺序执行

  9. DOMContentLoaded事件,

  10. async script加载并执行完毕,img等资源加载完毕,window对象中的onload事件才开始触发,document.readyState = ‘complete’

window.onload 和 DOMContentLoaded的区别:window.onload是页面全部加载完毕才触发,DOMContentLoaded是文档解析完成和异步脚本加载完成后就触发

事件代理

  • 事件是不需要绑定的,绑定的是事件处理函数

  • 事件冒泡,从子元素开始,触发了 事件,默认会向父级元素传递,可以一直向上级传递,body、html、document、window

  • 事件对象,event,事件触发那一刻所有相关信息和方法的集合,event的原型链如图所示

    image-20221028133521313

  • 阻止事件冒泡的方法:

    • e.stopPropagation() W3C规范

    • e.cancelBubble = true IE

    1
    2
    3
    4
    5
    6
    var e = e || window.event;
    if(e.stopPropagation){
    e.stopPropagation();
    } else{
    e.cancelBubble = true;
    }
  • 事件代理:给父元素绑定事件处理函数,通过事件源对象找到子元素,然后执行特定的程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var items = document.getElementsByTagName('li');

    listWrapper.onclick = function(e){
    var e = e || window.event,
    tar = e.target || e.srcElement, //事件源对象
    tarName = tar.tagName.toLowerCase();

    if(tarName === 'li'){
    console.log(tar.innerText);
    var index = [].indexOf.call(items, tar);//获取索引的方法
    console.log(index);
    }
    }

  • 事件流的三个阶段

    • 捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段默认不执行处理函数;
    • 目标阶段:在目标节点上触发,称为“目标阶段”
    • 冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;
    1
    2
    window.addEventListener('click',function(){},false)
    //第三个参数 false 捕获阶段默认不执行; true 捕获阶段默认执行

日期对象 Date()

  • getDate() 返回当前是一个月的第几天 1-31
  • getDay() 返回当前是一周中的第几天 0-6 周日算0
  • getMonth() 返回当前为第几个月, 0-11 记得要加1
  • getFullYear() 返回年份、getHours、getMinutes、getSeconds
  • getTime,获取时间戳,自此1970年1月1日0点0分0秒后过了多少毫秒
  • 以上函数只是记录片段,并不是实时计算,当算持续时间的时候,需要重新实例化一次
  • setInterval 、setTimeout 默认归属于window
  • Math.round() 并不是完全的四舍五入,在负的点5的时候,退一位 eg:Math.round(-5.5) return -5

计时器

setInterval ,间隔时间重复执行;clearInterval 取消计时器

  • setInterval 运行过程中只取一次值,无法更改
  • setInterval 的返回值是它的唯一标识(从1开始),即多次实例化后的打印输入会累加
  • clearInterval() 参数是 Interval 的唯一标识
1
2
3
4
5
6
setInterval(function(){

},1000) // 参数1,执行的函数;参数2,间隔多少毫秒执行一次

var test(){};
setInterval(test,1000) // 这里可以写成‘test()',但是不可以直接写test()

延迟器

setTimeout,延迟特定的毫秒时间执行一次函数;clearTimeout,取消延迟器

参考资料

(8条消息) JavaScript 中堆和栈的区别_you_wne的博客-CSDN博客_js堆和栈的区别