HTML
什么是HTML语义化
html语义化**就是让页面的内容结构化,便于对浏览器、搜索引擎解析**;在没有样式CCS情况下也以一种文档格式显示,并且是容易阅读的。 搜索引擎的爬虫依赖于标记来确定上下文和各个关键字的权重,利于SEO。 使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解
总结:
根据内容的结构化(内容语义化),选择适合的标签(代码语义化)
1.有利于搜索引擎更好的解析和进行爬虫(SEO)
2.有助于开发者方便阅读,维护和写出更优雅的代码
3.在没有css的情况下,也能呈现出内容的结构和代码的结构
什么是HTML5,以及和HTML的区别是什么
什么是html5:
html5 是html的第五次修改,是w3c的最新标准,其目的是将互联网语义话,方便机器和人阅读,更好的支持各种媒体的嵌入,html5 是向后兼容
和HTML的区别:
1. 文档的声明不一样
(1) html 声明
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
//或
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
(2) html5 声明
<!DOCTYPE html>
html5 的声明简洁,有助于开发人员阅读和开发
2. 结构化语义不一样
html: 没有体现出结构化语义 如: <div id='xxx'></div>
html5: 添加了很多语义化标签,如 header, aside, article, vudio, audio等
3. 绘图不一样
html: 矢量图形
html5: 可以使用canvas 进行绘图
4. 音频和视频支不支持
html:不支持
html5: 支持
5. 语法的处理
html: 不会处理不准确的语法
html5: HTML5能够处理不准确的语法
简单描述从输入网址到页面现实的过程
1. DNS 域名解析
2. TCP 连接
深挖(3次握手,4次挥手)
3. 发送HTTP请求
4. 服务器响应请求,并返回http 报文
5. 浏览器解析渲染页面
(1)解析HTMl 生成dom树
(2)解析CSS 生成CSSom 树
(3)合并生渲染树
(4)浏览器开始绘制和渲染页面
6. 断开链接
简述html页面渲染的过程
浏览器解析html 是‘自上而下’加载,并在加载中进行解析和渲染
1.浏览器解析Dom生成一个dom树,深度遍历当前子树,如果当前节点所有子树结构完成,才遍历当前下一个兄弟节点树
2.css 解析成css rule tree
3.根据dom tree 和 cssom tree 来构成 rendering tree
4.根据rendering tree, 浏览器知道每个节点和每个节点的从属关系,然后计算出每个节点在屏幕展示的位置
回流和重绘是什么,什么场景下会触发
1.回流: 通过渲染树计算出元素在可视化窗口的大小和位置,这个过程就回流
2.重绘: 再经过渲染树和回流,最终显示在屏幕上的实际像素,这个阶段叫重绘
回流触发:
1. 删除或者添加元素
2. 浏览器的窗口尺寸发生了变化
3. 元素位置发生了变化
4. 元素的尺寸发生了变化(如:padding, border, height, width 等)
重绘触发:
1. 颜色修改
2. 阴影修改
CSSOM树和DOM树是同时解析的吗?
浏览器会下载HTML解析页面生成DOM树,遇到css标签 会解析成CSSOM,这个过程是不会造成阻塞的,但是如果遇到了js脚本,假如CSSOM 还没有构造完成,会等CSSOM构造完成,在执行js脚本,然后在进行DOM 解析,此过程会造成阻塞Dom渲染阻塞
script 标签中,async和defer 有什么区别
相同点:都是异步加载(fetch)
不同点:
async加载完成后,会立即执行(execution)
defer加载完成后延迟到dom解析完成之后执行(execution),但会在事件DomContentLoaded之前执行
什么是DOM,BOM
DOM:Document Object Model (文档对象模版)
处理网页内容的方法和接口,是W3C的标准
如:删除,添加元素等
BOM:Brower Object Model (浏览器对象模版)
可以简单理解为控制浏览器的行为和接口, 是浏览器厂商根据Dom在各个浏览器上的实现
如:前进,后退,跳转到另一个页面,获取屏幕大小等
补充:
BOM的核心就是window,BOM包含了(DOM)对象,浏览器提供了访问BOM的方法,所以我们能通过BOM访问到Dom对象上
浏览器的内核
1、IE浏览器内核:Trident内核,也是俗称的IE内核;
2、Chrome浏览器内核:统称为Chromium内核或Chrome内核,以前是Webkit内核,现在是Blink内核;
3、Firefox浏览器内核:Gecko内核,俗称Firefox内核;
4、Safari浏览器内核:Webkit内核;
5、Opera浏览器内核:最初是自己的Presto内核,后来是Webkit,现在是Blink内核;
6、360浏览器、猎豹浏览器内核:IE+Chrome双内核;
7、搜狗、遨游、QQ浏览器内核:Trident(兼容模式)+Webkit(高速模式);
8、百度浏览器、世界之窗内核:IE内核;
9、2345浏览器内核:以前是IE内核,现在也是IE+Chrome双内核;
https 和 http 的区别
http:超文本传输协议,
简单理解: 客户端和服务器端相互传输消息
https: 超文本传输安全协议,在http 的基础上添加了一个 ssl(安全加密隧道协商完成)
简单理解:在客户端和服务器端相互传输消息的过程中,进行了数据加密,保护了数据的隐私性和完整性
区别:
1.http是明文,https是进行数据加密的
2.https需要证书
3.http比https 速度更快
4.https比http 更消耗资源
http 强缓存和协商缓存
htt缓存分为协商缓存和强缓存
强缓存大于协商缓存
1. 判断是否有强缓存
expires(http1.0): 绝对时间
cache-control(http1.1) :
max-age : xx秒(时间戳)
no-cache
no-store: 不使用缓存
public/private: 是否只能被单个用户使用
通过cache-control和expires 来判断是否被强缓存(cache-control优先级比expires高)
2,判断是否为协商缓存
ETag/ If-None-Match
Last-Modified/ If-Modified-Since
ETag 优先级高于Last-Modified
(1)服务器设置是否缓存
eg. const express = require('express');\
const app = express();\
var options = { \
etag: true, // 开启协商缓存\
lastModified: true, // 开启协商缓存\
setHeaders: (res, path, stat) => {\
res.set({\
'Cache-Control': 'max-age=00', // 浏览器不走强缓存\
'Pragma': 'no-cache', // 浏览器不走强缓存\
});\
},\
};\
app.use(express.static((__dirname + '/public'), options));\
app.listen(3001);
(2) 第一次请求 responese headers 会返回ETag(hash值),或者last-Modified(最后一次修改的值)
(3) 如果responese headers返回的结果的时候有ETag(hash值),再次请求的时候request headers 会通过 If-None-Match 携带ETag(hash值),服务端会判断If-None-Match 携带ETag与当前的hash值是否一致
一致,返回304,读取浏览器缓存。
不一致,获取新的资源
(4)如果responese headers返回的结果的时候没有ETag(hash值),会去判断上次请求的Last-Modified是否存在,存在 ==〉再次请求的时候,会判断 If-Modified-Since 携带Last-Modified 与服务端的是否一致
一致,返回304,读取浏览器缓存。
不一致,获取新的资源
CSS
js 和 css 是如何影响Dom树的构建的
1.html是自上而下加载
2.解析页面生成dom树,遇见css标签会进行下载和解析
3.css的下载和解析不会影响dom的解析,但是会阻塞dom的渲染
我的理解:如果不不阻塞的话,会造成回流或者重绘
4.如果js放在css标签下面,css解析完成过后,才会去执行js,执行js也会阻塞dom的渲染,js执行完成才回去渲染dom
理解:同样会造成回流或者重绘
css垂直居中有哪些实现方法
<div id="parent">
<p id="child"></p>
</div>
#parent {
width: 100vw;
height: 100vh;
}
#child {
width: 200px;
height: 200px;
background-color: aqua;
}
分为3种情况
1.已知子元素宽高
2.已知父元素宽高
3.未知子元素宽高
/* 已知子元素宽高 */
/* 1. 使用绝对定位和margin 子元素设置 */
/* position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto; */
/* 2. 绝对定位 加 margin负值 子元素设置 */
/* position: absolute;
top: 50%;
left: 50%;
margin-left: -100px;
margin-top: -100px; */
/* 3. 绝对定位加calc 子元素设置 */
/* position: absolute;
top: calc(50% - 100px);
left: calc(50% - 100px); */
/* 已知父元素宽高 */
/* 1.子元素设置
display: inline-block;
vertical-align: middle;
父元素设置
line-height: 500px;
text-align: center; */
/* 2.父元素设置
display: table-cell;
text-align: center;
vertical-align: middle;
子元素设置
display: inline-block; */
/* 未知宽高 */
/* 1. 绝对定位 加 transform 子元素设置 */
/* position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) */
/* 2. flex布局 父元素设置 */
/* display: flex;
justify-content: center;
align-items: center; */
/* 3. flex 加 magin auto */
/* 父元素设置
display: flex;
子元素设置
margin: auto; */
/* 4.grid 网格布局 父元素设置*/
/* display: grid;
align-items: center;
justify-content: center; */
CSS中的flex: 1; 是什么意思
flex: 1 === flex: 1 1 auto
flex:initail === flex: 0 1 auto
flex-frow:用于设置各item项按对应比例划分剩余空间,flex-grow默认是0;
eg: 若各个item都是flex-grow = 1; 则等分剩余的空间。
若其中一个flex-grow = 2; 代表该item占剩余空间的2份,其余 占1份
flex-shrink:用于设置item的总和超出容器空间时,各item的收缩比例,默认为1。
eg. 若各个item都是flex-shrink = 1, 则等比例缩小。
若其中flex-shrink = 0,则该item不缩小,其它item等比例缩小
flex-basic:在分配多余空间之前,占据主轴的空间(main size),浏览器根据这个属性计算出是否有剩余的空间,默认值为 auto,即item的本身的大小
什么是BFC
-
bfc是什么
-
bfc触发条件
-
bfc的应用场景
bfc是什么?
Block Formatting Context (块级格式化上下文) ,他是一个页面的渲染区域,有一套自己的规则
1. 内部的盒子自上而下一个一个的垂直放置
2. 同一个bfc的2个相邻的盒子会发生重叠(不论什么方向)
3. 每个元素的左外边距与包含块的左边距相接触(从左到右),浮动元素 也是如此
我的理解:bfc盒子内的元素只能达到盒子最左边,也就是盒子的左边距(浮动元素也包含在这里面) 不包含的: position: absolute
4. 计算bfc高度时,浮动元素也会被计算
5. 浮动元素不会和bfc元素发生重叠
6. bfc是一个页面独立的渲染区域,不会与外界元素发生关系,反之亦然
bfc的触发条件
1. 根元素
2. overflow: hidden, scroll, auto(除了visible的元素)
3. float: left, right,(除了none)
4. display: tabel,grid, flex, inline-block 等
5. position: absolute, fixed
bfc 的应用场景
1. margin发生重叠的案列
<style>
p {
color: #f55;
background: #fcc;
width: 200px;
line-height: 100px;
text-align:center;
margin: 100px;
}
</style>
<body>
<p>Haha</p >
<p>Hehe</p >
</body>
2个p之间的距离只会相差 100px, 而不是200px
补充: 如果margin不同,取最大一个值
修改过后
<style>
.wrap {
overflow: hidden;// 新的BFC
}
p {
color: #f55;
background: #fcc;
width: 200px;
line-height: 100px;
text-align:center;
margin: 100px;
}
</style>
<body>
<p>Haha</p >
<div class="wrap">
<p>Hehe</p >
</div>
</body>
2. 浮动后,不计算内部高度
<style>
.par {
border: 5px solid #fcc;
width: 300px;
}
.child {
border: 5px solid #f66;
width:100px;
height: 100px;
float: left;
}
</style>
<body>
<div class="par">
<div class="child"></div>
<div class="child"></div>
</div>
</body>
par的高度为0
修改过后:
<style>
.par {
border: 5px solid #fcc;
width: 300px;
overflow: hidden;
}
.child {
border: 5px solid #f66;
width:100px;
height: 100px;
float: left;
}
</style>
<body>
<div class="par">
<div class="child"></div>
<div class="child"></div>
</div>
</body>
css加载会造成阻塞嘛
css加载不会阻塞dom解析
css加载会造成dom渲染
css加载会阻塞js的执行
可以借鉴上面的: CSSOM树和DOM树是同时解析的吗?
css 画一个三角形
查看图一:
.triangle {
width: 0;
height: 0;
border: 40px solid transparent;
border-top:60px solid balck;
}
border-top:60px 定义的是这个三角形的高
在图一的基础上我们设置一条边border 为0的时候,会变成一个 直角三角形,请查看图二
.triangle {
width: 0;
height: 0;
border: 40px solid transparent;
border-top:60px solid balck;
border-left: 0;
}
图一
图二
em/px/rem/vh/vw 有什么区别
相对单位:em, rem, vh/vw, % 等
绝对单位: px, pt 等
em:
em相对单位,基准点为父节点字体大小(继承父元素的字体大小),如果自身定义了 font-size按自身来计算(浏览器默认字号是 16px),整个页面内 1em不是一个固定的值。
如:比如浏览器的默认size是 16px, 父元素并没有设置字体大小,子元素的1em = 16px
如果父元素设置了字体大小为32px, 则子元素的1em = 32px
rem:
相对单位,可理解为‘root em’,相对根节点 html(网页)的字体大小来计算,CSS3新加属性,chrome/firefox/IE9+支持。
不会像 em 那样,依赖于父元素的字体大小,从而造成混乱
如:浏览器的默认size是 16px,子元素都会相对于浏览器,不回去去找自身或者父元素的字体大小
vw:
v是 viewport,可视窗口的意思,w是 width,宽度的意思。
视窗宽度(视口宽度的 1%)
如:浏览器宽度1200px,1vw = 1200px/100 = 12px
css选择器有哪些,优先级分别是什么
css基本选择器
标签选择器:如body,div, p
id选择器: id='xxx'
类选择器: class='xxx'
后代选择器:(如:#head .nav ul li 从父集到子孙集的选择器)
子代选择器:(如:div>p ,带大于号>)
伪类选择器(如:就是链接样式,a元素的伪类,4种不同的状态:link、visited、active、hover。)
优先级:
id选择器 > 类选择器 > 标签选择器 > 子代选择器 > 后代选择器 > 伪类选择器
说说你对盒模型的理解
当对一个文档进行布局(layout)的时候,浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型(CSS basic box model),将所有元素表示为一个个矩形的盒子(box)
一个盒模型由margin, border, padding, content 组成
IE盒模型:
盒子的宽度: width + margin
width: padding + content(content width) + border
标准盒模型:
盒子的宽度:padding + width + border + margin
width: width((content width))
如何使用css提高页面性能
1.内联首屏关键CSS
2.异步加载CSS
3.资源压缩
4.合理使用选择器
5.减少使用昂贵的属性
6.不要使用@import
请参考此文章
JS
js基础类型,引用类型
基础类型:Null, Undefined, Boolean, Number, String, BigInt,symbols(唯一且不可修改**的原始值)
引用类型:object(被看作是一组属性的集合)
栈:先进后出的数据结构,栈会自动分配内存空间,会自动释放,存放基本类型和引用数据类型的地址,
堆:动态分配的内存,大小不定也不会自动释放,存放引用类型的对象,指那些可能由多个值构成的对象,保存在堆内存中
eg 1.
let a = 1
let b = a
b = 3
console.log(a, b) // 1, 3
eg 2.
let a = 1
let b = 111
function fn(a,b){
a = 10
return a + b
}
fn(a,b)
console.log(a, b)
// 1, 111
在稍微改变一下
function fn1(b){
a = 10
return a + b
}
fn1(b)
console.log(a, b)
// 10, 111
有什么区别,为什么?
因为在形参的时候,会进行一个浅拷贝
fn 的形参a进行了浅拷贝,形参a 和 全局a 都指向了1,
形参a = 10,进行了重新复制,然而全局的a 没有改变
fn1里面的a 就是全局a, 所以在重新赋值的时候,全局a也改变了
eg3 .
let obj = {
a: 1,
b: 2
}
let newObj = obj
newObj.a = 10
console.log(obj,newObj) //都是 { a: 10, b: 2 }
这篇文章值得推荐
总结一下这篇文章 :
- 全局变量:基础类型和引用类型的值都是存在堆的,但是地址是放在全局执行上下文的栈内存
- 被捕获的变量(闭包):基础类型和引用类型的值都是存在堆的,但是地址是放在调用变量的那个函数上面的栈
- 局部变量(未被捕获的):基础类型的值和地址都是放在栈的,引用类型的值是放在堆里面的,地址是放在 函数的栈的
js数组常用方法有哪些
1.map
2.forEach
3.filter
4.some
5.every
6.reduce
7.isArray
8.pop
9.push
10.splice
11.shift
12.join
13.reverse
14.unshift
15.concat
16.toString
17.indexOf
18.lastIndexOf
20.slice
21.sort
map
返回一个新的数组
const Arr = [1,2,3,4,5]
const newArray = Arr.map(item => {
return item + 1
})
console.log(Arr)
console.log(newArray)
// [ 1, 2, 3, 4, 5 ]
// [ 2, 3, 4, 5, 6 ]
forEach
第一个参数:数组中正在处理的元素
第二个参数:数组中正在处理的元素的index
第三个参数:当前遍历的数组
补充:不会被中断,不会返回新的数组
eg。据列几个比较有意思的 demo
// 深入研究的话,要去研究一下基础数据和引入数据的栈和堆
1.
const arr = [1,2,3,4]
arr.forEach(item => {
item*2
})
console.log(arr)
// [ 1, 2, 3, 4 ]
2.
arr.forEach((item, index, arrry) => {
arrry[index] = item*2
})
console.log(arr)
// [ 2, 4, 6, 8 ]
reduce
第一个参数: callbakFn
第二个参数: 初始值
callbakFn:
第一个参数:如果有初始值则为初始值,否则为数组索引为 0 的元素 array[0]
第二个参数:数组中正在处理的元素
第三个参数:数组中正在处理的元素的index
第四个参数:当前遍历的数组
eg.计算求和
const arr = [1,2,3,4]
const num = arr.reduce((pre, cur) => {
console.log(pre, cur)
return pre + cur
}, 0)
console.log(num) // 10
eg.实现升降排序
// ascending 升序
// descending 降序
let sort = 'ascending'
const arr = [{ age: 9},{age: 1},{age: 8},{ age:2},{age: 7},{age: 3}]
const sortFn = (arr, key, sort = 'ascending') => {
let newArr = []
let isSort = sort === 'ascending'
arr.reduce((pre, curren) => {
let index = pre.findIndex(item => isSort ? item[key] > curren[key] : item[key] < curren[key])
return index === -1 ? pre.push(curren) && pre : pre.splice(index, 0, curren) && pre
}, newArr)
return newArr
}
console.log(sortFn(arr, 'age', sort))
[
{ age: 1 },
{ age: 2 },
{ age: 3 },
{ age: 7 },
{ age: 8 },
{ age: 9 }
]
filter
会对数组中的元素挨个进行检过滤查,检查通过的,返回组成新的数组
const arr = [1, 5, 10, 20]
const newArr = arr.filter(item => item > 5)
console.log(newArr)
// [10,20]
some
返回一个Boolean, 测试数组中是不是至少有1个元素通过了检查,返回true
const arr = [1, 5, 10, 20]
console.log(arr.some(item => item > 10))
// true
every 返回一个Boolean, 测试数组中是不是全部元素通过了检查,返回true
const arr = [1, 5, 10, 20]
console.log(arr.every(item => item > 10))
// false
console.log(arr.every(item => item > 0))
// true
ES6+ 有哪些新特性
let、const 块级作用域
let
1、不存在变量提升,let声明变量前,该变量不能使用。就是 let 声明存在暂时性死区
2、let命令所在的代码块内有效,在块级作用域内有效,作用域只是在花括号里面
3、let不允许在相同作用域中重复声明,注意是相同作用域,不同作用域有重复声明不会报错
const
1、const声明一个只读的变量,声明后,值就不能改变
2、const并不是变量的值不能改动,而是变量指向的内存地址所保存的数据不得改动
3、let该有的特点const都有
解构
数组结构
const [a,b,...rest] = [1,2,3,4]
console.log(a, b, rest)
// 1 2 [ 3, 4 ]
对象结构
let { f1, f2 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2
模版字符串
let a = 'l'
let b = 'q'
let c = `${a}${b}`
// lq
class
class A {
constructor(name, age, clothes) {
this.name = name;
this.age = age;
this.clothes = clothes;
}
say() {
const sayStr = `Hello, my name is ${this.name}, ${this.age} years old!`;
console.log(sayStr);
}
static wear() {
return 'I wear cowboy'
}
}
class B extends A {
constructor(name, age, clothes) {
super(name, age, clothes);
this.Bname = name;
this.Bage = age;
this.Bclothes = clothes;
}
static wear() {
return super.wear();
}
}
console.log(B.wear())
// I wear cowboy
const aa = new B(1,2,3);
aa.say()
// Hello, my name is 1, 2 years old!
箭头函数
() => console.log(1);
const a = () => 1;
const add = (a, b) => a + b;
const resFunc = (a, b) => () => a + b; // 返回一个函数
Promise
一道面试题分享 :
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
await fn()
}
const fn = () => {
return new Promise((resolve, reject) => {
console.log('fn start')
resolve()
}).then(() => {
console.log('fn end')
})
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
});
async1()//
//初始化Promise是,传入的函数会立刻被执行
new Promise((resolve=>{
console.log('promise1')
resolve()
})).then(function () {
console.log('promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// fn start
// promise1
// script end
// fn end
// async1 end
// promise2
// setTimeout
模块化
其它
await/async
new set/ new map
symbol/BigInt
扩展运算符
如何区分数组和对象
1.
[] instanceof Array // true
{} instanceof Array // false
2.
Object.prototype.toString.call([])
'[object Array]'
Object.prototype.toString.call({})
'[object Object]'
3.
Array.isArray([])
// true
Array.isArray({})
// false
防抖和节流
防抖
在事件被触发的n秒后触发回调,如果在这n秒内有被触发,则重新计时
应用场景: 模糊查询, window触发resize 等
实现debounce
let timer = null
function debounce(fn, time) {
let timer = null
return function() {
let _this = this
let args = arguments
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(_this, args)
}, time)
}
}
节流
在规定时间内,只能触发一次,如果在这个单位时间内触发n次,只会触发一次
应用场景:监听滚动事件,比如是否滑到底部自动加载更多
实现
function throttle(fn, time) {
let pretTimer = 0
return function() {
let _this = this
let args = arguments
let timer = new Date().getTime()
if (timer - pretTimer > time) {
fn.apply(_this, args)
pretTimer = timer
}
}
}
promise方法有哪些,和怎么实现的
this理解
1.默认绑定
在严格模式下,this指向的是undefined
在非严格模式下,this指向window
eg.
function foo () { console.log(this) }
foo() // => window
// 严格模式
function bar () {
'use strict'
console.log(this)
}
bar()
// => undefined
2.隐式绑定
由上下文对象调用,绑定到上下文对象
简单说,就是谁调用函数,就指向谁
function fn() {
console.log(this)
}
let obj = {
a: 1,
fn:
}
obj.fn() // obj 隐式绑定
let a = obj.fn
a()
// window 思考?这个是隐式绑定还是默认绑定
其实2种都没得问题
(1)window.a(), this指向了window 隐式绑定
(2)直接默认调用a函数。 this默认绑定了window. 默认绑定
在稍微的加一点难度
区分默认绑定和隐式绑定
let obj = {
a: 1,
fn: function() {
console.log(this, '1')
// obj 隐式绑定
function fn1() {
console.log(this, '2')
// 这个this 打印的是window
// fn1() 没有调用,默认是window.fn1()
// 这个属于默认绑定
}
fn1()
}
}
obj.fn()
隐式绑定 this 丢失的问题
案列1:
let obj = {
a: 1,
fn: function() {
console.log(this)
}
}
setTimeout(obj.fn, 0)
// window
==> setTimeout(function() {
console.log(this)
}, 0)
解决方法:
1. setTimeout(obj.fn(), 0) // 隐式绑定
2. setTimeout(obj.fn.bind(obj), 0) // => obj // 显示绑定
案列2:
var name='zcxiaobao';
function introduce(){
console.log('Hello,My name is ', this.name);
}
const Tom = {
name: 'TOM',
introduce: function(){
setTimeout(function(){
console.log(this)
console.log('Hello, My name is ',this.name);
})
}
}
const Mary = {
name: 'Mary',
introduce
}
const Lisa = {
name: 'Lisa',
introduce
}
Tom.introduce();
setTimeout(Mary.introduce, 100);
setTimeout(function(){
Lisa.introduce();
},200)
// Tom.introduce(); Hello, My name is zcxiaobao
// setTimeout 回调函数的this指向window
// setTimeout(Mary.introduce,100); Hello, My name is zcxiaobao
// setTimeout(function(){
// Lisa.introduce();
// },200)
// Hello, My name is Lisa 隐式绑定
3.显示绑定
直接将this绑定到指定对象
比如call, apply, bind
call()和apply()
函数会立即执行bind()
函数会返回新函数,不会立即执行函数call()和apply()
的区别在于call
接受若干个参数,apply
接受数组。
function foo() {
console.log(this.a);
};
var obj = {
a: 2
};
foo.call(obj);
// 2
4.new绑定(构造函数)
请查看下面的new 的实现
箭头函数
箭头函数中的this,指向的是函数定义位置(定义作用域)的上下文this
如:
let obj = {
a: 11,
method: () => {console.log(this.a)}
}
obj.method() // undefined
// 因为对象不是作用域
优先级
显式 > 隐式 > 默认
bind,call,apply 有什么区别,如何实现
区别
call: call 接受多个参数,第一个为上下文(this),后面的参数为函数本身的参数,立即执行 apply: apply接受2个参数,第一个是上下文(this),第二个参数是一个数组,立即执行 bind: bind 接受多个参数,第一个为上下文(this),后面的参数为函数本身的参数,返回一个函数(该函数使用apply的方法)
call 实现
Function.prototype.call2 = function(context) {
//判断当前context是否存在,不存在默认等于window
context = context || window
// 当前函数赋值给context的fn
context['fn'] = this;
// 获取除了第一个的其他参数
let arg = [...arguments].slice(1)
// 这里就是this隐式调用,执行
context['fn'](arg)
// 删除context 到fn
delete context['fn']
}
function fn(arr) {
console.log(this, arr)
}
let obj = {a: 1, b:2000}
fn.call2(obj, 1, 2, 3) //
// {
// "a": 1,
// "b": 2000
// }
// [1, 2, 3]
apply 实现
Function.prototype.apply2 = function(context, arr) {
//判断当前context是否存在,不存在默认等于window
context = context || window
// 当前函数赋值给context的fn
context['fn'] = this;
if (arr) {
context['fn'](arr)
} else {
context['fn']()
}
// 删除context 到fn
delete context['fn']
}
bind实现
Function.prototype.bind2 = function(context) {
//判断当前context是否存在,不存在默认等于window
context = context || window
let self = this
let arg = [...arguments].slice(1)
return function() {
let newArg = arg.concat([...arguments])
self.apply2(context, newArg)
}
}
function fn() {
console.log(...arguments)
console.log(this)
}
let obj = {a: 1, b:2000}
let newFn = fn.bind2(obj, 1,2,3)
newFn(3,4)
// [
// 1,
// 2,
// 3,
// 3,
// 4
// ]
// {
// "a": 1,
// "b": 2000
// }
new的实现
初步查看new做了什么
function school() {
this.schoolName = 'LLL'
this.schoolClothes = 'XXX'
}
school.prototype.schoolStyle = 'CCC'
const children = new school()
children.schoolName // 'LLL'
children.schoolStyle // 'CCC'
- 访问到了 school 构造函数的属性
- 访问到了 school 构造函数原型的属性
实现new
1.创建了一个新的对象 2.在arguments里面获取构造函数,并且在arguments里面去掉第一个构造函数 3.新的对象的原型指向构造函数的prototype 4.构造函数的this指向新的对象 5.判断第四步是否返回的是一个对象,如果不是则返回新建的对象
function objectFactory() {
let obj = {}
constructor = [].shift.call(arguments)
obj.__proto__ = constructor.prototype
const ret = constructor.apply(obj, arguments)
return typeOf ret === 'object' ? ret : obj
}
js中原型,原型链理解
原型
每个javascript对象(除了null)在创建的时候,都会与之关联一个对象,而关联的哪个对象就是我们所说的原型,每个对象都会继承原型的属性
了解上面的话,首先得知道 实例, 构造函数, 实例原型 是什么?
通过一张图和一段代码来了解实例, 构造函数, 实例原型
function Person() {}
Person.prototype.name = 'xl'
const person = new Person()
// person: 实例
// Person: 构造函数
// Person.prototype 实例原型
比如:以上Person.prototype 就是 person的 原型
原型链
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线
补充:
补充1:
可以理解__proto__ === constructor.prototype
通过以上的代码我们可以进行一个实验
console.log(person.constructor === Person) // true
console.log(person.__proto__ === Person.prototype) // true
console.log(Person.prototype.constructor === Person) // true
补充2:
Object.prototype.__proto__ 指向的是null
说说你对事件的循环的理解
Event Loop: 事件循环,是浏览器和node在执行javascript的时候不回阻塞的一种方式,这就是我们经常说的异步
假设我们没有事件循环会造成什么
const a = 1
setTimeout(() => {console.log(a)}, 1000)
console.log('2')
// 等待一秒后执行 console.log(a),然后在执行console.log('2'),相当于中间的一秒什么都没有干
1. 栈 堆 队列
栈 堆: 请查看上面 ==> js基础类型,引用类型对他们的讲解
队列:又称队列元素,先进先出
2.MarockTask(宏任务)
`script`全部代码、`setTimeout`、`setInterval`、`setImmediate`、 `I/O`、`UI Rendering`
3.MirockTask(微任务)
`Process.nextTick(Node独有)`、`Promise`、`MutationObserver`
总结
在 同步进行完成过后,执行栈为空,就回去检查微任务队列是否为空,如果不为空,就执行完所有的微任务,再去执行宏任务,为空就直接去执行宏任务
每单个宏任务执行完毕后,又去检查微任务队列是否为空,依次循环
作用域
分为静态作用域和动态作用域
js 是使用静态作用域
作用域 : 全局作用域,函数作用域,块级作用域(自行了解)
静态作用域:
var a = '1'
function fn() {
console.log(a)
// 1
}
function fn1() {
var a = '2'
console.log(a)
// 2
fn()
}
fn1()
此时有3个作用域: 全局, fn fn1 ==> 同级,上一层作用域是全局
在执行fn1的时候 a 会先找fn1作用域里面的a,所以打印是2
继续执行fn,a 会先找fn作用域里面是否我a,没得就去上一层作用域全局,打印的结果是 1
闭包
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。 -- JavaScript高级程序设计(第三版)
我的理解: 一个函数调用另一个函数的内部变量
了解闭包首先要了解作用域(请参看上面的作用域),然后了解栈和堆
闭包的特点:
1. 函数嵌套函数
2. 内部函数调用外部函数的变量
3. 不会被垃圾机制回收
常用的地方:
1. 函数的柯里化
2. 防抖和节流
cookie, localstorage, sessionStorage
localStorage和sessionStorage
相同点:
- 储存大小都在5M 左右
- 都支持同源政策
- 都保存在客户端,不与服务端参加通信
不同点:
localStorage:
1. 永久保存,除非手动删除
2. 同一个浏览器里面,同源的情况下,可以共享所有数据
sessionStorage:
1. 页面关闭,就会自动清除缓存
2. 同一个浏览器里面,同源的情况下,新开的页面不回共享另一个页面的数据
cookie
- 保存数据小,限制在4kb 左右
- 每次都会存在http里面,如果cookie 很大,会导致性能问题
- 一般有服务器生成,可能设置消失时间
- 需要自己封装
数组扁平化
// 1.
let arr = [1,2,[3,4,[5,[6]]]]
console.log(arr.flat(1))
// 2.
function fn(arr) {
return arr.reduce(pre, cur => {
pre.concat(Array.isArray(cur) ? fn(cur) : cur)
}, [])
}