一、JS基础知识
(一) 变量类型和计算
[1]. 知识点
1). 类型分类
- 引用类型
array、function、object - 值类型
number,string,boolean,undefined,Symbol,null - null比较特殊
http://www.w3dev.cn/article/20130107/javascript-baseic-data-type.aspx
2). 类型转换
① 隐式类型转换
-
字符串拼接
var b = 100 + '10' //'10010' var c = true + '10' //'true10'
-
==运算符
100 == '100' //true null == undefined //true true == 1 //true
-
if语句
- truly变量:!!a === true的变量
- falsely变量:!!a === false的变量
var a = 100 if(a){ //... }
-
逻辑运算
console.log(10 && 0); //0 console.log('' || 'abc'); //'abc' consolse.log(!window.abc); //true //判断一个变量会被当做true还是false var a = 100 console.log(!!a); //true 先取对立的boolean e然后再取对立
② 转换为false的值
0, ‘’,NaN,null,undefind,flase
空数组、空对象、-1是true
3). typeof运算符
- 能够识别所有值类型
- 识别函数
- 判断是否是引用类型(不可再细分)
null -> object
array -> object
object -> object
4). 深拷贝
JSON.parse(JSON.stringify(object))的缺点
- 会忽略属性值为undefined、symbol、函数的这三种情况, 也就是不进行拷贝
- 不能解决循环引用问题
- 不能正确的处理new Date
- 不能处理正则(为正则的时候, 拷贝过去为一个空对象{})
const obj1 = {
name: '张三',
age: 20,
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.address.city ="上海"
console.log(obj1);
console.log(obj2);
const obj1 = {
name: '张三',
age: 20,
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
function deepClone(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
}
let result;
if (obj instanceof Object) {
result = {
};
} else {
result = [];
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
let obj2 = deepClone(obj1);
obj2.address.city = '上海';
console.log(obj1);
console.log(obj2);
[2]. 题目
1). JS中使用typeof能得到哪些类型
- number
- string
- boolean
- undefined
- symbol
- object
- function
2). 何时使用 === 何时使用 ==
除了下边的 其余都是 ===
if(obj.a == null){
//这里相当于 obj.a === null || obj.a === undefined,
//jquery 源码推荐
}
3). js变量按照存储方式区分哪些类型,并描述其特点
-
基本类型:基本类型有Number,String,Boolean,Undefined,Null;按值访问,可以直接操作保存在变量的实际值。存储方式栈存储。
基本类型在复制值的时候,会在变量对象上创建一个新的值,然后把这个值复制到新变量分配的位置上面来
-
保存在内存中的对象,JS不允许直接访问变量内存的位置,就是说不能直接操变量内存空间。存储方式是堆内存。引用类型可以添加属性和方法。
引用类型在复制值的时候,同样会将存储在变量对象中的值复制一份放到新变量的分配的空间上面,但是不同的是,这个值实际上就是一个副本发指针,这个指针指向存储子堆中的一个对象,实际上是两个变量引用的是同一个对象,改变其中一个变量,也会影响另外的一个变量。
4).如何理解JSON
- JSON只不过是JS对象而已,有两个API
- JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串
- JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。
JSON.stringify({ a:10, b:20}); JSON.parse('{"a":10, "b":20}')
5). 值类型和引用类型的区别
6). 手写深拷贝
(二) 原型和原型链
[1].知识点
1).class和继承
//父类
class People{
constructor(name){
this.name = name;
}
eat(food){
console.log(`${
this.name}吃:${
food}`);
}
}
// 子类通过 extends 继承父类
class Student extends People{
constructor(name, number){
super(name);//子类通过super调用父类的构造函数
this.number = number;
}
info(){
console.log(`${
this.name}的学号:${
this.number}`);
}
}
const stu = new Student('张三',123);
stu.eat('米饭');
stu.info();
2).类型判断 instanceof
instanceof 在原型链上进行查找
3).原型关系
- 每个class都有显示原型prototype
- 每个实例都有隐式原型 __proto__
- 实例的__proto__指向对应class的prototype
4).原型链
- 1.每个对象都有__proto__属性,该属性指向其原型对象,在调用实例的方法和属性时,如果在实例对象上找不到,就会往原型对象上找
- 2.构造函数的prototype属性也指向实例的原型对象
- 3.原型对象的constructor属性指向构造函数
[2].题目
1). 如何准确判断一个变量是数组类型
instanceof
var arr = [];
arr instanceof Array //true
typeof arr //object,type是无法判断是否是数组的
2).手写一个建议的jQuery,考虑插件和扩展
<p>文字1</p>
<p>文字2</p>
<p>文字3</p>
<script>
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector);
const length = result.length;
for (let i = 0; i < length; i++) {
this[i] = result[i];
}
this.length = length;
this.selector = selector;
console.log(this);
}
get(index) {
return this[index];
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i];
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
}
//插件
jQuery.prototype.dialog = function (info) {
console.log('插件:' + info);
}
//扩展 (造轮子)
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
//扩展自己的方法
//....
}
const $p = new jQuery('p');
$p.get(1);
$p.each((elem) => console.log(elem.nodeName));
$p.on('click', () => console.log('click'));
</script>
3).描述new一个对象的过程
4).zepto(或其他框架)源码中如何使用原型链
- 可以慕课网搜索“zepto设计和源码分析”(免费)
5). class的原型本质,怎么理解
- https://segmentfault.com/a/1190000008338987
- https://zhuanlan.zhihu.com/p/105664102
- class 并不是 ES6 引入的全新概念,它的原理依旧是原型继承。
(三) 作用域和闭包
[1].知识点
fn();//不会报错
function fn(){
//函数声明
}
fn1(); //会报错
var fn1 = function (){
//函数表达式
}
1). this
-
this的取值是在函数执行时确认的,不是函数定义时确认的
-
new 的方式优先级最高,接下来是 bind 这些函数,然后是 obj.foo() 这种调用方式,最后是 foo 这种调用方式,同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。
var a = { name: 'A', fn: function () { console.log(this.name); } } a.fn(); //this === a a.fn.call({ name: 'B' //this === {name:'B'} }); var fn1 = a.fn; fn1(); // this === window
2). 作用域
- 全局作用域
- 函数作用域
- 块作用域(ES6新增)
3). 作用域链(自由变量?)
- 一个变量在当前作用域没有定义,但被使用了
- 向上级作用域,一层一层一次寻找,直至找到为止
- 如果到全局作用域都没有找到,则报错 xx is not defined
- (undefined意思是已经声明了一个变量,只是还没有赋值,不是编译错误,而not defined是指没有生命,出现编译错误,程序退出执行。)
4). 闭包(closure)
所有的自由变量的查找是在函数定义的地方,向上级作用域查找,不是在执行的地方。
闭包的使用场景
- 函数作为返回值
function f1(){ var a = 100; return function(){ console.log(a) } } var fn = f1(); var a = 200; fn(); //100
- 函数作为参数传递
function print(fn) { var a = 200; fn(); } var a = 100; function fn() { console.log(a); } print(fn); //100
[2].题目
1). 说一下对变量提升的理解
- 通常JS引擎会在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端,然后进行接下来的处理。
- 函数声明会覆盖变量声明:如果存在函数声明和变量声明(注意:仅仅是声明,还没有被赋值),而且变量名跟函数名是相同的,那么,它们都会被提示到外部作用域的开头,但是,函数的优先级更高,所以变量的值会被函数覆盖掉。
2). this的不同应用场景,如何取值
-
[1]. 作为普通函数
function fn1(){ console.log(this) } fn1();//window
-
[2]. 使用call apply bind
function fn1(){ console.log(this) } fn1.call({ x:100});//{x: 100} const fn2 = fn1.bind({ x:200}) fn2();//{x:200}
-
[3]. 作为对象方法被调用
const zhangsan = { name:'张三', sayHi(){ console.log(this); }, wait(){ setTimeout(function(){ console.log(this); console.log(this.name); }) } } zhangsan.sayHi();//当前对象 zhangsan.wait();// window lisi.__proto__.sayHi();
-
[4]. 在class方法中调用
class People{ constructor(name){ this.name = name; } sayHi(){ console.log(this); } } const lisi = new People('李四'); lisi.sayHi(); //实例对象(lisi对象)
-
[5]. 箭头函数
箭头函数永远取得是上级作用域的this
箭头函数导致this总是指向函数定义生效时所在的对象
全局变量默认挂载在window对象下const zhangsan = { name:'张三', sayHi(){ console.log(this); }, wait(){ //console.log(this); setTimeout(()=>{ console.log(this); }) }, action:()=>{ console.log(this); } } zhangsan.sayHi();//当前对象 zhangsan.wait();// 当前对象 zhangsan.action();//window 对象
3). 创建10个<a>标签,点击的时候弹出来对应的序号

4). 如何理解作用域
- 自由变量
- 作用域链
- 闭包的两个场景
5). 实际开发中闭包的应用
6). 手写bind函数
https://juejin.im/post/6844903688897576974
Function.prototype._bind = function (context, ...args) {
//判断context是否存在,不存在挂载到window上
var thisContext = context || window;
//保存当前作用域的this
var self = this;
var thisFunction = function () {
// 如果当前函数的this指向的是构造函数中的this 则判定为new 操作
var thisRoot = this instanceof self ? this : thisContext;
return self.apply(thisRoot, args.concat(Array.from(arguments)));
}
// 为了完成 new操作
thisFunction.prototype = Object.create(self.prototype)
return thisFunction;
}
function foo(name) {
this.name = name;
}
var obj = {
};
var bar = foo._bind(obj);
bar('Jack');
console.log(obj.name); // Jack
var alice = new bar('Alice');
console.log(obj.name); // Jack
console.log(alice.name); // Alice。
7). 手写apply
Function.prototype._apply = function (context, args) {
//生成唯一数
var fn = Symbol(fn);
//判断要指向的对象是否存在,不存在将函数挂载到window上
var thisContext = context || window;
//当前作用域的this就是调用_call函数的对象
thisContext [fn] = this;
//执行对象
var res = thisContext [fn](...args);
delete thisContext [fn];
return res
}
var obj = {
name: '张三',
send(arg1) {
console.log('----------------');
console.log(this.name);
console.log(arg1);
console.log(arguments);
}
}
var obj1 = {
name: '李四'
}
obj.send(['参数1', '参数2']);
obj.send.apply(obj1, ['参数1','参数2'])
obj.send._apply(obj1, ['参数1','参数2'])
8). 手写call
Function.prototype._call = function (context, ...args) {
//生成唯一数
var fn = Symbol(fn);
//判断要指向的对象是否存在,不存在将函数挂载到window上
var thisContext = context || window;
//当前作用域的this就是调用_call函数的对象
thisContext [fn] = this;
//执行对象
var res = thisContext [fn](...args);
delete thisContext [fn];
return res
}
var obj = {
name: '张三',
send(arg1, arg2){
console.log(this.name);
console.log(arg1, arg2);
}
}
var obj1 = {
name:'李四'
}
obj.send('参数1','参数2');
obj.send.call(obj1, '参数1','参数2');
obj.send._call(obj1, '参数1','参数2');
(四) 异步和单线程
[1].知识点
1).单线程和异步
- js是单线程语言,只能同时做一件事儿
- JS和DOM渲染共用同一个线程,因为js可修改DOM结构
- 异步是为了在JS遇到等待(网络请求,定时任务)时不被卡住
- 异步基于回调callback函数形式调用的
- 异步不会阻塞代码执行,而同步会阻塞代码执行
2). 何时需要异步
- 在可能发生等待的情况
- 网络请求,如ajax,图片加载
- 定时任务,如setTimeout
3). callback hell 和 Promise
[2].题目
1). 同步和异步的区别是什么?
- js是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
2). 手写用Promise加载一张图片
function loadImg(imgUrl) {
const p = new Promise((resolve, reject) => {
console.log(1);
let img = document.createElement('img');
img.onload = () => {
console.log(2);
resolve(img)
}
img.onerror = () => {
console.log(3);
reject(`图片加载失败:${
err}`);
}
console.log(4);
img.src = imgUrl;
});
return p;
}
const url1 = 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png';
const url2 = 'https://developer.mozilla.org/static/img/hero-dino.6c51deebd4af.png';
loadImg(url1).then(img => {
document.body.appendChild(img);
return img; //返回一个普通对象
}).then(img => {
console.log(img.width);
return loadImg(url2); //返回一个promise实例
}).then(img2 => {
console.log(img2);
}).catch(err => {
console.error(err);
});
3). 前端使用异步的场景有哪些
- 定时任务:setTimeout,setInverval
- 网络请求:ajax请求,动态<img>加载
- 事件绑定
4). 场景题
二、JS进阶
(一) JS-Web-API-DOM
[1]. 知识点
1). DOM本质
是从html文件解析出的一棵树
2). DOM节点操作
-
1.获取节点
- document.getElementById(‘div1’);
- document.getElementsByName(‘box’);
- document.getElementsByTagName(‘div’);
- document.getElementsByClassName(‘content’);
- documet.querySelector(’.className’);
- documet.querySelectorAll(’.className’);
-
2.attributy
- ele.getAttribute(‘src’)
- ele.setAttribute(‘src’, ‘…’)
- ele.removeAttribute(‘src’)
-
3.property 形式
通过js来修改html的样式和class -
4.property和attribute
- property: 修改对象属性,不会体现大html结构中
- attribute:修改html属性,会改变html结构
- 两者都有可能引起DOM重新渲染
- 优先考虑property
3). DOM结构操作
-
1.新增/插入节点
const div1 = document.getElementById('div1'); const p1 = document.createElement('p'); p1.innerText = 'this is p1'; div1.appendChild(p1); //添加新创建的元素 //注意:移动已有节点。 const p2 = document.getElementById("p2"); div1.appendChild(p2);
-
2.获取子元素列表和获取父元素
//获取父元素 console.log(p1.parentNode); //获取子元素节点,返回伪数组 console.log(div1.children); //获取子元素节点2,返回数组 const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => { if(child.nodeType === 1){ return true; } return false; }) console.log(div1ChildNodesP);
-
3.删除节点
div1.removeChild(div1.children[0])
4). DOM性能
-
DOM操作非常“昂贵”,避免频繁的DOM操作
-
对DOM查询做缓存
-
将频繁操作改为一次性操作
[2]. 题目
1). DOM是那种数据结构
- 树 (DOM树)
2). DOM操作常用API
- DOM节点操作
- DOM结构操作
- attribute和property
3). attr和property的区别
4). 一次性插入多个DOM节点,考虑性能
(二) JS-Web-API-BOM
[1]. 知识点
1). navigator
const ua = navigator.userAgent;
const isChrome = ua.indexOf('Chrome');
console.log(isChrome);
2). screen
console.log(screen.width)
console.log(screen.height)
3). location
console.log(location.href);
console.log(location.host);
console.log(location.protocol);
console.log(location.port);
console.log(location.pathname);
console.log(location.search);
console.log(location.hash);
4). history
[2]. 题目
1). 如何识别浏览器的类型
2). 分析拆解url各个部分
(三) JS-Web-API-事件
[1]. 知识点
1). 事件绑定
ele.addEventListener(‘type’, fn)
2). 事件冒泡
2). 事件代理
- 代码简洁
- 减少浏览器内存占用
- 但是,不要滥用
[2]. 题目
1). 编写一个通用的事件监听函数
function bindEvent(ele, type, fn) {
if (ele.addEventListener) {
ele.addEventListener(type, fn);
} else if (ele.attchEven) {
ele.attachEvent(type, fn);
} else {
ele['on' + type] = fn;
}
}
2). 描述事件冒泡的流程
3). 无限下拉的图片列表,如何监听每个图片的点击
- 事件代理
- 用e.target获取触发元素
- 用matches来判断是否是触发元素
(四) JS-Web-API-Ajax
[1]. 知识点
1). XMLHttpRequest
-
xhr.readyState 代表请求的状态码。
- 4 - 意味着数据传输已经彻底完成或失败。
-
xhr.status 代表请求的响应状态。
- 200 表示成功处理请求。
-
get请求
var xhr = new XMLHttpRequest(); //请求类型,文件在服务器上的位置,true(异步)或false(同步) xhr.open('GET','/test.php',true); xhr.onreadystatechange = function (){ if(xhr.readyState === 4 && xhr.status === 200 ){ //获取服务器所响应的数据 //获得字符串形式的响应数据 console.log(xhr.responseText); //获得 XML 形式的响应数据。 //console.log(xhr.responseXML); } } xhr.send(null);
-
post请求
var xhr = new XMLHttpRequest(); //请求类型,文件在服务器上的位置,true(异步)或false(同步) xhr.open('POST','/test.php',true); xhr.onreadystatechange = function (){ if(xhr.readyState === 4 && xhr.status === 200 ){ //获取服务器所响应的数据 //获得字符串形式的响应数据 console.log(xhr.responseText); //获得 XML 形式的响应数据。 //console.log(xhr.responseXML); } } xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); var info = { name:'zhangsan',big:'180'} xhr.send(JSON.stringify(info));
2). 状态码
3). 跨域:同源策略,跨域解决方案
-
同源策略
- ajax请求时,浏览器要求当前网页和server必须同源(安全)
- 同源:协议、域名、端口,三者必须一致
- 加载图片、css、js可无视同源策略
- <img /> 可用于统计打点,可使用第三方统计服务
- <link /><script>可使用CDN,CDN一般都是外域
- <script>可实现JSONP
-
JSONP
- <script>可以获得跨域的数据,只要服务端愿意返回。
请求页面index.html
<body> <script> //fn域响应页面中的fn对应即可 window.fn = function(data){ console.log(data); } </script> <script src="http://localhost/jsonp.js"></script> </body>
响应页面jsonp.js:
fn( { name: 'zhangsan'} )
- CORS(纯服务端操作)
[2]. 题目
1). 手写一个简易的ajax
function ajax(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(
xhr.responseText
)
} else if (xhr.status === 404) {
reject(new Error('404 not found'))
}
}
}
xhr.send(null);
})
return p;
}
const url = 'http://localhost/jsonp.js';
ajax(url).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
2). 跨域的常用实现方式
(五) JS-Web-API-存储
[1]. 知识点
1). cookie
- 可用document.cookie = ‘a=100’ 来修改
- cookie缺点
- 存储有限,最大4kb
- http请求时需要发送到服务端,增加请求数据量
- 只能用 document.cookie=’…’ 来修改,过于简陋
2). localStorage和sessionStorage
- HTML5专门为存储而设计,最大可存5M
- API简单易用,setItem getItem
- 不会随着http请求被发送出去
- localStorage数据会永久存储,除非代码或手动删除
- sessionStorage数据只存在于当前会话,浏览器关闭则清空
- 一般用localStorage会更多一些
[2]. 题目
1). 描述cookie、localStorage、sessionStorage区别
-
存储大小:
- cookie 数据大小不能超过4 k 。
- sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大。
-
有期时间:
- localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据。
- sessionStorage 数据在页面会话结束时会被清除。页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。在新标签或窗口打开一个页面时会在顶级浏览上下文中初始化一个新的会话。
- cookie 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭。
-
作用域:
- sessionStorage 只在同源的同窗口(或标签页)中共享数据,也就是只在当前会话中共享。
- localStorage 在所有同源窗口中都是共享的。
- cookie 在所有同源窗口中都是共享的。
三、js开发
(一) 开发环境
[1]. git
- git add .
- git checkout xxx
- git commit -m ‘xxx’
- git push origin master
- git pull origin master
- git brach
- git checkout -b xxx / git checkout xxx
- git merge
[2]. chrome调试工具
- Elements
- Console
- debugger
- Network
- Aplication
[3]. 抓包
- 移动端h5页,查看网络请求,需要用工具抓包
- windows 一般用fiddler
- 手机和点连同一个局域网
- 将手机代理到电脑上
- 手机浏览网页即可抓包
- 查看网络请求
- 网址代理
- https
[4]. webpack
[5].linux常用命令
(二) 运行环境
[1]. 页面加载和渲染过程
1). 知识点
- 加载资源的形式
- html代码
- 媒体文件,如图片、视频等
- javascript,css
- 加载资源的过程
- DNS解析:将域名解析为IP地址
- 浏览器根据IP地址向服务器发起http请求
- 服务器处理http请求,将响应返回给浏览器
- 渲染页面的过程(进一步求证)
- 根据HTML代码生成DOM Tree
- 根据CSS代码生成CSSOM规则树 (OM:object model)
- 根据DOM Tree和CSSOM规则树构建Render Tree(渲染树)
- 根据Render Tree进行布局。(这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小)
- 布局阶段结束后是绘制阶段,将内容显示在屏幕上。
2). 题目
-
从输入url到渲染出页面的整个过程
-
window.onload 和DOMContentLoaded的区别
-
为什么建议将css放入到head中
FOUC:主要指的是样式闪烁的问题,由于浏览器渲染机制(比如firefox),在 CSS 加载之前,先呈现了 HTML,就会导致展示出无样式内容,然后样式突然呈现的现象。会出现这个问题的原因主要是 css 加载时间过长,或者 css 被放在了文档底部。
白屏:有些浏览器渲染机制(比如chrome)要先构建 DOM 树和 CSSOM 树,构建完成后再进行渲染,如果 CSS 部分放在 HTML尾部,由于 CSS 未加载完成,浏览器迟迟未渲染,从而导致白屏;也可能是把 js 文件放在头部,脚本的加载会阻塞后面文档内容的解析,从而页面迟迟未渲染出来,出现白屏问题。
-
为什么建议将js放入最后
JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么
它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复继续解
析文档。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的
原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。
[2]. 性能优化
-
1.性能优化原则
- 多实用内存、缓存或其他方法
- 减少CPU计算量,减少网络加载耗时
- (适用于所有编程的性能优化 - 空间换时间)
-
2.从何入手
- 让加载更快:
- 减少资源体积(压缩代码)
- 减少访问次数:合并代码,SSR服务器端渲染,缓存,雪碧图
- 使用更快的网络:CDN
- 让渲染更快
- css放在head,js放在body最下面
- 尽早开始执行JS,用DOMContentLoaded触发
- 懒加载(图片懒加载,上滑加载更多)
- 对DOM查询进行缓存
- 频繁DOM操作,合并到一起插入DOM结构
- 节流throttle,防抖 debounce
- 让加载更快:
-
3.ssr 服务端渲染(SSR:server side render)
- 服务器端渲染:将网页和书一起加载,一起渲染
- 非SSR(前后端分离):先加载网页,在加载数据,在渲染数据
- 早先的JSP ASP PHP,现在的VUE React都是SSR
-
4.懒加载
-
5.节流throttle
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> #box, #box2 { height: 100px; width: 100px; border: 1px solid #000; cursor: move; } </style> </head> <body> <div id="box"> <p draggable="true">拖动</p> </div> <div id="box2"> <p draggable="true">拖动</p> </div> <script> var box = document.getElementById('box'); var timer = null; box.addEventListener('drag', function (e) { if (timer) { return; } timer = setTimeout(() => { console.log(e.offsetX, e.offsetY); timer = null; }, 200) }, false); var box2 = document.getElementById('box2'); function throttle(fn, delay = 100) { let timer = null; return function() { if (timer) { return } timer = setTimeout(() => { fn.apply(this, arguments); timer = null; }, delay); } } box2.addEventListener('drag', throttle(function(e){ console.log(e.offsetX, e.offsetY); })) </script> </body> </html>
-
6.防抖debounce
-
防抖,即短时间内大量触发同一事件,只会执行一次函数,实现原理为设置一个定时器,约定在xx毫秒后再触发事件处理,每次触发事件都会重新设置计时器,直到xx毫秒内无第二次操作,防抖常用于搜索框/滚动条的监听事件处理,如果不做防抖,每输入一个字/滚动屏幕,都会触发事件处理,造成性能浪费。
<input type="text" id="search"> <input type="text" id="search2"> <script> const search = document.getElementById('search'); let timer = null; search.addEventListener('keyup', function () { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { //模拟触发change事件 console.log(search.value); //清空定时器 timer = null; }, 500) }) //封装 function debounce(fn, delay = 500) { let timer = null; return function () { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, arguments); timer = null; }, delay) } } const search2 = document.getElementById('search2'); search2.addEventListener('keyup', debounce(() => { console.log(search2.value); })) </script>
[3]. 安全
1). xss
3). xsrf
(三) 真题模拟
[1]. var和let const的区别
- var是ES5语法,let const 是ES6语法;var有变量提升
- var 和let是变量,可修改;const是常量,不可修改
- let const有块级作用域,var没有
[2]. typeof返回哪些类型
- undefined string number boolean symbol
- object(null等)
- function
[3].列举强制类型转换和隐式类型转换
- 强制: parseInt parseFloat toString等
- 隐式: if、逻辑运算、==、+拼接字符串
[4].手写深度比较,模拟lodash.isEqual
深度比较两个对象/数组是否相等。
function isObj(obj){
return typeof obj === 'object' && obj !== null;
}
function isEqual(obj, obj2) {
//判断是否是数组/对象
if (!isObject(obj) || !isObject(obj2)) {
return obj === obj2;
}
//判断数组/对象是否指向同一个地址
if (obj === obj2) {
return true;
}
//获取数组/对象的key,只能获取到第一层的key
let objKey = Object.keys(obj);
let obj2Key = Object.keys(obj2);
//判断key长度是否一致
if (objKey.length != obj2Key.length) {
return false;
}
//深度比较两个对象
for (let key in obj) {
//判断obj2是否有该key
if (obj2[key] === undefined) {
return false;
}
//递归比较
if(!isEqual(obj[key], obj2[key])){
return false;
}
}
//得到结果
return true;
}
[5]. split()和join()的区别
'1-2-3'.split('-'); // [1,2,3]
[1,2,3].join('-');//'1-2-3'
[6].数组pop push unshift shift分别做什么
- 功能是什么
- 返回值是什么
- 是否会对原数组造成影响
[7]. 纯函数
纯函数: 1.不改变源数组(没有副作用) 2.返回一个数组
- arr.concat()
- arr.map()
- arr.filter()
- arr.slice()
[8]. 数组slice和splice的区别
- 功能区别
- 参数和返回值
- 是否是纯函数
[9]. [10, 20, 30].map(parseInt)返回结果是什么?
parseInt(string, radix)
- radix
可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。
如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。
如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。[10, 20, 30].map((value, index) => { //parseInt(10,0) -- 10 //parseInt(20,1) -- NaN //parseInt(20,2) -- NaN return parseInt(value, index); }) //第二个参数是表示几进制,2表示二进制最大为1大于1数值为NaN,3表示三进制(基数)最大为2大于2的为NaN [10,0,1,2,3].map(parseInt); // 10 NaN 1 2 3
[10]. ajax请求get和post的区别
- get一般用于查询操作,post一般用于用户提交操作
- get参数拼接在url上,post挡在请求体内(数据体积可更大)
- 安全性:post易于防止CSRF
[11]. call和apply的区别
- 参数
[12]. 事件代理(委托)是什么
[13]. 闭包是什么,有什么特性?有什么负面影响?
-
闭包:闭包是指有权访问另一个函数作用域中的变量的函数
-
闭包的特性:
①.封闭性:外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外 界提供访问接口;
②.持久性:一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调 用之后,闭包结构依然保存在;
-
影响:变量会常驻内存,得不到释放,过多的使用闭包会导致内存溢出等。
[14]. 如何阻止事件冒泡和默认行为
[15]. 查找、添加、删除、移动DOM节点的方法
- (1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点 - (2)添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore() - (3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性
[16]. 如何减少DOM操作
- 缓存DOM查询结果
- 多次DOM操作,合并到一次插入
[17]. 解释jsonp的原理,为何他不是真正的ajax
①Ajax与JSONP这两种技术看起来很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jQuery等框架都把JSONP作为Ajax的一种形式。
②实际上Ajax与JSONP有着本质上的不同。Ajax的核心是通过XMLHttpRequest获取数据,而JSONP的核心则是动态添加<script>标签来调用服务器提供的js文件。
③Ajax与JSONP的区别也不在于是否跨域,Ajax通过服务端代理也可以跨域,JSONP也可获取同源数据。
[18]. document load 和DOMContentLoaded的区别
[19]. == 和 ===区别
- == 会尝试类型转换
- === 严格相等
[20]. 函数声明和函数表达式的区别
-
函数声明 function fn(){…}
-
函数表达式 var fn = function (){…}
-
(1)、以函数声明的方法定义的函数,函数名是必须的,而函数表达式的函数名是可选的。
-
(2)、以函数声明的方法定义的函数,函数可以在函数声明之前调用,而函数表达式的函数只能在声明之后调用。
-
(3)、以函数声明的方法定义的函数并不是真正的声明,他们仅仅可以出现在全局中或者嵌套在其它函数中。
[21]. new Object 和 Object.create的区别
- {}等同于new Object(),原型Object.prototype
- Object.create(null) 表示没有原型
- Object.create(obj) 以obj为原型创建新对象
[22]. 关于this的场景题
[23]. 关于作用域和自由变量的场景题
-
题目一
let i; for(i = 0; i < 10; i++){ setTimeout(()=>{ console.log(i); }) }
-
题目二
let a = 100; function test(){ console.log('第一个a',a); //对比 var a = 10 a = 10; console.log('第二个a',a) } test(); console.log('第三个a',a);
[24]. 判断字符串以字母开头,后面字母数字下换线长度6-30
//
var exg = /^[a-z]\w{5,29}$/i
console.log(exg.test('Aa1ss320'));
[25].手写字符串trim方法,保证浏览器兼容性
String.prototype.trim = function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
}
[26].获取多个数字的最大值
Math.max(10,20,35,100)
[27]. 如何用JS实现继承
- class继承
- prototype继承
[28]. 如何捕获js中的异常
-
方式一:
try{ // todi } catch (ex ) { console.lerror(ex) //手动捕获错误 } finally{ // todo }
-
方式二
前端js错误监控主要是利用了window.onerror函数来实现,onerror函数会在页面发生js错误时被调用。window.onerror = function (meessage, url, lineNum, colNum, error){ //第一,对跨域的js,如CDN的,不会有详细的报错信息 //第二,对于压缩的js,还要配合sourceMap 反查到未压缩代码的行、列 }
[29]. 什么是JSON
- json是一种数据格式,本质是一段字符串
- json格式和js对象结构一致,对js语言更友好
- window.JSON是一个全局对象:JSON.stringify()和JSON.parse()
[30]. 获取当前页面url参数
-
方式一
function query(name){ // ?a=10&b=100&c=125 const search = location.search.substr(1); const reg = new RegExp(`(^|&)${ name}=([^&]*)($|&)`, 'i'); search.match(reg); if(reg === null){ return null } return res[2]; }
-
方式二,兼容性不好
function query(name){ const search = location.search; const p = new URLSearchParams(search) return p.get(name) }
[31]. 将url参数解析为js对象
没做防错处理
function queryToArray(url){
var res = [];
var search = url.split('?')[1];//获取参数
search.split('&').forEach(item =>{
var arr = item.split('=');
var key = arr[0];
var value = arr[1]
res[key] = value;
})
return res
}
[32]. 手写flatern考虑多层级
var arr = [1,2,3,[4,5,6,[7,8,9,[10]]]]
function flat(arr){
//判断数组是否还有更深层
const isDeep = arr.some(item => item instanceof Object);
if(!isDeep){
return arr;
}
//concat只能拍平嵌套了一层的数组
const res = Array.prototype.concat.apply([], arr);
return flat(res);
}
console.log(flat(arr));
[33]. 数组去重
-
方式一:
var arr = [10,25,35,13,10,25]; function unique(arr){ var res = []; arr.forEach(item => { if(res.indexOf(item) < 0){ res.push(item) } }) return res; } console.log( unique(arr));
-
方式二:
function unique2(arr){ const set = new Set(arr); return [...set]; }
[34]. 手写深拷贝
//Object.assign不是深拷贝
[35]. 介绍一下RAF(requetAnimationFrame)
- 要想动画流畅,更新频率要60帧/s,即16.67ms跟新一次视图
- setTimeout要手动控制频率,而RAF浏览器会自动控制
- 后台标签页或隐藏iframe时,RAF会暂停,而setTimeout依然执行
[36]. 性能优化
- 原则: 多使用内存、缓存,减少计算、减少网络请求
- 方向:加载页面,页面渲染, 页面操作流畅度