1.从几个问题说起
- window.onload和DOMContentLoaded区别? script为什么放在头部,为什么放在body(页面加载)
- JS创建10个
<a>
标签,点击弹出对应的序号1~10。(闭包,作用域) - 手写节流
throttle
和防抖debounce
(体验,性能优化) - Promise解决的问题?(JS异步)
2.JS部分
2.1基础语法
题目
- typeof能判断的变量类型(基本变量类型)
- 什么时候用===和什么时候用==?(强制类型转化)
- 值类型和引用类型的区别
- 手写深浅拷贝
知识点
原始值类型(互相不干扰),引用类型(类似指针)
值存在栈中,引用类型存在堆中(存放的是地址,修改的是地址中的内容)
值类型存储需要的空间较少,为了复制较快,所以放在栈中,而且是完全拷贝
常见引用类型 Object(对象),数组(array),null
特殊引用类型function
,但不用于存储数据
数据类型
基本数据类型:Undefined、Null、Boolean、Number、String、Symbol (new in ES 6) !
引用类型有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 等。
变量计算-类型转换
先举几个例子
const a = 100 + 10; //110
const b = 100 + '10' //10010, String类型
const c = 100 + 'a' // 100a
100 == '100' //true
0 == '' //true
0 == false//true
false == ''//true
null == undefined//true
- 字符串拼接
- ==运算符和===
//两种等价写法,因为undefine和null不能再转化为其他的
if (obj.a == null) { }
if (obj.a === undefined || obj.a === null) { }
规则:除了判断null之外,其余一律用===
- if和逻辑运算
if(真|假),主要是为了逻辑运算服务
- 真:!!a === true
- 假: !!a === false
为假的有
深浅拷贝
这样是浅拷贝
const obj1 = {
a: 1,
b: {
z: "shenzhen"
},
arr:['a','b','c']
}
const obj2 = obj1;
obj2.b.z = "guangzhou";
console.log(obj1.b.z) //guangzhou
手写深拷贝
/**
* 深拷贝
* @param {Object} obj
*/
/**
* 深拷贝
* @param {Object} obj
*/
function deepClone(obj = {}) {
//非引用类型,或者为空,直接返回(function不能深拷贝)
if (typeof obj !== 'object' || typeof obj == null) {
return obj;
}
//保存返回的结果, 并根据不同的类型初始化
let res;
if (obj instanceof Array) {
res = [];
} else {
res = {};
}
for (const key in obj) {
//拷贝不是原型中的属性,保证不能是原型的属性
if (obj.hasOwnProperty(key)) {
res[key] = deepClone(obj[key]);
}
}
return res;
}
const obj2 = deepClone(obj1);
obj2.b.z = "guangzhou";
console.log(obj1.b.z) //shenzhen
解答
- typeof能判断的变量类型(基本变量类型)
- 识别所有的值类型
- 识别函数
- 判断是否为引用类型(不可再细分)
- 什么时候用===和什么时候用==?见上文
- 值类型和引用类型的区别 见上文
- 手写深浅拷贝 见上文
2.2原型和原型链
es6之前,所有的对象都是基于原型的继承。
题目
- 如何判断一个变量是不是数组(instanceof发生了什么)
- 手写一个简易的jQuery,考虑插件和拓展性
- class的原型本质,怎么理解
知识点
class和继承
三部分:constructor,属性,方法
我的理解:class即一个模板,里面有属性和方法,可以通过class创建一个实例,即数据+模板
考虑以下代码
// 父类
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat something`)
}
}
// 子类
class Student extends People {
constructor(name, number) {
super(name)
this.number = number
}
sayHi() {
console.log(`姓名 ${this.name} 学号 ${this.number}`)
}
}
// 子类
class Teacher extends People {
constructor(name, major) {
super(name)
this.major = major
}
teach() {
console.log(`${this.name} 教授 ${this.major}`)
}
}
// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
xialuo.eat()
// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()
子类继承了父类,所以默认拥有父类的sayHi方法,而且可以通过super调用父类的构造函数,从而给name赋初始值
实际上,ES6中的class是原型的语法糖
console.log(typeof People) //function
console.log(typeof Student) //function
原型和原型链
原型和类与继承的关系密不可分,再看以下代码
console.log(Student.prototype === xialuo.__proto__) // true
console.log(Teacher.prototype === wanglaoshi.__proto__) //true
console.log(People.prototype.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__) //null
为什么会这样呢?那就牵扯到原型链了,我们称__proto__为隐式原型,在创建每个实例时,都会有默认有一个__proto__属性。规则如下
- 每个class都有一个显示原型prototype
- 每个实例都有隐式原型__proto__
- 实例中的__proto__指向对应class的prototype
即
实际上每个原型都会层层往上,最终的原型链如下
所以
类型判断instanceof
instanceof 实际上就是顺着原型链逐步查找的过程,顺着原型链往上的,都为true
比如x instanceof y ,y只要是x的class或者x的祖先类,都会返回true(因为能顺着原型链找到
console.log(xialuo instanceof Student) //true
console.log(xialuo instanceof People) //true
console.log(xialuo instanceof Object) //true
所以在调用某个方法时候,可能某个实例没有这个方法,比如xialuo没有hasOwnProperty
这个方法,但顺着原型链往上查找,可以找到Object的原型上有这个方法,所以xialuo也可以调用hasOwnProperty
console.log(xialuo.hasOwnProperty('number')) //true
//解释,是自己的属性,所以返回true
console.log(xialuo.hasOwnProperty('sayHi')) //false
//虽然本身有这个方法,但是sayHi在原型中,所以返回false
问题解答
上述知识点解答了两个问题,剩下的就是jQuery的设计问题了,代码如下
设计jQuery
<p>我是p1</p>
<p>我是p2</p>
<p>我是p3</p>
class jQuery{
constructor(selector) {
const res = document.querySelectorAll(selector);
for (let i = 0; i < res.length; i++) {
this[i] = res[i];
}
this.selector = selector;
this.length = res.length;
}
//返回第几个元素
get(index) {
return this[index];
}
//遍历,每个元素执行fn,传入一个函数
each(fn) {
for (let i = 0; i < this.length; i++) {
fn(this[i]);
}
}
//监听某个方法,要加则给全部的元素加上type方法
on(type, fn) {
this.each((elem) => {
elem.addEventListener(type, fn);
})
}
//..其余还可以扩展DOM API
}
const $p = new jQuery('p')
console.log($p.get(1).innerText) //我是p2
$p.each((elem)=>{ console.log(elem.innerText) })
/*
我是p1
我是p2
我是p3*/
$p.on('click', () => { console.log('click') })
//点击的时候就会打印click
考虑插件的拓展性
//插件,给显示原型添加
jQuery.prototype.dialog = function(){
console.log("我是插件")
}
//考虑插件的拓展性,造轮子
class myJQuery extends jQuery {
constructor(selector) {
//包含了jQuery的所有功能
super(selector)
}
//在此之上拓展自己的方法
addClass(className) {
}
//...
style(data) {
}
}
//使用插件中的方法
$p.dialog() //我是插件
2.3作用域和闭包
题目
- this的不用应用场景,应该如何取值
- 手写bind函数
- 实际开发中闭包的应用场景,举例说明
- 创建10个a标签,点击弹出对应的序号
知识点
作用域
作用域即变量的合法使用范围,即红框部分。ES6是块级作用域
作用域有三种
- 全局作用域
- 函数作用域
- 块级作用域,即花括号里面就是块
自由变量
定义:如果在某个作用域中使用了变量“a”,而变量“a”并未在该作用域中声明(在其它作用域中声明了),则该变量“a”即为自由变量
闭包
原理:
定义
闭包只是作用域应用的特殊情况,有两种表现
- 函数作为参数被传递
- 函数作为返回值被返回
// 函数作为返回值
function create() {
const a = 100
return function () {
console.log(a)
}
}
const fn = create()
const a = 200
fn() // 100
/*
解释:fn是在create内部被定义的,定义的时候会记住a,但在当前作用域无,往上查找,发现a为100
*/
// 函数作为参数被传递
function print(fn) {
const a = 200
fn()
}
const a = 100
function fn() {
console.log(a)
}
print(fn) // 100
/*
解释:同上,fn定义的地方无a,上上一层的作用域有a,所以打印的a=100
*/
总结:所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方!!!
this的问题
使用场景分为以下5种,记编号为1,2,3,4,5,this取什么值,由函数执行的时候确认
解释:fn1()在执行的时候上下文为window,故打印window(第一种)
fn1.call()改变了this的指向,且马上执行(第二种)
fn1.bind()功能和fn1.call()一样,但区别是不回马上执行,而是返回一个函数,所以需要再执行再打印(第二种)
sayHi()解释:sayHi执行时,是由对象的方法触发的,所以this指向的是当前对象(第三种)
wait()解释:wait中打印时,是由setTimeout函数触发的(第一种)
waitAgain():箭头函数虽然是setTImeout触发的,但由于箭头函数的特点:即永远取上级作用域中的this,即waitAgain中的this,所以也是当前对象(第五种)
第四种:
再附上一个题的链接
解答
- this的不用应用场景,应该如何取值。(见上文,共有5中情况
- 手写bind函数
bind的位置位于Functioni的原型对象之中
console.log(Function.prototype.hasOwnProperty('call')) //true
console.log(Function.prototype.hasOwnProperty('bind')) //true
bind使用举例
const fn1 = function (a, b, c) {
console.log(this);
console.log(a, b, c);
return 'this is fn1'
}
const fn2 = fn1.bind({ x: 100 }, 10, 20, 30);
const fn3 = fn1;
fn2();
/*
{x:1 00}
10 20 30
*/
fn3();
/*
window
10 20 30
*/
先补以下apply、call、bind这些基础知识,这三者都是修改this的指向,apply和bind只是传入的参数不同,call会立马执行。
const fn1 = function (a, b, c) {
console.log(this)
console.log(a, b, c);
return 'this is fn1'
}
Function.prototype._bind = function () {
//获取参数
const args = Array.prototype.slice.call(arguments);
//等价于args = Array.from(arguments); args = [...arguments]
//第一个参数是t是传入的想要绑定到的数据,即{x: 100}
const t = args.shift();
//this指向调用_bind的函数,即fn1
const self = this
//返回一个函数,注意apply的参数是数组,而bind后面的参数是,分割的参数
return function () {
self.apply(t, args)
}
}
const fn2 = fn1._bind({ x: 100 }, 10, 20, 30)
fn2() //{ x: 100 }, 10, 20, 30
类似于上面的思路,可以写出call函数
Function.prototype._call = function () {
//获取参数,其实可以不用call
const args = Array.prototype.slice.call(arguments);
//第一个参数是t是传入的想要绑定到的数据,即{x: 100}
const thisArg = args.shift();
//新添加一个fn, 保存this,即fn1
thisArg.fn = this;
//传入参数并执行
const res = thisArg.fn(...args);
//工具人没用了,丢掉它
delete thisArg.fn;
return res;
}
fn1._call({ x: 100 }, 10, 20, 30)
- 实际开发中闭包的应用场景,举例说明
//闭包隐藏数据,只提供API
function createCache() {
//闭包中的数据,只能通过闭包内定义的方法访问,外部无法直接访问
const data = {
a : 1
}
return {
set: function (key, val) {
data[key] = val;
},
get: function (key) {
return data[key];
}
}
}
const c = createCache();
console.log(c.get('a')) //1
c.set('c', "new data")
console.log(c.get('c')) //new data
但是如果不合理使用闭包会导致内存泄漏的问题,在闭包内部引用的对象,只要funciton还在作用域内,就不会被垃圾回收
如果看得不够爽,再来一篇闭包的文章
5. 创建10个a标签,点击弹出对应的序号
//注意定义域的问题,所以i要写在这个位置
for (let i = 0; i < 10; i++) {
let a = document.createElement('a');
a.innerHTML = i + '<br>';
a.addEventListener('click', (e) => {
e.preventDefault();
alert(i);
})
document.body.append(a);
}
2.4异步和单线程
题目
- 同步和异步的区别是什么
- 手写Promise加载一张图片
- 前端使用异步的场景有哪些
- 如下:
知识点
异步和单线程
- 异步基于js是单线程语言,只能同时做一件事。同步会阻塞代码的执行,而异步不会阻塞代码的执行
- 所以遇到等待(网络请求,定时任务)不能卡住,需要异步,且基于callback调用,异步基于函数来执行
应用场景
- 网络请求
- 定时任务,setTimeOut
这里返回数据时执行回调函数
这里onload是回调函数
定时任务
callback hell和Promise
如果要获取数据的顺序是data1=>data2=>data3,可能引起回调地狱的问题,Promise就解决了这个问题
解答
- 同步和异步的区别是什么 (见上图
- 手写Promise加载一张图片
const loadImg = url => {
//返回一个Promise对象
return new Promise((resolve, reject) => {
const img = document.createElement('img');
img.onload = () => {
//因为是加载图像,所以需要把img传入resolve,知道在成功时返回的then是什么
resolve(img);
}
img.onerror = () => {
//自定义加载失败时候then接受的参数
const err = new Error(`图片加载失败 ${url}`);
reject(err);
}
//这里会触发上面的onload和onerror时间,从而使用promise
img.src = url;
})
}
const url1 = "https://i-blog.csdnimg.cn/blog_migrate/84e53527fd054885c728cc034840d4e8.png"
loadImg(url1).then(img => { //可以通过then来接受resolve状态的元素和reject状态的元素
console.log(img.width);
return img; //返回的是img元素
}).then(img => {
console.log(img.height);
return loadImg(img.src); //返回的是promise
})
如果需要先加载图片1,再加载图片2,可以这样,这样就解决了回调地狱的问题
const url1 = "https://i-blog.csdnimg.cn/blog_migrate/84e53527fd054885c728cc034840d4e8.png"
const url2 = "https://i-blog.csdnimg.cn/blog_migrate/4c46eb231627933d4b9ff04fdc9be31a.png"
loadImg(url1).then(img => { //可以通过then来接受resolve状态的元素和reject状态的元素
console.log(img.width); //705
return img; //返回的是img元素
}).then(img => {
console.log(img.height); //609
return loadImg(url2); //返回的是promise
}).then(img2 => {
console.log(img2.width); //895
return img2;
}).then(img2 => {
console.log(img2.height); //714
return img2;
}).catch(err => {
console.log(err)
});
- 前端使用异步的场景有哪些(加载数据,如加载图片,定时任务,见上
- 答案13542
2.5ECMA 262标准
主要是js的各种内置对象和api需要熟练掌握
3.JS-web-api
js Web APi是网页操作api,是W3C定义的标准
3.1DOM(Document Object Model)
vue和React框架应用广泛,封装了DOM操作
题目
- DOM是哪种数据结构
- DOM操作的常用API
- attr和property
- 一次性插入多个DOM结点,考虑性能
知识点
DOM本质
DOM本质是一棵树,每个元素都是一个结点
DOM结点操作
获取dom结点,attibut,property
<style>
.container {
border: 1px solid #ccc;
}
.red {
color: red;
}
</style>
<div id="div1" class="container">
<p id="p1">一段文字 1</p>
<p>一段文字 2</p>
<p>一段文字 3</p>
</div>
<div id="div2">
<img src="https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg"/>
</div>
const div1 = document.getElementById('div1') //元素
console.log('div1', div1); //打印id = div1的标签
const divList = document.getElementsByTagName('div') // 集合
console.log('divList.length', divList.length) // divList.length 2
console.log('divList[1]', divList[1]) //打印文档树中的第二个div标签
const containerList = document.getElementsByClassName('container') // 集合
console.log('containerList.length', containerList.length)
console.log('containerList[0]', containerList[0])
const pList = document.querySelectorAll('p') //传入的是CSS选择器
console.log('pList', pList)
const p1 = pList[0]
区别
attribute 是我们在 html 代码中经常看到的键值对
property 是 attribute 对应的 DOM 节点的 对象属性 (Object field)
- attribute 会始终保持 html 代码中的初始值, 而 Property 是有可能变化的.
- 两者都可以操作dom,进而导致dom的重新渲染
其实, 我们从这两个单词的名称也能看出些端倪:
attribute 从语义上, 更倾向于不可变更的
而 property 从语义上更倾向于在其生命周期中是可变的
// property 形式
// 可以直接通过id获取某个标签
p1.style.width = '100px'
p1.className = 'red'
console.log( p1.className ) //将p1的类名修改为red
console.log(p1.nodeName) //p
console.log(p1.nodeType) // 1
// attribute
p1.setAttribute('data-name', 'imooc')
console.log(p1.getAttribute('data-name')) //imooc
p1.setAttribute('style', 'font-size: 50px;')
console.log( p1.getAttribute('style') ) //font-size: 50px;
DOM结构操作
- 增减、删除结点
- 移动结点
- 获取子元素、父元素
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)
// 移动节点,对现有结点使用appendchild则会移动结点
const p1 = document.getElementById('p1')
div2.appendChild(p1)
// 获取父元素
console.log( p1.parentNode )
// 获取子元素列表,现在得到的结点包括了text
const div1ChildNodes = div1.childNodes
console.log( div1.childNodes )
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
if (child.nodeType === 1) {
return true
}
return false
})
console.log('div1ChildNodesP', div1ChildNodesP)
div1.removeChild( div1ChildNodesP[0] )
DOM性能
有以下几个方案
- 缓存
改为一次性操作
未优化
const list = document.getElementById('list')
for (let i = 0; i < 20; i++) {
const li = document.createElement('li')
li.innerHTML = `List item ${i}`
list.appendChild(frag)
}
console.log(list)
优化后
const list = document.getElementById('list')
// 创建一个文档片段,此时还没有插入到 DOM 结构中,frag是游离dom之外的
const frag = document.createDocumentFragment()
for (let i = 0; i < 20; i++) {
const li = document.createElement('li')
li.innerHTML = `List item ${i}`
// 先插入文档片段中
frag.appendChild(li)
}
// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)
console.log(list)
解答
- DOM是哪种数据结构 (dom数
- DOM操作的常用API(获取结点、增加结点、移动结点,删除结点,获取父元素、子元素
- attr和property(见上
- 一次性插入多个DOM结点,考虑性能(见上
3.2BOM(Browser Object Model
题目
- 如何识别浏览器类型
- 分析拆解url的各个部分
知识点
navigator
可以通过浏览器的userAgent来判断浏览器的信息
const ua = navigator.userAgent
const isChrome = ua.indexOf('Chrome') > 0;
console.log(isChrome) //true
console.log(ua ) //Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
screen
屏幕的高度和宽度,因屏幕大小而异
console.log(screen.height) //1536
console.log(screen.width) //864
location
location.href //https://admission.pku.edu.cn/zsxx/sszs/index.htm?CSRFT=DD70-CXOT-TEKP-QJS6-52BO-8GWF-LX85-RLWP#zz
location.hostname // "admission.pku.edu.cn"
location.protocol // "https:"
location.pathname // "/zsxx/sszs/index.htm"
location.search // "?CSRFT=DD70-CXOT-TEKP-QJS6-52BO-8GWF-LX85-RLWP"
location.hash // "#zz"
history
history.forward() //对应网页上的前进
history.back() //对应后退
解答
见上文
3.3事件绑定
题目
- 编写一个通用的事件监听函数
- 描述事件冒泡的流程
- 无限下拉的图片的列表,如何监听每个图片的点击
知识点
事件绑定
最简单的事件绑定
const btn = document.getElementById('eve_bind')
btn.addEventListener('click', e => {
console.log(e.target) //触发该事件的元素c
e.preventDefault(); //如果是链接,默认行为是跳转,阻止默认行为
console.log("click lzz");
})
// 通用的事件绑定函数
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
bindEvent(btn, 'click', e => {
console.log("click btn")
})
事件冒泡
即某个dom元素触发了某个事件,如果dom的上级也有相同的事件,则事件会从最低层次的dom元素一层一层往上冒泡。整个路径上的dom元素都能够触发事件
<button id="btn1">一个按钮</button>
<div id="div1">
<p id="p1">激活</p>
<p id="p2">取消</p>
<p id="p3">取消</p>
<p id="p4">取消</p>
</div>
<div id="div2">
<p id="p5">取消</p>
<p id="p6">取消</p>
</div>
绑定了p1和第div1的click事件,点击p1后,两个元素都会打印click,e.target打印的是p1。
通过加入e.stopPropagation();
后,事件就不会往上冒泡
冒泡顺序 => 底层到上层
//普通的事件绑定函数
const p1 = document.getElementById('p1');
const div1 = document.getElementById('div1');
bindEvent(p1, 'click', e => {
console.log("p1 click");
//e.stopPropagation();
console.log('click', e.target);
})
bindEvent(div1, 'click', e => {
console.log("div1 click");
console.log('click', e.target);
})
事件代理
有时候不想给每个元素都绑定事件,这样代码繁琐,且耗费浏览器内存。于是有了事件代理,基于事件冒泡,给祖先元素绑定事件,由祖先元素“代理”每个事件的响应
function bindEvent(elem, type, selector, fn) {
//注意是两等, 可以不输入标签,则是普通绑定(selector才是处理函数)
if (fn == null) {
[fn, selector] = [selector, null];
}
elem.addEventListener(type, function (e) {
const target = e.target;
//事件代理
if (selector) {
//如果匹配
if (target.matches(selector)) {
//执行绑定的函数
fn.call(target, e);
}
}
else { //普通绑定
fn.call(target, e);
}
});
}
//通用的事件代理函数
const div = document.getElementById('div1');
//祖先元素,事件,绑定事件的标签名, 事件处理函数
//div下的a标签都被绑定了click事件
bindEvent(div, 'click', 'a', function(e) {
e.preventDefault();
//this指向触发该事件的dom元素
alert(this.innerHTML);
})
解答
- 编写一个通用的事件监听函数(需要考虑事件代理,见上
- 描述事件冒泡的流程
1. 基于DOM树形结构
2. 事件会顺着触发元素往上冒泡
3. 应用场景:事件代理 - 无限下拉的图片的列表,如何监听每个图片的点击
1. 使用事件代理
2. 用e.target获取触发事件的元素
3. 用matches来判断是否是触发元素
3.4ajax
题目
- 手写一个简易的ajax(例如vue的axios,fetch,jquery中的ajax)
- 跨域的常用实现方式
知识点
XMLHttpRequest
先写如何用
//创建xhr实例
const xhr = new XMLHttpRequest();
//true表示该请求是异步的请求
xhr.open("GET", "data.json", true);
//状态改变的时候触发函数
xhr.onreadystatechange = function () {
//readyState === 4代表内容已经解析完成
if (xhr.readyState === 4) {
//xhr即是响应码
if (xhr.status === 200) {
console.log(xhr.responseText)
}
else {
console.log("其他情况")
}
}
}
//可以发或者不发数据
xhr.send(null);
// post请求
// xhr.open("POST", "data.json", true);
// //状态改变的时候触发函数
// xhr.onreadystatechange = function () {
// if (xhr.readyState === 4) {
// if (xhr.status === 200) {
// console.log(xhr.responseText)
// }
// else {
// console.log("其他情况")
// }
// }
// }
// const userData = {
// userName: "zhangsan",
// pwd: "xxxx"
// }
// xhr.senc(JSON.stringify(userData));
状态码
关于xhr中的readyState和status的解析:
跨域、同源策略、jsonP、CORSE
第三个例子就不同源,同源是浏览器保证的(必须保证,否则一个页面可以请求其他的页面的数据
JSONP实现跨域需要服务端配合
jsonP的示范
<script>
window.abc = function (data) {
//跨域后得到的信息
console.log(data)
}
</script>
<!-- src是前台的访问,传入的参数为username,并希望后台提供的服务为callback , 将返回函数执行的结果,即abc( {a : 1}) -->
<script src="http://localhost:8002/jsonp.js?username=xxx&callback=abc"></script>
<!-- 后台的服务为http://localhost:8002/jsonp.js, 只有一段话abc({a : 1}), 即后台希望把数据传给前端的abc -->
解答
- 手写一个简易的ajax0
const myAjax = function (url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
//默认为true
xhr.open("GET", url);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
}
else if (xhr.status === 404 || xhr.status === 500) {
reject(new Error("404 not found hhh"));
}
}
}
xhr.send(null);
});
}
const url = "data.json";
myAjax(url).then(res => console.log(res))
.catch(err => console.log(err));
- 跨域的常用实现方式
- JSONP(前端
- CORS (后端配置实现
补几个封装了ajax的插件
jQuery.ajax() (不是基于promise,过时)
Fetch API
axios
3.5存储
题目
- 描述cookie localStorage sessionStorage 区别
知识点
cookie
通过;
分割的key:val
形式,每次访问服务器都会用cookie访问
通过document.cookie
可以查看cookie
使用背景
localStorage 和 sessionStorage
H5的 localStorage 和 sessionStorage解决了上面的问题
两者的区别
解答
描述cookie localStorage sessionStorage 区别
- 容量,cookie是4KB,后者为5M
- cookie的API只能通过document.cookie="xxx"来赋值,使用起来不符合js的风格,后者可以通过setItem和getItem来获取
- cookie会随着http请求发送出去,增加了数据负荷。而后者保存在本地