前言
为了方便现在和以后前端学习和面试,在此收集和整理了Js相关的笔试面试题,供自己查阅的同时,希望也会对大家有所帮助。
目录:
前端HTML+CSS笔试题面试题
前端Vue笔试题面试题
前端小程序笔试题面试题
数据类型
JS的基本数据类型
Undefined、Null、Boolean、Number、String
新增:Symbol
JS有哪些内置对象?
Object
是 JavaScript
中所有对象的父对象
数据封装类对象:Object、Array、Boolean、Number
和 String
其他对象:Function、Arguments、Math、Date、RegExp、Error
JS中使用typeof能得到的哪些类型?(考点:JS变量类型)
typeof undefinded //undefined
typeof null // object
typeof'abc' // string
typeof 123 // number
typeof [] // object
typeof {} // object
typeof true //boolean
typeof b // b 没有声明,但是还会显示 undefined
何时使用===
何时使用==
?(考点:强制类型转换)
==
比较两个值相等返回ture
===
比较两者的值和类型都相等才返回true
严格相等
0,NAN,null,undefinded,'',false
在if
语句中会强制转化为 false
if (obj.a == null) {
// 这里相当于 obj.a === null || obj.a ===undefinded, 简写形式
// 这里jquery 源码中推荐的写法
}
null,undefined
的区别?
1、null
: Null
类型,代表“空值”,代表一个空对象指针,使用typeof
运算得到 “object”
,所以你可以认为它是一个特殊的对象值。
2、undefined
: Undefined
类型,当一个声明了一个变量未初始化时,得到的就是undefined
。
ps:一句话简单来说null
和undefine
值比较是相等,但类型不同。
Javascript创建对象的几种方式?
1、对象字面量的方式
var = {}
2、通过构造函数方式创建。
var obj = new Object();
3、通过Object.create()
方式创建。
var obj = Object.create(Object.prototype);
翻转一个字符串
可以先将字符串转成一个数组,然后用数组的reverse()+join()
方法。
let str="hello word";
let b=[...str].reverse().join("");//drow olleh
描述new
一个对象的过程
1、创建一个新对象
2、this
指向这个对象
3、执行代码,即对this
赋值
4、隐式返回this
JS按存储方式区分变量类型
// 值类型
var a = 10
var b = a
a = 11
console.log(b) // 10
// 引用类型
var obj1 = {x : 100}
var obj2 = obj1
obj1.x = 200
console.log(obj2) // 200
如何判断一个变量是对象还是数组?
1、instanceof
方法
instanceof
运算符是用来测试一个对象是否在其原型链原型构造函数的属性
var arr = [];
arr instanceof Array; // true
2、constructor
方法
constructor
属性返回对创建此对象的数组函数的引用,就是返回对象相对应的构造函数
var arr = [];
arr.constructor == Array; //true
3、最简单的方法,这种写法兼容性最好使用Object.prototype.toString.call()
function isObjArr(value){
if (Object.prototype.toString.call(value) === "[object Array]") {
console.log('value是数组');
}else if(Object.prototype.toString.call(value)==='[object Object]'){//这个方法兼容性好一点
console.log('value是对象');
}else{
console.log('value不是数组也不是对象')
}
}
4、ES5
新增方法isArray()
var a = new Array(123);
var b = new Date();
console.log(Array.isArray(a)); //true
console.log(Array.isArray(b)); //false
ps:千万不能使用typeof
来判断对象和数组,因为这两种类型都会返回"object"
。
如何对一个数组去重?
1、Set
结构去重(ES6
用法)。
ES6
提供了新的数据结构Set
。它类似于数组,但是成员的值都是唯一的,没有重复的值。
[...new Set(array)];
2、遍历,将值添加到新数组,用indexOf()
判断值是否存在,已存在就不添加,达到去重效果。
let a = ['1','2','3',1,NaN,NaN,undefined,undefined,null,null, 'a','b','b'];
let unique= arr =>{
let newA=[];
arr.forEach(key => {
if( newA.indexOf(key)<0 ){ //遍历newA是否存在key,如果存在key会大于0就跳过push的那一步
newA.push(key);
}
});
return newA;
}
console.log(unique(a)) ;//["1", "2", "3", 1, NaN, NaN, undefined, null, "a", "b"]
// 这个方法不能分辨NaN,会出现两个NaN。是有问题的,下面那个方法好一点。
3、利用for
嵌套for
,然后splice
去重(ES5
中最常用)。
function unique(arr){
for(var i=0; i
4、forEach
遍历,然后利用Object.keys(对象)
返回这个对象可枚举属性组成的数组,这个数组就是去重后的数组。
let a = ['1', '2', '3', 1,NaN,NaN,undefined,undefined,null,null, 'a', 'b', 'b'];
const unique = arr => {
var obj = {}
arr.forEach(value => {
obj[value] = 0;//这步新添加一个属性,并赋值,如果不赋值的话,属性会添加不上去
})
return Object.keys(obj);//`Object.keys(对象)`返回这个对象可枚举属性组成的数组,这个数组就是去重后的数组
}
console.log(unique(a));//["1", "2", "3", "NaN", "undefined", "null", "a", "b"]
作用域和闭包
var、let、const
之间的区别
var
声明变量可以重复声明,而let
不可以重复声明
var
是不受限于块级的,而let
是受限于块级
var
会与window
相映射(会挂一个属性),而let
不与window
相映射
var
可以在声明的上面访问变量,而let
有暂存死区,在声明的上面访问变量会报错
const
声明之后必须赋值,否则会报错
const
定义不可变的量,改变了就会报错
const
和let
一样不会与window
相映射、支持块级作用域、在声明的上面访问变量会报错
说明This
几种不同的使用场景
1、作为构造函数执行
2、作为对象属性执行
3、作为普通函数执行
4、call apply bind
谈谈This
对象的理解
1、this
总是指向函数的直接调用者(而非间接调用者)
2、如果有new
关键字,this
指向new
出来的那个对象
3、在事件中,this
指向触发这个事件的对象,特殊的是,IE
中的attachEvent
中的this
总是指向全局对象Window
作用域
ES5
作用域分为 全局作用域 和 函数作用域。
ES6
新增块级作用域,块作用域由 { }
包括,if
语句和 for
语句里面的{ }
也属于块作用域。
// 块级作用域
if (true) {
var name = 'zhangsan'
}
console.log(name)
// 函数和全局作用域
var a = 100
function fn () {
var a = 200
console.log('fn', a)
}
用JS
创建10个
标签,点击的时候弹出来对应的序号?(考点:作用域)
var i
for (i = 0; i < 10; i++) {
(function (i) {
var a = document.createElement('a')
a.innerHTML = i + '
'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
})(i)
}
说说你对作用域链的理解
1、作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window
对象即被终止,作用域链向下访问变量是不被允许的
2、简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期
3、通俗来说,一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。
但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链
var a = 100
function fn () {
var b = 200
// 当前作用域没有定于的变量,即‘自由变量’
console.log(a)
console.log(b)
}
fn()
console.log(a) 去父级作用域找a自由变量 作用域链
闭包
闭包就是能够读取其他函数内部变量的函数
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域
function F1 () {
var a = 100
// 返回一个函数,函数作为返回值
return function () {
console.log(a)
}
}
// f1 得到一个函数
var f1 = F1()
var a = 200
f1() // 100 定义的时候父级作用域
闭包的特性
函数内再嵌套函数
内部函数可以引用外层的参数和变量
参数和变量不会被垃圾回收机制回收
说说你对闭包的理解
使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中
闭包的另一个用处,是封装对象的私有属性和私有方法
好处:能够实现封装和缓存等;
坏处:就是消耗内存、不正当使用会造成内存溢出的问题
使用闭包的注意点
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露
解决方法是,在退出函数之前,将不使用的局部变量全部删除
闭包的使用场景
闭包的两个场景:函数作为返回值 和 函数作为参数传递
function F1 () {
var a = 100
// 返回一个函数,函数作为返回值
return function () {
console.log(a)
}
}
// f1 得到一个函数
var f1 = F1()
var a = 200
f1() // 100 定义的时候父级作用域
function F2 (fn) {
var a = 200
fn()
}
F2(f1)
实际开发中闭包的应用
// 闭包实际应用中主要用于封装变量 收敛权限
function isFirstLoad() {
var _list = []
return function (id) {
if (_list.indexOf(id) >= 0) {
return false
} else {
_list.push(id)
return true
}
}
}
// 使用
var firstLoad = new isFirstLoad()
firstLoad(10) //true
firstLoad(10) //false
firstLoad(30) //true
原型和原型链
JavaScript原型,原型链 ? 有什么特点?
每个对象都会在其内部初始化一个属性,就是prototype
(原型),
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype
里找这个属性,这个prototype
又会有自己的prototype
,于是就这样一直找下去,也就是我们平时所说的原型链的概念
原型和原型链关系
关系:instance.constructor.prototype = instance.__proto__
原型和原型链特点
JavaScript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
当我们需要一个属性的时,Javascript
引擎会先看当前对象中是否有这个属性, 如果没有的
就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object
内建对象
PS:
1.所有的引用类型(数组,对象,函数)都具有对象的特性,即可自由扩展属性(除了‘null
’)除外
2.所有的引用类型(数组,对象,函数)都有一个_proto_
(隐式原型)属性,属性值是一个普通对象
3.所有的函数,都有一个prototype
(显示原型)的属性,也是一个普通对象
4.所有的引用类型(数组,对象,函数)_proto_
属性值指向他的构造的‘prototype
’ 属性值
当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_proto_
(即它的构造函数的prototype
) 中寻找
// 构造函数
function Foo(name, age){
this.name = name
}
Foo.prototype.alertName = function () {
alert(this.name)
}
//创建实例
var f = new Foo('zhangsan')
f.printName = function () {
console.log(this.name)
}
//测试
f.printName()
f.alertName()
循环对象自身的属性
var item
for (item in f) {
// 高级浏览器已经在 for in 中屏蔽了来自原型的属性
// 但是这里建议大家还是加上这个判断,保证程序的健壮性
if (f.hasOwnProperty(item)) {
console.log(item)
}
}
原型链
f.toString() //到f._proto_._proto_ 中去找
异步和单线程
同步和异步的区别是什么?分别举一个同步和异步的例子
同步交互
:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程。
异步交互
:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。
相同的地方
:都属于交互方式,都是发送请求。
不同的地方
:一个需要等待,一个不需要等待。
简单而言,同步就是必须一件一件的做事,等前一件事做完后才能做下一件事。而异步这是把事情指派给别人后,接着继续做下一件事,不必等别人返回的结果。
举例:
1、广播,就是一个异步例子。发起者不关心接收者的状态。不需要等待接收者的返回信息;
在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。
2、电话,就是一个同步例子。发起者需要等待接收者,接通电话后,通信才开始。需要等待接收者的返回信息
比如银行的转账系统,对数据库的保存操作等等,都会使用同步交互操作。
ps: alert
是同步,setTimeout
和setInterval
是异步,同步会阻塞代码执行,而异步不会
异步编程的实现方式?
1、回调函数
优点:简单、容易理解
缺点:不利于维护,代码耦合高
2、事件监听(采用时间驱动模式,取决于某个事件是否发生):
优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
缺点:事件驱动型,流程不够清晰
3、发布/订阅(观察者模式)
类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者
4、Promise对象
优点:可以利用then
方法,进行链式写法;可以书写错误时的回调函数;
缺点:编写和理解,相对比较难
5、Generator函数
优点:函数体内外的数据交换、错误处理机制
缺点:流程管理不方便
6、async函数
优点:内置执行器、更好的语义、更广的适用性、返回的是Promise
、结构清晰。
缺点:错误处理机制
定时器的执行顺序或机制。
简单来说:因为js
是单线程的,浏览器遇到setTimeout
或者setInterval
会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码之后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。 所以即使把定时器的时间设置为0
还是会先执行当前的一些代码。
一个关于setTimeout
的笔试题
console.log(1)
setTimeout(function () {
console.log(2)
},0)
console.log(3)
setTimeout(function () {
console.log(4)
},1000)
console.log(5)
//结果为 1 3 5 2 4
前段使用异步的场景
1、定时任务:setTimeout,setInverval
2、网络请求:ajax
请求,动态加载
3、事件绑定
数组和对象API
map与forEach的区别?
1、forEach
方法,是最基本的方法,就是遍历与循环,默认有3个传参:分别是遍历的数组内容item
、数组索引index
、和当前遍历数组Array
2、map
方法,基本用法与forEach
一致,但是不同的,它会返回一个新的数组,所以在callback
需要有return
值,如果没有,会返回undefined
JS 数组和对象的遍历方式,以及几种方式的比较
通常我们会用循环的方式来遍历数组。但是循环是 导致js 性能问题的原因之一。一般我们会采用下几种方式来进行数组的遍历
for in循环
for循环
forEach
1、这里的 forEach
回调中两个参数分别为 value,index
2、forEach
无法遍历对象
3、IE
不支持该方法;Firefox
和 chrome
支持
4、forEach
无法使用 break,continue
跳出循环,且使用 return
是跳过本次循环
5、可以添加第二个参数,为一个数组,回调中的this会指向这个数组,若没有添加,则是指向window
;
在方式一中,for-in
需要分析出array
的每个属性,这个操作性能开销很大。用在 key
已知的数组上是非常不划算的。所以尽量不要用for-in
,除非你不清楚要处理哪些属性,例如JSON
对象这样的情况
在方式2中,循环每进行一次,就要检查一下数组长度。读取属性(数组长度)要比读局部变量慢,尤其是当array
里存放的都是 DOM
元素,因为每次读取都会扫描一遍页面上的选择器相关元素,速度会大大降低
写一个能遍历对象和数组的通用forEach
函数
function forEach(obj, fn) {
var key
// 判断类型
if (obj instanceof Array) {
obj.forEach(function (item, index) {
fn(index, item)
})
} else {
for (key in obj) {
fn ( key, obj[key])
}
}
}
数组API
map: 遍历数组,返回回调返回值组成的新数组
forEach: 无法break,可以用try/catch中throw new Error来停止
filter: 过滤
some: 有一项返回true,则整体为true
every: 有一项返回false,则整体为false
join: 通过指定连接符生成字符串
push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项【有误】
unshift / shift: 头部推入和弹出,改变原数组,返回操作项【有误】
sort(fn) / reverse: 排序与反转,改变原数组
concat: 连接数组,不影响原数组, 浅拷贝
slice(start, end): 返回截断后的新数组,不改变原数组
splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组
indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)
对象API
var obj = {
x: 100,
y: 200,
z: 300
}
var key
for (key in obj) {
// 注意这里的 hasOwnProperty,再讲原型链的时候讲过
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key])
}
}
日期和随机数
获取2020-06-10
格式的日期
Date.now() // 获取当前时间毫秒数
var dt = new Date()
dt.getTime() //获取毫秒数
dt.getFullYear() // 年
dt.getMonth() // 月 (0-11)
dt.getDate() // 日 (0-31)
dt.getHours() // 小时 (0-23)
dt.getMinutes() //分钟 (0-59)
dt.getSeconds() //秒 (0-59)
ps: ES6
新增padStart(),padEnd()
方法可用来返回06
格式日期
获取随机数,要求是长度一致的字符串格式
var random = Math.random()
var random = random + '0000000000'
console.log(random)
var random = random.slice(0, 10)
console.log(random)
DOM
和BOM
操作
DOM
是哪种基本的数据结构
DOM
是一种树形结构的数据结构
DOM
操作的常用API
有哪些
1、获取DOM
节点,以及节点的property
和Attribute
2、获取父节点,获取子节点
3、新增节点,删除节点
DOM
节点的Attribute
和property
有何区别
property
只是一个JS
对象的属性修改和获取
Attribute
是对html
标签属性的修改和获取
DOM
的节点操作
创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
添加、移除、替换、插入
appendChild() //添加
removeChild() //移除
replaceChild() //替换
insertBefore() //插入
查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性
如何检测浏览器的类型
可以通过检测navigator.userAgent
在通过不通浏览器的不通来检测
var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)
拆解url
的各部分
使用location
里面的location.href location.protocol location.pathname location.search location.hash
来获取各种参数
console.log(location.href)
console.log(location.host) //域名
console.log(location.protocol)
console.log(location.pathname) //路径
console.log(location.search) // 参数
console.log(location.hash)
// history
history.back()
history.forward()
事件机制
请解释什么是事件代理
事件代理(Event Delegation
),又称之为事件委托。是 JavaScript
中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM
元素的事件冒泡。
使用事件代理的好处是:
1、可以提高性能
2、可以大量节省内存占用,减少事件注册,比如在table
上代理所有td
的click
事件就非常棒
3、可以实现当新增子对象时无需再次对其绑定
事件模型
W3C
中定义事件的发生经历三个阶段:捕获阶段(capturing)、目标阶段(targetin)、冒泡阶段(bubbling)
冒泡型事件:
当你使用事件冒泡时,子级元素先触发,父级元素后触发
捕获型事件:
当你使用事件捕获时,父级元素先触发,子级元素后触发
DOM事件流:
<时支持两种事件模型:捕获型事件和冒泡型事件
阻止冒泡:
在W3c
中,使用stopPropagation()
方法;在IE
下设置cancelBubble = true
阻止捕获:
阻止事件的默认行为,例如click - 后
的跳转。在W3c
中,使用preventDefault()
方法,在IE
下设置window.event.returnValue = false
编写一个通用的事件监听函数
function bindEvent(elem,type,selector,fn){
if(fn==null){
fn = selector;
selector = null
}
elem.addEventListener(type,function(e){
var target;
if(selector){
target = e.target;
if(target.matches(selector)){
fn.call(target,e)
}
}else{
fn(e)
}
})
}
描述事件冒泡流程
当给某元素绑定一个事件的时候,首先会触发自己绑定的,然后会逐层向上级查找事件,这就是事件冒泡
var p1 = document.getElementById('p1')
var body = document.body
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
bindEvent(p1, 'click', function (e) {
e.stopPropagation()
var target = e.target
alert("激活")
})
bindEvent(body, 'click', function (e) {
var target = e.target
alert("取消")
})
对于一个无限下拉加载图片的页面,如何给每个图片绑定事件
可以使用代理,通过对父级元素绑定一个事件,通过判断事件的target
属性来进行判断,添加行为
var div1 = document.getElementById('div1')
var div2 = document.getElementById('div2')
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
bindEvent(div1, 'click', function (e) {
var target = e.target
alert(target.innerHTML)
})
bindEvent(div2, 'click', function (e) {
var target = e.target
alert(target.innerHTML)
})
AJAX
Ajax
原理
Ajax
的原理简单来说是在用户和服务器之间加了—个中间层(AJAX
引擎),通过XmlHttpRequest
对象来向服务器发异步请求,从服务器获得数据,然后用javascript
来操作DOM
而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
Ajax
的过程只涉及JavaScript、XMLHttpRequest和DOM
。XMLHttpRequest
是ajax的核心机制
手动编写一个ajax
,不依赖第三方库
// 1. 创建连接
var xhr = null;
xhr = new XMLHttpRequest()
// 2. 连接服务器
xhr.open('get', url, true)
// 3. 发送请求
xhr.send(null);
// 4. 接受请求
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
success(xhr.responseText);
} else { // fail
fail && fail(xhr.status);
}
}
}
ajax
的优点和缺点
ajax的优点
1、无刷新更新数据(在不刷新整个页面的情况下维持与服务器通信)
2、异步与服务器通信(使用异步的方式与服务器通信,不打断用户的操作)
3、前端和后端负载均衡(将一些后端的工作交给前端,减少服务器与宽度的负担)
4、界面和应用相分离(ajax
将界面和应用分离也就是数据与呈现相分离)
ajax的缺点
1、ajax不支持浏览器back按钮
2、安全问题 Aajax
暴露了与服务器交互的细节
3、对搜索引擎的支持比较弱
4、破坏了Back
与History
后退按钮的正常行为等浏览器机制。
什么情况下会碰到跨域问题?有哪些解决方法?
跨域问题是这是浏览器为了安全实施的同源策略导致的,同源策略限制了来自不同源的document、脚本
,同源的意思就是两个URL
的域名、协议、端口要完全相同。
script
标签jsonp
跨域、nginx
反向代理、node.js
中间件代理跨域、后端在头部信息设置安全域名、后端在服务器上设置cors
。
ps:有三个标签允许跨域加载资源:
三个标签场景:
用于打点统计,统计网站可能是其他域
可以使用CDN
,CDN
的也是其他域
可以用于JSONP
⚠️跨域注意事项
所有的跨域请求都必须经过信息提供方允许
如果未经允许即可获取,那么浏览器同源策略出现漏洞
XML
和JSON
的区别?
数据体积方面
JSON
相对于XML
来讲,数据的体积小,传递的速度更快些。
数据交互方面
JSON
与JavaScript
的交互更加方便,更容易解析处理,更好的数据交互
数据描述方面
JSON
对数据的描述性比XML
较差
传输速度方面
JSON
的速度要远远快于XML
get
和post
的区别
1、get
和post
在HTTP
中都代表着请求数据,其中get请求相对来说更简单、快速,效率高些
2、get
相对post
安全性低
3、get
有缓存,post
没有
4、get
体积小,post
可以无限大
5、get
的url
参数可见,post
不可见
6、get
只接受ASCII
字符的参数数据类型,post
没有限制
7、get
请求参数会保留历史记录,post
中参数不会保留
8、get
会被浏览器主动catch,post
不会,需要手动设置
9、get
在浏览器回退时无害,post
会再次提交请求
什么时候使用post
?
post
一般用于修改服务器上的资源,对所发送的信息没有限制。比如
1、无法使用缓存文件(更新服务器上的文件或数据库)
2、向服务器发送大量数据(POST
没有数据量限制)
3、发送包含未知字符的用户输入时,POST
比 GET
更稳定也更可靠
TCP
建立连接为什么需要三次?
- 为了实现可靠数据传输,
TCP
协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤 - 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认
ES6
ES5
的继承和ES6
的继承有什么区别?
ES5
的继承时通过prototype
或构造函数机制来实现。ES5
的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this
上(Parent.apply(this)
)。
ES6
的继承机制完全不同,实质上是先创建父类的实例对象this
(所以必须先调用父类的super()
方法),然后再用子类的构造函数修改this
。
具体的:ES6
通过class
关键字定义类,里面有构造方法,类之间通过extends
关键字实现继承。子类必须在constructor
方法中调用super
方法,否则新建实例报错。因为子类没有自己的this
对象,而是继承了父类的this
对象,然后对其进行加工。如果不调用super
方法,子类得不到this
对象。
ps:super
关键字指代父类的实例,即父类的this
对象。在子类构造函数中,调用super
后,才可使用this
关键字,否则报错
javascript
如何实现继承?
1、构造继承
2、原型继承
3、实例继承
4、拷贝继承
原型prototype
机制或apply和call
方法去实现较简单,建议使用构造函数与原型混合方式
function Parent(){
this.name = 'wang';
}
function Child(){
this.age = 28;
}
Child.prototype = new Parent();//继承了Parent,通过原型
var demo = new Child();
alert(demo.age);
alert(demo.name);//得到被继承的属性
}
谈谈你对ES6
的理解
新增模板字符串(为JavaScript
提供了简单的字符串插值功能)
箭头函数
for-of
(用来遍历数据—例如数组中的值。)
arguments
对象可被不定参数和默认参数完美代替。
ES6
将promise
对象纳入规范,提供了原生的Promise
对象。
增加了let和const
命令,用来声明变量。
增加了块级作用域。
let
命令实际上就增加了块级作用域。
还有就是引入module
模块的概念
谈一谈箭头函数与普通函数的区别?
- 函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象 - 不可以当作构造函数,也就是说,不可以使用
new
命令,否则会抛出一个错误 - 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用Rest
参数代替 - 不可以使用
yield
命令,因此箭头函数不能用作Generator
函数
forEach、for in、for of
三者区别
forEach
更多的用来遍历数
for in
一般常用来遍历对象或json
for of
数组对象都可以遍历,遍历对象需要通过和Object.keys()
for in
循环出的是key,for of
循环出的是value
Set、Map
的区别
应用场景Set
用于数据重组,Map用于数据储存
Set:
1,成员不能重复
2,只有键值没有键名,类似数组
3,可以遍历,方法有add, delete,has
Map:
1,本质上是健值对的集合,类似集合
2,可以遍历,可以跟各种数据格式转换
promise
对象的用法,手写一个promise
promise
是一个构造函数,下面是一个简单实例
var promise = new Promise((resolve,reject) => {
if (操作成功) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
},function (value) {
// failure
})
请描述一下Promise
的使用场景,'Promise
'它所解决的问题以及现在对于异步操作的解决方案。
Promise
的使用场景:ajax
请求,回调函数,复杂操作判断。
Promise
是ES6
为了解决异步编程所诞生的。
异步操作解决方案:Promise、Generator
、定时器(不知道算不算)、还有ES7
的async
ECMAScript6
怎么写class
,为什么会出现class
这种东西?
这个语法糖可以让有OOP
基础的人更快上手js
,至少是一个官方的实现了
但对熟悉js
的人来说,这个东西没啥大影响;一个Object.creat()
搞定继承,比class
简洁清晰的多
算法和其他
冒泡排序
每次比较相邻的两个数,如果后一个比前一个小,换位置
var arr = [3, 1, 4, 6, 5, 7, 2];
function bubbleSort(arr) {
for (var i = 0; i < arr.length - 1; i++) {
for(var j = 0; j < arr.length - i - 1; j++) {
if(arr[j + 1] < arr[j]) {
var temp;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
console.log(bubbleSort(arr));
快速排序
采用二分法,取出中间数,数组每次和中间数比较,小的放到左边,大的放到右边
var arr = [3, 1, 4, 6, 5, 7, 2];
function quickSort(arr) {
if(arr.length == 0) {
return []; // 返回空数组
}
var cIndex = Math.floor(arr.length / 2);
var c = arr.splice(cIndex, 1);
var l = [];
var r = [];
for (var i = 0; i < arr.length; i++) {
if(arr[i] < c) {
l.push(arr[i]);
} else {
r.push(arr[i]);
}
}
return quickSort(l).concat(c, quickSort(r));
}
console.log(quickSort(arr));
懒加载
缓存DOM
查询
// 未缓存 DOM查询
var i
for (i = 0; i < document.getElementByTagName('p').length; i++) {
//todo
}
//缓存了DOM查询
var pList = document.getElementByTagName('p')
var i
for (i= 0; i < pList.length; i++) {
// todo
}
合并DOM
插入
var listNode = document.getElementById('list')
//要插入10个li标签
var frag = document.createDocumentFragment();
var x,li
for (x = 0; x < 10; x++) {
li = document.createElement('li')
li.innerHTML = "List item" + x
frag.appendChild(li)
}
listNode.appendChild(frag)
事件节流
var textarea = document.getElementById('text')
var timeoutId
textarea.addEventListener('keyup', function () {
if (timeoutId) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(function () {
//触发事件
},100)
})
尽早操作
window.addEventListener('load', function () {
//页面的全部资源加载完才会去执行,包括图片,视频
})
document.addEventListener('DOMContentLoaded', function() {
//DOM 渲染完即可执行,此时图片,视频还可能没有加载完成
})
浅拷贝
首先可以通过 Object.assign
来解决这个问题
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
深拷贝
这个问题通常可以通过JSON.parse(JSON.stringify(object))
来解决
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
数组降维
[1, [2], 3].flatMap(v => v)
// -> [1, 2, 3]
如果想将一个多维数组彻底的降维,可以这样实现
const flattenDeep = (arr) => Array.isArray(arr)
? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , [])
: [arr]
flattenDeep([1, [[2], [3, [4]], 5]])
预加载
在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载
预加载其实是声明式的 fetch
,强制浏览器请求资源,并且不会阻塞 onload
事件,可以使用以下代码开启预加载
预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好
预渲染
可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染
性能优化
JavaScript性能优化
1、尽可能把