原文来自 我的个人博客
最近一直在学习 vue3
源码,搞的头有点大,用这篇文章来换换脑子~
PS:面试题下附有解答,解答会结合我的思考以及扩展,仅供参考,如果有误麻烦指出,好了就酱~
在面试中,HTML
的面试题相对来说比较简单,一般面试官不会花太多时间去关注 HTML
的细节。
语义化的含义就是用正确的标签做正确的事情,html
语义化就是让页面的内容结构化。
打个比方就是,如果我要实现一个一级标题,可以用 div+css
设置样式字体来达到效果,也可以用 h1
,前者当然也能实现效果,但是语义化的方式还是使用 h1
标签,因为我们一看到 h1
标签就会知道他是一个 一级标题,这就是 html
语义化。
SEO 优化
)这个问题可以从三个方向回答:
SEO
是什么?SEO
优化方法有哪些?回答:
SEO(Search Engine Optimization)
,意思就是搜索引擎优化。通俗点讲就是提高你的网页在搜索结果中的排名(排名越高越靠前)SEO
的原理可以大致分为四个步骤:
分词
检查拼音
错别字
等等。。。SEO
优化可以分为内部优化和外部优化
SSR
)Tag
标签),锚文本链接,各导航链接,及图片链接B2B
、新闻、分类信息、贴吧、知道、百科、相关信息网等尽量保持链接的多样性display: block/inline/inline-block
width
, height
属性而行内元素设置无效margin
和 padding
,而行内元素只有水平方向有效,竖直方向无效HTML5
是 HTML
的新标准,其主要目标是无需任何额外的插件如 Flash
、Silverlight
等,就可以传输所有内容。它囊括了动画、视频、丰富的图形用户界面等。
区别:
HTML
是很长的一段代码,很难记住。HTML5
却只有简简单单的声明,方便记忆。html<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<!DOCTYPE html>
HTML4.0
没有体现结构语义化的标签,通常都是这样来命名的<div id="header"></div>
,这样表示网站的头部。HTML5
在语义上却有很大的优势。提供了一些新的标签,比如:<header><article><footer>
。nav
、header
、footer
、aside
、section
、article
audio
、video
localStorage
、sessionStorage
canvas
(画布)、Geolocation
(地理定位)、websocket
(通信协议)input
标签新增属性:placeholder
、autocomplete
、autofocus
、required
history API
:go
、forward
、back
、pushstate
不多说了,前端面试 CSS
是必考知识,不过关直接回家
思路:
box-sizing
这个属性回答:
当对一个文档进行布局(layout
)的时候,浏览器的渲染引擎会根据标准之一的 CSS基础框盒模型(CSS basic box model),将所有元素表示为一个个矩形的盒子(box
)。通俗来讲就是网页是由一个一个盒子组成的,而这个盒子是有自己的标准的,一个盒子由以下四个部分组成:
content(盒子的内容)
padding(盒子的内边距)
border(盒子的边框)
margin(盒子的外边距)
在 CSS
中,盒子模型可以分成:标准盒子模型 和 怪异盒子模型
标准盒子模型:浏览器默认的盒子模型
盒子总宽度 = width
+ padding
+ border
+ margin
盒子总高度 = height
+ padding
+ border
+ margin
width/height
只是内容高度,不包含 padding
和 border
值
怪异盒子模型
盒子总宽度 = width
+ margin
盒子总高度 = height
+ margin
width/height
包含了 padding
和 border
值
CSS
中的 box-sizing
属性定义了引擎应该如何计算一个元素的总宽度和总高度
content-box
默认值,元素的 width/height
不包含padding
,border
,与 标准盒子模型表现一致border-box
元素的 width/height
包含 padding
,border
,与怪异盒子模型表现一致inherit
指定 box-sizing
属性的值,应该从父元素继承下面我们来看一段代码:
html<body>
<div style="margin-top: 20px">
<div style="margin-top: 10px">aaa</div>
</div>
<p style="margin-bottom: 50px">bbb</p>
<p style="margin-top: 10px">ccc</p>
<p style="margin-bottom: 50px">ddd</p>
<p></p>
<p></p>
<p></p>
<p style="margin-top: 10px">eee</p>
</body>
请问:
aaa
距离最顶部多少 px?bbb
距离 ccc
多少 px?ddd
距离 eee
多少 px?公布答案: 分别是 20px
50px
50px
常见的重叠现象
解决方法:
float:left
;position
的值为 absolute/fixed
margin-top/bottom
值时统一设置上或下(推荐)padding
overflow:hidden/auto
(推荐);border:1px solid transparent
;postion:absolute
:float:left
;或 display:inline-block
;margin-top
和 margin-left
负值,元素向上、向左移动margin-right
负值,右侧元素左移,自身不受影响margin-bottom
负值,下方元素上移,自身不受影响第一点应该很好理解,下面这张图分别展示了第二和第三点。
1. 定义:
BFC
全称:Block Formatting Context
, 名为 "块级格式化上下文"。(全程说出来也能加分)
W3C
官方解释为:BFC
它决定了元素如何对其内容进行定位,以及与其它元素的关系和相互作用,当涉及到可视化布局时,Block Formatting Context
提供了一个环境,HTML
在这个环境中按照一定的规则进行布局。
简单来说就是,BFC
就是一块独立渲染区域,内部元素的渲染不会影响边界以外的元素。BFC
有自己的规则和触发条件。
2. 规则
BFC
就是一个块级元素,块级元素会在垂直方向一个接一个的排列BFC
就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签margin
决定, 属于同一个 BFC
的两个相邻的标签外边距会发生重叠BFC
的高度时,浮动元素也参与计算3. 怎样触发 BFC
overflow: hidden
display: flex / inline-block / table-cell
position: absolute / fixed
4. BFC 解决了什么问题
1. 使用 float
脱离文档流,造成高度塌陷
html<style>
.box {
margin: 100px;
width: 100px;
height: 100px;
background: red;
float: left;
}
.container {
border: 1px solid #000;
}
</style>
<div class="container">
<div class="box"></div>
<div class="box"></div>
</div>
效果:
给 container
触发 BFC
就可解决问题,例如 给 container
元素 添加 display:inline-block
。效果:
2. margin
边距重叠问题
这个问题 2.2
已经讲过:
<style> .box { margin: 10px; width: 100px; height: 100px; background: #000; } .container { margin-top: 20px; overflow: hidden; } </style> <div class="container"> <div class="box"></div> </div>
效果以及触发 BFC
后的效果如下:
原理:css
画三角形的原理就是通过 border
画的,因为在 css
中 border
并不是一个矩形,而是一个梯形,在 box
的内容越来越小时,border
的一条底边也会越来越小,直到 box
的宽高都是 0
时,此时的四条 border
就组成了四个三角形,将其他三个隐藏,就能得到一个三角形。
css<style>
.box {
/* 内部大小 */
width: 0px;
height: 0px;
/* 边框大小 只设置两条边*/
border-top: #4285f4 solid;
border-right: transparent solid;
border-width: 85px;
/* 其他设置 */
margin: 50px;
}
</style>
<div class="box"></div>
效果:
relative
依据自身定位absolute
依据最近一层的定位元素居中的方式有很多,我最常用的还是 flexbox
:
cssdisplay:flex;
justify-content: center; // 水平居中
align-items: center; // 垂直居中
另外常见的还有:
请问下面代码 p
标签的行高是多少
html<style>
body {
font-size: 20px;
line-height: 200%;
}
p {
font-size: 16px;
}
</style>
<p>AAA</p>
因为上面的代码中 p
标签没有自己的行高,所以会继承 body
的行高,这题考的就是行高是如何继承的。
规则:
30px
, 则直接继承该值2 / 1.5
,则继承该比例200%
, 则继承计算出来的值答案:40px
rem
是一个长度单位:
px
,绝对长度单位,最常用em
,相对长度单位,相对于父元素,不常用rem
,相对长度单位,相对于 html
根元素,常用于响应式布局1. css3 中新增了一些选择器,主要为下图:
2. css3 中新增了一些样式
边框:border-radius
box-shadow
border-image
背景:background-clip
、background-origin
、background-size
和 background-break
文字:word-wrap
text-overflow
text-shadow
text-decoration
颜色:rgba
与 hsla
动画相关: transition
transform
animation
渐变:linear-gradient
radial-gradient
其他: flex布局
grid布局
(上面提及的属性都应该去了解!)
不会变量,别说会 JS
JS 中的数据类型分为 值类型(基本类型) 和 引用类型(对象类型)
undefined
、null
、boolean
、string
、number
、symbol
、BigInt
七种Object
、Function
、Array
、RegExp
、Date
等等首先 undefined
和 null
都是基本数据类型。
undefined
翻译过来是 未定义。表示 此处应该有一个值,但是还没有定义
null
翻译过来是 空的。表示 没有对象,即此处不应该有值
典型用法:
一般变量声明了但还没有定义的时候会返回 undefined
,函数中没有返回值时默认返回undefined
,以及函数参数没提供时这个参数也是 undefined
null
作为对象原型链的终点。null
作为函数的参数,表示该函数的参数不是对象。
typeof undefined
值为 undefined
,typeof null
是 'object'
jsundefined == null // true
undefined === null // false
至于为什么在 js
中会有 null
和 undefined
吗,就要从历史谈起了,建议阅读一下阮一峰老师的文章 undefined与null的区别
我的总结就是,JS
最初是只设计了一种表示 “无” 的值的就是 null
,这点很好理解就像 java
以及其他语言中的 null
一样,但是在 JS
中的数据类型分为 基本类型 和 对象类型,JS
的作者认为表示 “无” 的变量最好不是一个对象,另外 JS
早期没有错误处理机制,有时null
自动转为 0
,很不容易发现错误,而 undefined
会转换成 NaN
。
JS
的类型转换分为:显式转换
和 隐式转换
1. 显式转换
显式转换常见的方法有:
Number
:将任意类型的值转化为数值jsNumber(324) // 324
// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324
// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN
// 空字符串转为0
Number('') // 0
// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0
// undefined:转成 NaN
Number(undefined) // NaN
// null:转成0
Number(null) // 0
// 对象:通常转换成NaN(除了只包含单个数值的数组)
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5
parseInt
:和 Number
相比,Number
会更严格一些,只要有一个字符无法转换成数值,整个字符串就会被转为 NaN
,而 parseInt
函数逐个解析字符,遇到不能转换的字符就停下来例如:jsNumber('32a3') // NaN
parseInt('32a3') // 32
String
:可以将任意类型的值转化成字符串js// 数值:转为相应的字符串
String(1) // "1"
//字符串:转换后还是原来的值
String("a") // "a"
//布尔值:true转为字符串"true",false转为字符串"false"
String(true) // "true"
//undefined:转为字符串"undefined"
String(undefined) // "undefined"
//null:转为字符串"null"
String(null) // "null"
//对象
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
Boolean
:可以将任意类型的值转为布尔值jsBoolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
在 JS 中,很多时候会发生隐式转换,我们可以归纳为两种场景:
==
、!=
、>
、<
)、if
、while
需要布尔值的地方+
、-
、*
、/
、%
)上面的场景有个前提就是运算符两边的操作数要是不同一类型的
在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用Boolean函数。
undefined
null
false
+0
-0
NaN
""
这些都会被转化成 false
,其他都换被转化成 true
遇到预期为字符串的地方,就会将非字符串的值自动转为字符串
常发生在 +
运算中,一旦存在字符串,则会进行字符串拼接操作
js'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"
除了 +
有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值
js'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN
==
叫 等于操作符===
叫 全等操作符==
在比较时会进行隐式的类型转换
js'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n' == 0 // true
比较 null
的情况的时候,我们一般使用相等操作符 ==
jsconst obj = {};
if(obj.x == null){
console.log("1"); //执行
}
等同于下面写法
jsif(obj.x === null || obj.x === undefined) {
...
}
使用相等操作符 (==
) 的写法明显更加简洁了
所以,除了在比较对象属性为 null
或者 undefined
的情况下,我们可以使用相等操作符 (==
),其他情况建议一律使用全等操作符 (===
)
let
和 const
都是 ES6
新增加了两个重要的关键字,var
是 ES6
之前就有的,
他们都是用来声明变量的,const
是用来声明一个只读的常量。
var
、let
、const
三者区别可以围绕下面五点展开:
var
可以在声明之前调用,let
和 const
不行(会报错)var
不存在跨级作用于,let
和 const
有var
允许重复声明变量,let
和 const
不行var
和 let
可以,const
不行const
的情况尽量使用 const
,其他情况下大多数使用 let
,避免使用 var
可以
数组是引用类型,const
声明的引用类型变量,不可以变的是变量引用始终指向某个对象,不能指向其他对象,但是所指向的某个对象本身是可以变的
答案是 false
不相等。
原因:0.1
和 0.2
在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成 0.30000000000000004
。
总结一句话就是:二进制模拟十进制进行计算时的精度问题
1. 第一道
jsvar a = {"x": 1};
var b = a;
a.x = 2;
console.log(b.x);
a = {"x": 3};
console.log(b.x);
a.x = 4;
console.log(b.x);
答案放在了下面,思考一下哦~
原理:
首先:第一个 log
打印 2
大家应该都没问题
然后 a
指向了栈内存中的另一块地址,而 b
没变,所以 b.x
仍然为 2
a.x = 4
,因为此时 b
仍然指向之前的地址,所以修改 a.x
并不会去影响 b
,所以打印仍然为 2
答案:2 2 2
2. 第二道:
jsvar a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a.x);
console.log(b.x);
答案在下面思考下哦~
这次我尝试一张图解释:
答案: undefined {n: 2}
三座大山之一,必考!!!
在 JS 中,原型和原型链是一个很重要的概念,可以说原型本质就是一个对象。它分为两种: 对象的原型和 函数的原型
对象的原型:
__proto__
(这个不是规范,是浏览器加的,因为早期没有获取原型对象的方法)Object.getPrototypeOf(obj)
函数的原型:
__proto__
隐示原型prototype
属性(显式原型)prototype
new
操作符调用函数时, 创建一个新的对象obj.__proto__ = F.prototype
原型链:
上面讲过,隐式原型的作用是 当前对象查找某一个属性时, 如果找不到, 会在原型上面查找,而原型也是一个对象,所以在原型对象上找不到的话,还会去原型对象的原型对象上找,这样一层一层、以此类推就形成了原型链(prototype chain
)
1. instanceof
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上
如果让你实现一个 instanceof
应该就很简单了吧?(循环遍历对象的隐式原型直到为 null
或者为 Array
)
jslet arr = [1, 2];
arr instanceof Array // true
2. toString
jslet arr = [1, 2];
Object.prototype.toString.call(arr) === '[object Array]'
3. constructor
jslet arr = [1,2];
arr.constructor === Array; // true
4. isArray
jslet arr = [1,2];
Array.isArray(arr) // true
在 ES6
中可以使用 class + extends
的方式很容易实现继承,而它的本质其实就是通过原型链来实现的
ES6
实现继承:
jsclass Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log("你好,我是" + this.name);
}
}
class Student extends Person {
constructor(name, sno) {
super(name);
this.sno = sno;
}
readingBooks() {
console.log("我正在读书");
}
}
const stu = new Student("张三", "001");
// Student 子类能调用父类的 sayHello 方法
stu.sayHello();
stu.readingBooks();
ES5
实现继承:
function Person(name) { this.name = name; } Person.prototype.sayHello = function () { console.log("你好,我是", this.name); }; function Student(name, sno) { this.name = name; this.sno = sno; } const per = new Person() Student.prototype = perper; Student.prototype.readingBooks = function () { console.log("我正在读书"); }; const stu = new Student("张三", "001"); // Student 子类能调用父类的 sayHello 方法 stu.sayHello(); stu.readingBooks();
ES5
中的继承还会复杂一些,初级面知道这些就够了,如果感兴趣,还可以看下我的这篇文章 js 常见手写题汇总 第 14
章实现继承。
下面我们用一张图来解释原型链继承的原理:
三座大山之二,不会闭包,基本不会通过
作用域 就是即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合。
我们一般将作用域分成:
ES6
引入了 let
和 const
关键字,和 var
关键字不同,在大括号中使用 let
和 const
声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。自由变量 就是一个变量在当前作用域没有定义,但被使用了。那这个时候怎么办呢?它会向上级作用域,一层一层依次寻找,知道找到为止。如果到全局作用域都没找到,则报错 xx is not defined
闭包是什么?
一个函数和对其周围状态(lexical environment
,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure
)
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
在 JavaScript
中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁
下面给出一个简单的例子:
jsfunction init() {
var name = "Mozilla"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
displayName();
}
init();
使用场景
闭包常常作为函数 参数 和 返回值:
jsfunction print(fn) {
let a = 200;
fn();
}
let a = 100;
function fn() {
console.log(a);
}
print(fn); // 100
jsfunction create() {
let a = 100;
return function () {
console.log(a);
};
}
let fn = create();
let a = 200;
fn(); // 100
任何闭包的使用场景都离不开这两点:
一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的
使用闭包的注意点
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE
中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object
)使用,把闭包当作它的公用方法(Public Method
),把内部变量当作它的私有属性(private value
),这时一定要小心,不要随便改变父函数内部变量的值。
this
关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。
根据不同的使用场合,this
有不同的值,主要分为下面几种情况:
1. 默认绑定:
什么情况下使用默认绑定呢?独立函数调用。
案例一:普通函数调用
this
指向全局对象(window
);jsfunction foo() {
console.log(this); // window
}
foo();
案例二:函数调用链(一个函数又调用另外一个函数)
js// 2.案例二:
function test1() {
console.log(this); // window
test2();
}
function test2() {
console.log(this); // window
test3()
}
function test3() {
console.log(this); // window
}
test1();
案例三:将函数作为参数,传入到另一个函数中
jsfunction foo(func) {
func()
}
function bar() {
console.log(this); // window
}
foo(bar);
我们对案例进行一些修改,考虑一下打印结果是否会发生变化:
window
,为什么呢?jsfunction foo(func) {
func()
}
var obj = {
name: "why",
bar: function() {
console.log(this); // window
}
}
foo(obj.bar);
2. 隐式绑定:
另外一种比较常见的调用方式是通过某个对象进行调用的:
案例一:通过对象调用函数
foo
的调用位置是 obj.foo()
方式进行调用的foo
调用时 this
会隐式的被绑定到 obj
对象上jsfunction foo() {
console.log(this); // obj对象
}
var obj = {
name: "why",
foo: foo
}
obj.foo();
案例二:案例一的变化
obj2
又引用了 obj1
对象,再通过 obj1
对象调用 foo
函数;foo
调用的位置上其实还是 obj1
被绑定了 this
;jsfunction foo() {
console.log(this); // obj1 对象
}
var obj1 = {
name: "obj1",
foo: foo
}
var obj2 = {
name: "obj2",
obj1: obj1
}
obj2.obj1.foo();
案例三:隐式丢失
结果最终是 window
,为什么是 window
呢?
foo
最终被调用的位置是 bar
,而 bar
在进行调用时没有绑定任何的对象,也就没有形成隐式绑定;jsfunction foo() {
console.log(this);
}
var obj1 = {
name: "obj1",
foo: foo
}
// 讲obj1的foo赋值给bar
var bar = obj1.foo;
bar();
3. 显示绑定
隐式绑定有一个前提条件:
对象内部
有一个对函数的引用(比如一个属性);如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
JavaScript
所有的函数都可以使用 call
和 apply
方法(这个和 Prototype
有关)。
apply
为数组,call
为参数列表;this
准备的。this
绑定到这个传入的对象上。因为上面的过程,我们明确的绑定了 this
指向的对象,所以称之为 显示绑定。
案例一:call
、apply
通过 call
或者 apply
绑定 this
对象
this
就会明确的指向绑定的对象jsfunction foo() {
console.log(this);
}
foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number对象,存放 123
案例二:bind
函数
如果我们希望一个函数总是显示的绑定到一个对象上,我们可以使用 bind
函数:
function foo() { console.log(this); } var obj = { name: "why" } var bar = foo.bind(obj); bar(); // obj对象 bar(); // obj对象 bar(); // obj对象
案例三:内置函数
有些时候,我们会调用一些 JavaScript
的内置函数,或者一些第三方库中的内置函数。
JavaScript
内部或者第三方库内部会帮助我们执行;this
又是如何绑定的呢?js// 1. setTimeout中会传入一个函数,这个函数中的this通常是window
setTimeout(function() {
console.log(this); // window
}, 1000);
// 2. forEach map filter 等高阶函数 this 通常指向 window对象,但我们可以通过第二个参数改变
var names = ["abc", "cba", "nba"];
names.forEach(function(item) {
console.log(this); // 三次window
});
var names = ["abc", "cba", "nba"];
var obj = {name: "why"};
names.forEach(function(item) {
console.log(this); // 三次obj对象
}, obj);
4. new
绑定
JavaScript
中的函数可以当做一个类的构造函数来使用,也就是使用 new
关键字。
使用 new
关键字来调用函数时,会执行如下的操作:
1.创建一个全新的对象;
2.这个新对象会被执行 Prototype
连接;
3.这个新对象会绑定到函数调用的 this
上 (this
的绑定在这个步骤完成);
4.如果函数没有返回其他对象,表达式会返回这个新对象;
js// 创建Person
function Person(name) {
console.log(this); // Person {}
this.name = name; // Person {name: "why"}
}
var p = new Person("why");
console.log(p);
5. 优先级
new绑定
> 显示绑定(bind)
> 隐式绑定
> 默认绑定
PS: new
绑定后可以使用 bind
但是 bind
不会生效。 new
绑定后使用 call
和 apply
会报错。
5. 规则之外
bind
绑定一个 null
或者 undefined
无效jsfunction foo() {
console.log(this);
}
var obj1 = {
name: "obj1",
foo: foo
};
var obj2 = {
name: "obj2"
}
obj1.foo(); // obj1对象
// 赋值(obj2.foo = obj1.foo)的结果是foo函数
// foo函数被直接调用,那么是默认绑定;
(obj2.foo = obj1.foo)(); // window
ES6
箭头函数:箭头函数不使用 this
的四种标准规则(也就是不绑定 this
),而是根据外层作用域来决定 this
。三座大山之三,必考!!!
JS
是一门单线程的编程语言,这就意味着一个时间里只能处理一件事,也就是说 JS
引擎一次只能在一个线程里处理一条语句。(浏览器和 nodejs
已经支持 js
启动进程,如 web worker
)
虽然单线程简化了编程代码,因为这样咱们不必太担心并发引出的问题,这也意味着在阻塞主线程的情况下执行长时间的操作,如网络请求。
想象一下从 API
请求一些数据,根据具体的情况,服务器需要一些时间来处理请求,同时阻塞主线程,使网页长时间处于无响应的状态。这就是引入异步 JS
的原因。使用异步 (如 回调函数、promise
、async/await
),可以不用阻塞主线程的情况下长时间执行网络请求。
总结:基于 js
单线程本质,同步会阻塞代码执行,异步不会阻塞代码执行。
ajax
加载图片setTimeout
1. 介绍
Promise
,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大。
Promise
的出现主要解决了用回调函数处理多层异步操作出现的回调地狱问题
js// 1. 经典的回调地狱
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('得到最终结果: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
// 2. 链式操作减低了编码难度 代码可读性明显增强
doSomething().then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback);
promise
对象仅有三种状态:pending(进行中)
fulfilled(已成功)
rejected(已失败)
pending
变为 fulfilled
和从 pending
变为 rejected
),就不会再变,任何时候都可以得到这个结果2. 用法
创建一个 Promise
js// 1. Promise对象是一个构造函数,用来生成Promise实例
// resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
// reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”
const promise = new Promise(function(resolve, reject) {});
实例方法:
then()
:then
是实例状态发生改变时的回调函数,第一个参数是 resolved
状态的回调函数,第二个参数是 rejected
状态的回调函数catch()
:catch()
方法是 .then(null, rejection)
或 .then(undefined, rejection)
的别名,用于指定发生错误时的回调函数finally()
:finally()
方法用于指定不管 Promise
对象最后状态如何,都会执行的操作构造函数方法:
ps:all
race
allSettled
这三个方法都能将多个 promise
实例转换为 1
个 promise
实例,区别就是 all
只有当所有实例状态都变为 fulfilled
最终返回的实例状态才会变为 fulfilled
。race
则返回最快改变状态的实例,allSettled
则会等所有实例状态改变才会改变。
jsconst preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = () => { resolve(image) };
image.onerror = () => {
const err = new Error(`图片加载失败${path}`)
reject(err)
};
image.src = path;
});
};
关于异步还有更多的问题,很重要
js
是单线程的,为了防止代码阻塞会将代码分成同步和异步js
引擎执行,异步代码交给宿主环境(浏览器、node)event loop
)上题讲到 js
将代码分成同步和异步,而在异步任务中又分 宏任务(macro-task
)和微任务(micro-task
)。 宏任务是由宿主(浏览器、node
)发起的,微任务是由 js
引擎发起的
宏任务大概包括
微任务大概包括
宏任务和微任务的执行过程
如下图所示:
下面有个小例子:
jssetTimeout(() => {
console.log(1);
});
Promise.resolve().then(() => {
console.log(2);
});
console.log(3);
代码最终执行: 3 2 1
考异步代码执行顺序的题目有很多,这里推荐一个 练习事件循环的网站
用法很简单,这里不说了。
你可以把 await
后面的代码理解为是放在 Promise.then
中,看上去相当于将链式的调用变成了同步的执行
关于async/await 的本质 最好去理解一下,其实就是 Promise
和 Generator
的语法糖
详见 JavaScript 常见手写题汇总 第 15
章
学会DOM,才能具备网页开发的基础
DOM
:Document Object Model 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
DOM 的本质就是一颗树
获取 DOM
节点
DOM
节点的 property
通过 js
对象属性的方式来获取或修改 DOM
节点
jsconst pList = document.querySelectorAll('p')
const p = pList[0]
console.log(p.style.width) // 获取样式
p.style.width = '100px' // 修改样式
console.log(p.className) // 获取 class
p.className = 'p1' // 修改 class
// 获取 nodeName 和 nodeType
console.log(p.nodeName)
console.log(p.nodeType)
DOM
节点的 attribute
通过 getAttribute
setAttribute
这种 API
的方式来修改 DOM
节点
jsconst pList = document.querySelectorAll('p')
const p = pList[0]
p.setAttribute('data-name', 'coder') // 设置值
console.log(p.getAttribute('data-name')) // 获取值
property
和 attribute
形式都可以修改节点的属性,但是对于新增或删除的自定义属性,能在 html
的 dom
树结构上体现出来的,就必须要用到 attribute
形式了。
新增插入节点:
html<div id="div1">div1</div>
<div id="div2">div2</div>
<p id="p2">这是 p2 标签</p>
<script>
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");
// 新建节点
const p1 = document.createElement("p");
p1.innerHTML = "这是新的 p 标签";
// 插入节点
div1.appendChild(p1);
// 移动节点
const p2 = document.getElementById("p2");
div2.appendChild(p2);
</script>
获取子元素列表,获取父元素
js// 获取父元素
console.log(p1.parentNode);
// 获取子元素列表
console.log(div2.childNodes);
删除子元素
js// 删除子元素
div2.removeChild(div2.childNodes[0]);
DOM
操作非常昂贵,避免频繁的 DOM
操作
DOM
操作做缓存DOM
操作改为一次性操作内容虽然不多,但是你不能不会
BOM
:Browser Object Model 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM
的核心是 window
,而 window
对象具有双重角色,它既是通过 js
访问浏览器窗口的一个接口,又是一个 Global
(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window
对象含有 location
对象、navigator
对象、screen
对象等子对象,并且 DOM
的最根本的对象 document
对象也是 BOM
的 window
对象的子对象。
navigator.userAgent
简称 ua,可以从 ua 里拿到浏览器信息
location:
事件不会,等于残废,必考!必考!
事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。
html<div id="outer">
<p id="inner">Click me!</p>
</div>
上面的的两个元素,如果点击 inner,他们的执行顺序是什么呢?
其实这个很好理解,冒泡就是从一个泡泡从水底往上冒当然是里面的先执行啦。
至于为什么会有这两种情况,这就要谈到网景和微软的战争了,两家公司的理念不同。网景主张捕获方式,微软主张冒泡方式。后来 w3c
将两者都保留了下来。
addEventListener
的第三个参数就是为冒泡和捕获准备的。第三个参数设置为 true
可以将让当前元素绑定的事件先于里面的元素绑定事件执行。默认是 false
事件代理(Event Delegation
)也称之为事件委托。是 JavaScript
中常用绑定事件的常用技巧。
顾名思义,事件代理
即是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务。
事件代理的原理是DOM元素的事件冒泡。
每个工程师必须熟练掌握的技能
Ajax
(全称 Asynchronous JavaScript And XML
) 翻译过来就是 异步的 Javascript 和 XML
AJAX
是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
随着谷歌搜索建议功能在 2005 的发布,AJAX 开始流行起来。
网页中实现 Ajax
最核心的 API
就是 XMLHttpRequest
,如果不知道这个就别谈实现了。
jsfunction ajax(url) {
const xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.onreadystatechange = function () {
// 异步回调函数
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.info("响应结果", xhr.response)
}
}
}
xhr.send(null);
}
同源策略(Same origin policy
)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web
是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
它的核心就在于它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。
所谓同源是指:域名、协议、端口相同。
另外,同源策略又分为以下两种:
DOM
同源策略:禁止对不同源页面 DOM
进行操作。这里主要场景是 iframe
跨域的情况,不同域名的 iframe
是限制互相访问的。XMLHttpRequest
同源策略:禁止使用 XHR
对象向不同源的服务器地址发起 HTTP
请求。跨域本质是浏览器基于同源策略的一种安全手段
同源策略(Sameoriginpolicy
),是一种约定,它是浏览器最核心也最基本的安全功能
所谓同源(即指在同一个域)具有以下三个相同点
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用
postman
请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。
首先跨域是因为浏览器的同源策略造成的,他是浏览器的一种安全机制。跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了
实现跨域常见的方案:
其中 jsonp
由于其局限性,以及对比其他方案的效果。此处不做介绍。
1. nginx方向代理
nginx
反向代理常用在开发环境及线上环境。通过拦截转发请求来处理跨域问题。
假如现在前端项目运行在 8080
端口,而实际后端项目的地址为 https://1.1.1.1:9000
,需要拦截前缀为 api
的请求,此时 nginx
配置为:
shserver { listen 8080 default_server; location /api { proxy_pass https://1.1.1.1:9000; } }
假如现在有个接口为 /api/test
,在没有做转发前为 http://localhost:8080/api/test
,实际接口位置为 https://1.1.1.1:9000/api/test
.结果转发为实际接口位置。
2. webpack devserver代理
webpack devserver
代理用在开发环境。
配置如下:
jsdevServer({
proxy: {
'/api': {
target: 'https://1.1.1.1:9000',
changeOrigin: true,
pathRewrite: { '^/api': 'api' },
},
}
})
3. cors跨源资源共享(服务端设置)
跨源资源共享 (CORS
) (或通俗地译为跨域资源共享)是一种基于 HTTP
头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin
(域,协议和端口),这样浏览器可以访问加载这些资源。
Access-Control-Allow-Origin: 'xxx'
可以通过服务端设置 Access-Control-Allow-Origin
字段的白名单来处理跨域问题。
如果在此情况下,发送请求时需要带上cookie的话,则需要配置Access-Control-Allow-Credentials,同时客户端需要同步设置xhr.withCredentials = true;,两者缺一不可
ajax
是 js
异步技术的术语,早期相关的 api
是 xhr
,它是一个术语。fetch
是 es6
新增的用于网络请求标准 api
,它是一个 api
。(是 es6
用来代替 xhr
的, xhr
很不好用)axios
是用于网络请求的第三方库,它是一个库。内容虽然不多,但不可不会
cookie
本身是用于浏览器和 server
通讯的,他是被借用到本地用于存储的,因为后两者是在 H5
后才提出来的( 2010年左右),我们可以通过 document.cookie = 'xxx'
来改变 cookie
。
cookie
的缺点:
4kb
(因为 cookie
本身就不是用来做存储的)http
请求时需要发送到服务端,增加请求数据量document.cookie
来修改,太过简陋localStorage
和 sessionStorage
:
H5
专门为了存储设计的,最大可存 5M
左右API
更简洁:getItem
setItem
http
被发送出去localStorage
数据会永久存储,除非代码或者手动删除, sessionStorage
数据只存在于房钱会话,浏览器关闭则清除。一般 localStorage
会更多一些前后端分离的时代,网络请求是前端的生命线
分类:
常见状态码:
Continue
情景:客户端向服务端传很大的数据,这个时候询问服务端,如果服务端返回100,客户端就继续传 (历史,现在比较少了)Switching Protocols
协议切换。比如下面这种:HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade
告诉客户端把协议切换为 Websocket
Ok
正常的返回成功 通常用在 GET
Created
已创建 通常用在 POST
Accepted
已接收 比如发送一个创建 POST
请求,服务端有些异步的操作不能马上处理先返回 202
,结果需要等通知或者客户端轮询获取Non-Authoritative Infomation
非权威内容 原始服务器的内容被修改过No Content
没有内容 一般 PUT
请求修改了但是没有返回内容Reset Content
重置内容Partial Content
服务端下发了部分内容Multiple Choices
用户请求了多个选项的资源(返回选项列表)Moved Permanently
永久转移Found
资源被找到(以前是临时转移)不推荐用了 302
拆成了 303
和 307
See Other
可以使用 GET
方法在另一个 URL
找到资源Not Modified
没有修改Use Proxy
需要代理Temporary Redirect
临时重定向 (和 303
的区别是,307
使用原请求的method
重定向资源, 303
使用 GET
方法重定向资源)Permanent Redirect
永久重定向 (和 301
区别是 客户端接收到 308
后,之前是什么 method
,之后也会沿用这个 method
到新地址。301
,通常给用户会向新地址发送 GET
请求)Bad Request
请求格式错误Unauthorized
没有授权Payment Required
请先付费Forbidden
禁止访问Not Found
没有找到Method Not Allowed
方法不允许Not Acceptable
服务端可以提供的内容和客户端期待的不一样Internal Server Error
内部服务器错误Not Implemented
没有实现Bad Gateway
网关错误Service Unavailable
服务不可用 (内存用光了,线程池溢出,服务正在启动)Gateway Timeout
网关超时HTTP Version Not Supported
版本不支持面试的时候常见该记住的有:101
200
201
301
302
304
403
404
500
502
504
规范就是一个约定,要求大家跟着执行,不要违反规范,例如 IE
浏览器
1. Content-Length
发送给接收者的 Body 内容长度(字节)
示例:Content-Length: 348
2. User-Agent
帮助区分客户端特性的字符串
示例:User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
3. Content-Type
帮助区分资源的媒体类型(Media Type/MIME Type)
示例:Content-Type: application/x-www-form-urlencoded
4. Origin
描述请求来源地址
示例: Origin: https://yewjiwei.com
5. Accept
建议服务端返回何种媒体类型(MIME Type)
示例:
Accept: text/plain
Accept-Charset: utf-8
Accept-Encoding: gzip, deflate
6. Referer
告诉服务端打开当前页面的上一张页面的URL;如果是ajax请求那么就告诉服务端发送请求的URL是什么
7. Connection
决定连接是否在当前事务完成后关闭
Restful API
是一种新的 API
设计方法(早已推广)
API
设计:把每一个 url
当做一个功能Restful API
设计:把每个 url
当做一个唯一的资源传统
/api/list?pageIndex=2
Restful API
/api/list/2
HTTP
缓存即是浏览器第一次向一个服务器发起 HTTP
请求后,服务器会返回请求的资源,并且在响应头中添加一些有关缓存的字段如:cache-control
,expires
, last-modifed
,ETag
, Date
,等,之后浏览器再向该服务器请求资源就可以视情况使用强缓存和协商缓存,
本文作者:叶继伟
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!