- 前端基础github地址。
README.md
可以下载到typora
中打开,会有整个大纲目录显示! - 前端基础gitbook地址。
README.md
中会实时更新进度内容。以目录文档形式展示所有内容,gitbook
版本可建议后期碎片化时间进行复习使用! - 前端基础csdn地址。
CSDN
博客专栏前端自我修养进阶中,也会一篇一篇实时更新相关知识点。 - 前端基础一掘金地址、前端基础二掘金地址
[TOC]
-
标签语义化
- 合适的标签做合适的事情
- 标签分类:
- 块状标签
- 独占一行
- example:div,h1-h6,hr,table,form,p,li,dl,dt
- 行内标签
- example:span、a、img
- 行内块状标签
- example:input
- 块状标签
-
音视频
- 改变了传统的flash播放,采用video和audio等
-
webGL/canvas等动画渲染
-
websocket:
- 改变了传统的长轮询方式
-
盒子模型
- 标准盒模型
- width和height只是指内容content部分,不包括padding、border、margin部分
- IE盒模型(怪异盒模型)
- width和height包括border、padding的盒子
- 通常我们使用的都是标准盒模型,但是我们有些情况下需要使用IE盒模型的话可以设置
box-sizing:border-box
- 标准盒模型
-
水平居中的实现方式
- 使用定位
.container{ position:relative; } .box{ width:200px; height:200px; position:absolute; left:50%; top:50%; margin-left:-100px; margin-right:-100px; } //或者 .box{ position:absolute; left:50%; top:50%; transformX:-50%; transformY:-50%; }
- 使用flex
.container{ display:flex; justify-content:center; align-item:center; }
- 使用table
.container{ display:table-ceil; text-align:center; vertical-align:middle; width:500px; height:500px; } .box{ display:inline-block; }
- 使用JS控制
-
响应式布局
- 圣杯布局
- 利用position定位以及浮动还有负margin进行实现
- 双飞翼布局
- flex实现圣杯布局
- 圣杯布局
.container{
display:flex;
flex-direction:row;
justify-content:space-between;
}
.left{
height:200px;
flex:0 0 200px; //缩放比0 0,宽度200px
}
.center{
flex:1; //或者使用flex-grow
}
.right{
height:200px;
flex:0 0 200px;
}
- media媒体查询
- 适用于一套代码多端适配
- rem适用于移动端
- vm/vh百分比布局
- flex
- 使元素脱离文档流
- 使元素脱离文档流的其他的方式:
- 浮动
- 定位
- transform
- Css3动画
//方式一
.box a{
}
//方式二
a{
}
方式二:因为浏览器的渲染机制使从右到左进行查找的
- 基本类型:
- number
- string
- boolean
- null
- undefined
- 引用类型
- object
- function
- 特殊类型
- Symbol
- typeof
- instanceof
- constructor
- Object.prototype.toString.call
let a = {},b = '0',c = 0;
a[b] = '张三'
a[c] = '李四'
console.log(a[b]) //李四
对象中,数字属性名和字符串属性名都是一样的,数组是特殊的字符串
拓展:对象和数组的区别
let a={}, b=Symbol('1'), c=Symbol('1');
a[b]='张三';
a[c]='李四';
console.log(a[b]); //张三
Symbol创建的是唯一的值,不相等。对象的属性名不一定是字符串,如果是数字和字符串则是同一个值,因为索引是字符串。对象的属性名可以是不二null,Symbol、undefined等等。引用类型值都是变成字符串逆行存储。
拓展:自己实现一个Symbol
let a={}, b={n:'1'}, c={m:'2'};
a[b]='张三';
a[c]='李四';
console.log(a[b]); //李四
b、c作为引用的时候会被转化成为字符串,对象转化toString那么即
[Object Object]
,两个都是[Object Object]
,所以结果是李四
拓展:Object.prototype.toString项目中应用,跟valueOf跟toString区别(编译顺序)
基本类型直接存储,引用类型要放进堆里面,最终是把地址复制给这个值。
浏览一加载页面,就形成一个栈内存。每个函数执行,叫做把一个执行上下文压缩到栈中执行。
null和undefined
//立即执行函数,执行完被销毁,回收机制。但是由于i被占用因此不销毁。test表示的不是一个函数,而是一个函数返回的执行结果
var test = (function(i){
return function(){
alert(i*=2); //没有i,那么在它上级作用域中找。在哪创建的上级作用域就是谁
}
})(2);
test(5); //字符串4。test中没有形参。
alert输出的结果都是会转化成字符串的。
var a=0,
b=0;
function A(a){
A=function(b){
alert(a+b++);
};
alert(a++);
}
A(1); //"1"
A(2); //"4"
注意: a++先跟别人运算,再自身累加1;++a先自身累加1,再跟别人运算。
过程解释:
1、GO全局:初始化 a = 0,b = 0, A一个方法,引用地址,此处暂时记为AAAFFF000
2、A(1)执行,传入形参,此时在执行中a= 1,往下中性,A = function…,此处相当于重新定义了全局的方法A,改变了原来的方法A的指向,此处引用地址从AAAFFF000改变到另外一个记为BBBFFF000,继续执行alert(a++),由于a=1,并且由于下面demo可以看出,a是在执行之后再自身加1的,那么此时alert的是没有叠加之前的1
3、继续执行A(2),那么此时A指向的function是改变之后的BBBFFF000,此时传入的形参b=2,而a在其上一级作用域中,由步骤2可知,叠加之后为2,那么此时alert出来的应该是2+2,为字符串4(alert出来的会自动同toString转化)
let a = 1;
console.log(5+a++); //6
console.log(a) //2
let b = 1;
console.log(5+(++b)); //7
console.log(b) //2
闭包的作用:保存、保护
let obj = {
a: 100,
b: [10, 20, 30],
c: {
x: 10
},
d: /^\d+$/
};
let arr = [10, [100, 200], {
x: 10,
y: 20
}];
//=>深克隆
function deepClone(obj) {
if (typeof obj !== "object") return obj;
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
let cloneObj = new obj.constructor;
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
- 浅拷贝:只拷贝第一层,操作第二级仍然可以改第一级。只是浅拷贝,只拷贝引用
let obj = {
a: 100,
b: [10, 20, 30],
c: {
x: 10
},
d: /^\d+$/
};
let obj2 = {}
for(let key in obj){
//obj.hasOwnProperty(key),true是私有的,false不是私有的。不是私有的不遍历,只遍历私有的
if(!obj.hasOwnProperty(key))break;
obj2[key] = obj[key]
}
//ES6实现浅克隆
let obj3 = {...obj}
- 深拷贝:(递归/字符串转化)
// JSON.stringify&JSON.parse
let obj = {
a: 100,
b: [10, 20, 30],
c: {
x: 10
},
d: /^\d+$/
};
let obj2 = JSON.stringify(obj); //"{"a":100,"b":[10,20,30],"c":{"x":10},"d":{}}"
obj2 = JSON.parse(obj2)
JSON.parse
这种方式弊端:正则、函数、日期、symbol等,会有问题
//递归实现深克隆
function deepClone(obj){
//或者let newobj = new obj.constructor;或者{};一般obj.constructor为Object,防止传入的是实例
//不直接创建控对象的目的,克隆的结果和之前保持相同的所属类
//过滤特殊情况,object才递归
if(typeof obj === null)return null;
if(typeof obj !== "object")return obj;
if(obj instanceOf RegExp){
return new RegExp(obj)
}
if(obj instanceOf Date){
return new Date(obj)
}
let newobj = new Object();
for(let key in obj){
if(obj.hasOwnProperty(key)){ //判断是私有属性
newobj[key] = deepclone(obj[key ])
}
}
return newobj;
}
排除:null,Date,正则RegExp,以及不是object
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName(); //2
getName(); //4
Foo().getName(); //Foo()普通函数执行;Foo执行过程中,内部函数有个getName赋值,但是getName并不是私有的,于是此时重新定义全局的getName = -> 1,并且返回this,这里的this的指向就是window,那么应该是window.getName 1
getName(); //1
new Foo.getName(); //无参数new,点的方式叫做成员访问。优先级问题,先执行成员访问,new一个输出2的函数,那么输出也是2
new Foo().getName(); //有参数new,执行方式先new Foo,再getName。创建实例,实例的getName,那么应该找prototype上的,因此结果是3
new new Foo().getName(); //优先级new (new Foo()).getName().getName,先new Foo()创建一个实例foo,变成 new foo.getName(),此时变成了先成员访问,原型上的方法,输出3的方法,变成了new 3,即3
注意:由参数的new先执行new,无参数的new和成员访问两个同级别,谁先救先执行谁
答案为:2 4 1 1 2 3 3
//function声明加赋值,var先声明先不赋值
Foo AAAFFF0000
getName = func -> 5
//代码执行
Foo是一个堆,里面存了方法代码字符串,Foo仍然是一个对象,有prototype(也是一个引用类型即也是一个堆)、length、然后我们给他加了一个getName属性;
getName = func -> 4 //之前赋值输出5,之后重新赋值输出4的
变量提升:在var function,所有代码执行之前,带var和function提前定义和赋值
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
浏览器多线程,js单线程。时间队列EventQueue。微任务队列,宏任务队列。主线程代码先执行。
定时器、事件绑定、ajax都是宏任务,async、await、promise等是微任务
- 题一:
function A(){
alert(1);
}
function Fn() {
A=function(){
alert(2);
};
return this;
}
Fn.A=A;
Fn.prototype={
A:()=>{
alert(3);
}
};
A();
Fn.A();
Fn().A();
new Fn.A();
new Fn().A();
new new Fn().A();
//注意最后一个,箭头函数是不可以被new的,因为箭头函数没有原型链,也就没有constructor构造器
- 题三:
var a = ?;
if (a == 1 && a == 2 && a == 3) {
console.log(1);
}
两个等于号转化数据类型值相等,三个等于号要绝对相等
双等
- 对象==字符串 对象toString变成字符串,再变成数字,对比;[10] == 10
- null == undefined但是和其他比较不想等
- NaN和任何东西都不想等
- 剩下的都转化成数字。eg.
'1'==true
1、利用toString
由于两个等号对比,一方为数字,我们假设a为object(例如:[1],[2],[3]这样的,数组是特殊的object),那么他们执行的对比方式一定是要先toString再转化成数字进行对比的。因此我们可以在toString上做文章,去修改它的toString
var a = {
i:0,
toString(){
return ++this.i;
}
}
//因此三次对比,执行三次toString,依次返回1,2,3。valueOf也可以代替toString
var a= [1,2,3]
a.toString = a.shift; //shift方法删除数组第一个值,并且返回第一个数值。因此每次对比要调用a的toString的时候,依次获得的数值就是i,2,3
2、利用数据劫持get:
由于a是全局的,那么再全局上去劫持获取a的set方法去修改
var i = 0;
Object.defineProperty(window,'a',{
set(){
},
get(){
return ++i
}
})
console.log(a==1&&a==2&&a==3)
3、或者用true也行
-
react和vue区别
- vue代表MVVM(双向数据绑定)。
- 数据更改视图变化,视图更改数据变化
- React代表MVC。
- vue吧给我们把表单绑定事件写好了,React需要自己去绑定
- React中需要自己去实现onchange事件
- vue中直接v-mode直接帮我们绑定了数据,不用自己写onChange事件
- MVC和MVVM区别
- MVC默认值实现数据的更改(单向数据改变)
- MVVM实现数据更改视图更改,视图更改数据更改(双向数据改变)
- MVC这种只是缺少一个onChange事件绑定
-
vue中数据的双向绑定2.0和3.0的实现
- vue2.0中,通过
Object.definedProperty
进行set和get修改; - vue3.0中通过
Proxy
进行拦截set和get方法进行相关操作,实现数据的双向绑定。
- vue2.0中,通过
1、JSONP
利用script标签
- 只有get请求
- 不安全
- 有缓存
- 传递的数据大小有限制
- 需要服务器单独的支持
- 客户端
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
$.ajax({
url: 'http://127.0.0.1:8001/list',
method: 'get',
dataType: 'jsonp',
success: res => {
console.log(res);
}
});
</script>
- 后台
let express = require('express'),
app = express();
app.listen(8001, _ => {
console.log('OK!');
});
app.get('/list', (req, res) => {
let {
callback = Function.prototype
} = req.query;
let data = {
code: 0,
message: '测试数据'
};
res.send(`${callback}(${JSON.stringify(data)})`);
});
2、基于iframe的跨域解决方案
主域相同,子域不一样
- window.name
- document.domin
- location.hash
- post message
3、CORS跨域资源共享
客户端
import axios from 'axios';
import qs from 'qs';
axios.defaults.baseURL = "http://127.0.0.1:3000";
axios.defaults.timeout = 10000;
axios.defaults.withCredentials = true;
/*
* 设置请求传递数据的格式(看服务器要求什么格式)
* x-www-form-urlencoded
*/
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.transformRequest = data => qs.stringify(data);
/*
* 设置请求拦截器
* TOKEN校验(JWT):接收服务器返回的token,存储到vuex/本地存储中,每一次向服务器发请求,我们应该把token带上
*/
axios.interceptors.request.use(config => {
let token = localStorage.getItem('token');
token && (config.headers.Authorization = token);
return config;
}, error => {
return Promise.reject(error);
});
/*
* 响应拦截器
*/
axios.interceptors.response.use(response => {
return response.data;
}, error => {});
export default axios;
服务器
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "";
res.header("Access-Control-Allow-Credentials", true);
res.header("Access-Control-Allow-Headers", "PUT,POST,GET,DELETE,OPTIONS,HEAD");
res.header("Access-Control-Allow-Methods", "Content-Type,Content-Length,Authorization, Accept,X-Requested-With");
req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();
});
4、基于http proxy实现跨域请求
开发时候用proxy,部署用nginx反向代理
vue
- 属性传递
- 发布订阅(EventBus):$on / $emit
- Provide / inject
- slot插槽
- $parent / $children
- vuex
react
- 属性
- 发布订阅
- React.createContext
- redux / react-redux / mobix / dva
组件通信有哪些方案
-
属性方案:父传子
-
vue发布订阅(子传父)
- 属性加发布订阅
-
React传回调函数(子传父)
- 属性加回调函数
祖先和后代进行通信:后代元素中的所有元素都放到祖先中,叫做上下文
- 任何之间的通信使用本地存储方案
- localstorage(cookie有大小限制,不是一个量级的)
- redux/vuex(一刷新没有;公共状态管理、本地存储、优化)
session和cookie的区别
- 服务器设置session,服务器返回给客户端的信息会带set-cookies,客户端会把信息设置到cookie中,只能看不能改。客户端再次请求的时候,会带上cookie。
- set
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
//转化成Set
//转化成Array方式一
let arr = [...new Set(ary)];
//转化成Array方式二
let arr = Array.from(new Set(ary))
-
遍历对比
- 遍历,重新开辟一个新数组,没有相等的push进去
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16]; let arr = []; for(var i;i<ary.length;i++){ let item = ary[i] let args = ary.slice(i+1) //后面的值一一对比 if(args.indexOf(item)>-1){ }else{ arr.push(item) } }
- 遍历,与后面一一对比,设置为null,最后把值为null的全部筛出
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16]; let arr = []; for(var i;i<ary.length-1;i++){ //最后一个不用对比,肯定与前面都不想等 let item = ary[i] let args = ary.slice(i+1) //后面的值一一对比 if(args.indexOf(item)>-1){ arr[i] = null; //每一个与后面的进行对比,如果是有相同的就设置成null } } ary = ary.filter(item=>item!==null) //筛去等于null的
-
拿数组中的每一项,往新容器中存储,如果没有就存,有就不存
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
let obj = {}
for(var i;i<ary.length;i++){
let item = ary[i];
//如果对象没有这个属性就是undefined;也可以用in或者Object.keys。不是
if(typeof obj[item] !== 'undefined'){ //一定有了这个属性
//把最后一项拿过来替换。长度减1,i减1
ary[i] = ary[ary.length - 1]
ary.length--;
i--;
continue;
}
obj[item] = item
}
obj = {}
- 先排序,再相邻比较
ary.sort(); //升序
- ES6 : Set
,
let arr = [...new Set(ary)] //使用展开运算符
let arr = Array.from(new Set(ary)) //使用Array.from
- 拿所有项和其后面的每一项进行对比,如果有重复的话就删除(最后一项不拿,因为其后面没有东西进行比较了)
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
for(let i = 0; i < ary.length - 1;i++){
let item = ary[i],
args = ary.slice(i+1); //取出除当前项之后的所有项为一个数组
if(args.indexOf(item)>-1){ //判断当前项是否在其后面数组项中存在;或者此处可以使用includes
//此处有两个思路:1、包含,将当前项删除 2、定义一个新数组将当前的不包含的放到新数组中,这样的话要遍历所有的包括最后一项
//删除方式一:splice(i,1);i--; //i--是为了解决删除后导致的数组塌陷问题
//使用splice删除会造成问题 1、原来数组改变,如果继续i++会造成数组塌陷问题 2、性能不好,一旦当前项删除,后面每一项的索引都要改变
//删除方式二:原来数组克隆一份一模一样的,之后在克隆数组中去删除
//删除方式三:赋值为null,之后过滤掉ary.filter(item=>item !== null)
// ary[i] = null;
//删除方式四:用最后一项替换,替换之后整个数组长度要减少1,并且替换过来后当前的替换项需要重新对比所以i--
ary[i] = ary[ary.length - 1]
ary.length--;
i--;
}
}
- 对象键值对方式(数组)
- 拿出一项放入空容器,如果已经存储过,删除当前项
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
let obj = {};
for(let i = 0;i<ary.length;i++){
let item = ary[i];
if(typeof obj[item] !== 'undefined'){ //对象中有这个属性,不存储
//此处对数组的操作跟上面的方式对数组的操作一样,都是讲最后一项替换到当前项
ary[i] = ary[ary.length - 1]
ary.length--;
i--;
continue;cshu
}
obj[item] = item;
}
obj=null; //obj使用完之后,销毁掉当前使用的堆
- 先排序,再相邻比较
- 相邻项处理方案
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
ary.sort((a,b)=>a-b) //升序排序
//方式一:每一项和后一项进行比较,如果相同就删除当前项。最后一项没有后一项,所以不用
for(let i = 0;i<ary.length-1;i++){
if(ary[i] === ary[i+1]){
ary.splice(i,1);
ary.length--;
i--;
}
}
//方式二:可以使用正则
ary = ary.join('@')+'@' //"12@12@14@15@16@23@23@25@25@"
let reg = /\d+@\1*/g // \d拿到数字。 \d+@ 拿到数字加上@符号
let arr = []
ary.replace(reg,(val,group1)=>{
arr.push(Number(group1.slice(0,group1.length-1) ))
})
conole.log(arr)
数组去重经典的四大方案:
使用ES6的Set方案
前一项跟后面每一项进行比较
使用空容器存储验证是否存过
相邻项方案
sort方法,用于排序,并且是在原来数组基础上进行排序,不生成新数组
- 冒泡排序
- 拿当前项和后一项进行比较,每一轮比较后,最大的放到末尾
- 下一轮就只比较前部分,不用比较已经放到末尾的最大的
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
function bubble(ary){
for(let i = 0;i < ary.length-1;i++){ //外面一层控制循环几轮
for(let j = 0;j < ary.length-1-i;j++){ //里面一层控制循环到哪个数值
if(ary[j] > ary[j+1]){
[ary[j],ary[j+1]] = [ary[j+1],ary[j]] //ES6解构赋值
}
}
}
}
- 插入排序
- 类似于扑克牌。新抓的牌比手里某张大,那么放到这张后面,小那么放到前面。
- 先取第一个数据放入一个新数组中,然后依次从第二个数值开始取,一次在新数组中从最大的往最小的进行比较。如果比新数组中某项值大就放在它后面,如果比到第一项还没有找到的话,那么插入新数组最前面
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
function insert(ary){
let handle = [];
handle.push(ary[0])
for(let i = 1;i<ary.length;i++){
let A = ary[i]
for(let j = handle.length-1;j>=0;j--){
let B = handle[j]
if(A>B){
handle.splice(j+1,0,A); //将A插在B后面
break;
}
if(j===0){
handle.unshift(A); //如果是比较到第1个还没有大于第1个,那么直接放到数组的最前面
}
}
}
return handle;
}
insert(ary)
unshift方法是在数组头部插入一个元素,并且是在原数组上进行操作不生成新数组
- 快速排序
- 取出中间项,比中间项小的放左边,比中间项大的放右边,没有的不再处理
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
function quick(ary){
if(ary.length <= 1){
return ary
}
let middleIndex = Math.floor(ary.length/2) //获取中间项index
let middleValue = ary.splice(middleIndex,1)[0] //将中间项哦从原数组中删除,并且获得删除的中间项的值
let aryLeft = [];
let aryRight = [];
for(let i = 0;i < ary.length;i++){
ary[i]>middleValue?aryRight.push(ary[i]):aryLeft.push(ary[i])
}
//递归方式让左右持续这样处理,一直到左右两边都排好为止。并且左+中+右 就是最后的结果
return quick(aryLeft).concat(middleValue,quick(aryRight))
}
- ES6 : flat方法
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
arr = arr.flat(Infinity) //后面传入的参数是扁平化的级数;Infinity表示扁平化无限级
- toString
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
arr = arr.toString().split(',').map((item)=>{
return parseFloat(item)
})
//toString后 "1,2,2,3,4,5,5,6,7,8,9,11,12,12,13,14,10"
//split(',')后 ["1", "2", "2", "3", "4", "5", "5", "6", "7", "8", "9", "11", "12", "12", "13", "14", "10"]
//然后再把每一项转化成数字
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
arr = JSON.stringify(arr).replace(/\[|\]/g,'').split(', ').map((item)=>{
return parseFloat(item)
})
// JSON.stringify(arr)后 "[[1,2,2],[3,4,5,5],[6,7,8,9,[11,12,[12,13,[14]]]],10]"
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
arr.join('|').split(/(?:,|\|)/g).map((item)=>{
return parseFloat(item)
})
- 循环验证数组
- 循环验证是否为数组,是的话继续循环,不是的话直接存入
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
function flatten(arr){
//some循环数组中的每一项,返回的是true那么就是找到了要求中的。
while(arr.some(item => Array.isArray(item))){
arr = flatten([].concat(...arr)) //[].concat(...arr)这样操作会展开一层。递归继续扁平化处理
}
return arr;
}
some验证数组中的某一项是否有符合规则的。some返回的结果不是true就是false
some用于检测数组中是否有符合条件的元素,方法会依次执行数组的每个元素
- 如果有一个满足条件的,则返回true,剩余的元素不会再执行检测
- 没有满足条件的,返回false
every用于检测数组中的元素是否都符合条件
- 如果检测的数组中有一个不满足条件,那么返回false,并且剩余的不再检测
- 如果都满足条件那么返回true
var A = [3, 4, 5, 5] var B = A.some((item)=>{ return item >=5; //只要数组中有大于5的那么B就是true })Find返回的结果如果有就找出这项结果返回,如果没有就返回undefined。并且只查找这一项
var A = [3, 4, 5, 5] var B = A.find((item)=>{ return item >=4; //有返回这项值,没有返回undefined })
//循环递归,判断当前项是否是数组,如果不是数组那么存进新数组中,如果是数组那么继续校验
(function(){
function myFlat(){
let result = [],
_this = this;
let fn = (arr) => {
for(let i = 0;i<arr.length;i++){
let item = arr[i]
if(Array.isArray(item)){ //如果是数组那么自己递归继续展开,不是的话直接push到result中
fn(item);
continue;
}
result.push(item)
}
}
fn(_this)
return result;
}
Array.prototype.myFlat = myFlat;
})()
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
arr = arr.myFlat();
1
- 方式一:递归
//构造前两项,判断n是否从第三项即值为2开始。是的话,等于前两项之和,采用递归方式
function fabonacci(n){
if(n<=1) return 1; //斐波那契数列前两项,1
return fabonacci(n-2)+fabonacci(n-1)
}
- 方式二:
function fabonacci(n){
if(n<=1) return 1; //斐波那契数列前两项,1
let arr = [1,1];
let i = n +1 - 2; //之后需要构造的项的数量
while(i>0){
arr.push(arr[arr.length-1]+arr[arr.length-2]) //依次构造后一项
i--;
}
return arr[arr.length-1]
}
- 方式三:递归
function fabonacci(count){
fn(count,curr=1,next=1){
if(count=0){
return 1;
}else{
return fn(count-1,next,curr+next) //把当前项作为下一项
}
}
return fn(count);
}
- 输入一个正数N,输出所有和为N的连续正数序列
- 例如:输入15
- 结果:[[1,2,3,4,5],[4,5,6],[7,8]]
//从N开始,连续M个正数序列的和
function fn(count){
let result = [];
let middle = Math.ceil(count/2)
for(let i = 1;i<=middle;i++){
for(let j = 2;;j++){ //j累加次数
let total = (i+(i+j-1))*j/2 //连续数只之和公式,首项➕尾项 乘以项数 除以2
if(total>count){
break;
}else if(total === count){
result.push(createArr(i,j))
break;
}
}
}
return result;
}
function createArr(n,len){
let arr = new Array(len).fill(null);
arr[0] = n;
arr = arr.map((item,index)=>{
return n+index
})
return arr;
}
call和apply都是function原型上的方法,用于改变this指向的,唯一的区别就是传入参数的形式不一样,call是一个一个传参,而apply把所有参数用数组形式传。bind与他们类似(传参数也是一个个的类似call),都是改变this指向,只是预先处理函数,但是并不会立即执行。 经过测试,call比apply性能要好一些。
//使用apply场景
let arr = [10,20,30],
obj = {};
function fn(x,y,z){
}
fn.apply(obj,arr) //x,y,z分别为10 20 30
fn.call(obj,...arr) //基于ES6的展开运算符,可以展开依次传递给函数
自己实现性能测试(只供参考,任何代码测试都跟测试环境有关。CPU、内存、GPU等电脑当前性能不会有相同的时间)。console.time
可以测出一段程序执行的时间。在火狐浏览器中可以安装firebug
插件取监控更精确的时间
console.time('A') //A相当于给时间测试起个名字
for(let i = 0;i<100000;i++){
}
console.timeEnd('A')
- 正向预查:要匹配的字符串必须满足pattern这个条件
- 负向预查:要匹配的字符串必须不满足pattern这个条件
- 括号中的内容只是参与条件,并不参与真正的条件
let reg = /^(?!^[a-zA-Z]+$)(?!^[A-Z0-9]+$)(?!^[0-9a-z]+$)(?![0-9]+$)[a-zA-Z0-9]{6,16}$/
- 相当于写一个属性选择器。
- 获得所有标签,循环所有标签。得到每一个标签,传入什么属性就按照这个属性去获取属性值。特殊情况,class这些只要包含这个属性值就可以
let ary = $attr('id','AAA')
function $attr(property,value){
let elements = document.getElementByTagName('*'), //获取当前页面的所有标签
arr = [];
//方式一:可以借用数组循环
// [].forEach.call(elements,item=>{})
//方式二:利用Array.from将类数组非数组转化成为数组
elements = Array.from(elements)
elements.forEach((item)=>{
let itemValue = item.getAttribute(property) //在这个标签中获取property
if(property === 'class'){ //class样式类属性名要做特殊处理,因为class中可能有多个属性值
//判断当前字符串中是否包含着这个单词
new RegExp(/\b+value+\b/).test(itemValue)?arr.push(item):null // \b表示单词开头结尾
return;
}
if(itemValue === value){ //获取的值和传递的值相等校验成功就是我们需要的
arr.push(item)
}
})
return arr;
}
let str = "test一个测试,just测试一下下smileyqp",
reg = /\b[a-z]+\b/ig; //后面的g是全局匹配,i是忽略大小写
str.replace(reg,value=>{ //value正则捕获的内容
return ' '+value+' '
}).trim(); //trim()去除开头和结尾的空格;trimLeft()去除开头空格;trimRight()去结尾空格
//str "test 一个测试, just 测试一下下 smileyqp"
- 对象的实例可以调用对象原型。上的方法。由于数字5可以调用add那么说明,add一定是数字5原型上的方法。数字5属于Number类,那么Number类上一定要有add方法
- 并且由于是链式调用(链式写法),那么执行完add之后,返回的一定是可以继续调用minus的,那么,每一次执行add返回一定是一个数字 ,即返回Number类的实例
(function(){
function check(n){
n = Number(n); //进行检测
return isNaN(n)?0:n //判断是否为有效数字,有效数字返回n,非有效数字返回0
}
function add(n){
n = check(n); //进行有效性检测处理
return this+n; //这里的this是操作的实例,即5,那么this+n即5+3 =》 8
}
function minus(n){
n = check(n);
return this-n;
}
//Number.prototype.add = add;
//Number.prototype.minus = minus;
//也可以如下写,JQ源码走红经常下面这种写法
['add','minus'].forEach((item)=>{
Number.prototype[item] = eval(item); //eval将字符串转化成表达式
})
})()
(5).add(3).minus(2)
- 箭头函数语法上比普通函数更简洁(ES6中每种函数都可以形参赋默认值和使用…剩余运算符)
function fn(x){
return function(y){
return x+y
}
}
let fn = x => y=>x+y
- 箭头函数中没有this,它里面出现的this从属于所属上下文 (使用call、apply等任何方式都无法改变this指向)
let obj = {
name:'smileyqp'
}
function fn1(){
console.log(this)
}
fn1.call(obj) //this => obj
let fn2 = ()=>{
console.log(this)
}
fn2.call(obj) //this => window
- 箭头函数中没有Arguments类数组,只有基于
…arg
传递的参数集合(数组)
let fn = (...arg ) =>{
console.log(arg) // [10, 20, 30]
}
- 箭头函数不能被new执行,因为箭头函数没有this也没有prototype
function Fn (){
this.X = 100;
}
fn.prototype.getX = function(){}
let f = new Fn;
思考题拓展:
题目一:数组上实现一个each方法,实现下面的三个要求
let arr = [10,20,30,'AA'],
obj = {};
arr = arr.each(function(item,index){
// this => obj 1、第二个参数不传,this指向window
if(!isNaN(item)){
return false; //2、如果不是数字,那么返回的是false
}
return item*10; //3、返回结果是啥就把数组中当前项替换掉
},obj)
//这个方法最后实现的结果是 [100,200,300,false]
//解答
(function(){
function each(fn,obj){
this.bind(obj);
for(let i = 0;i<this.length;i++){
this[i] = isNaN(Number(this[i]))?false:fn(this[i])
}
return this;
}
Array.prototype.each = each;
})()
题目二:重写replace,replace([REG正则],callback)
let str = 'smileyqp2019smile2020'
str = str.replace(/smile/g,function(...arg){
//arg中存储了每一次大正则匹配的信息和小正则匹配的信息
return ; //返回把正则匹配的替换后的字符串
})
//解答
(function(){
function replace(reg,fn){
for(let i = 0;i<this.length;i++){
...
}
}
String.prototype.replace = replace;
})()
let str = 'smileyqpTestItSMILEYQP@沛沛$3434'
str = str.replace(/a-zA-Z/g,(content)=>{ //每一次正则匹配到的结果
//验证是否为大写:1、转化成大写之后是否和原来一样,一样那原来的为大写,反之之前为小写2、ASCII表中找大写字母的取值范围(65-90)
//1、content.toUpperCase() === content
//2、content.charCodeAt() >=65 || content.charCodeAt <=90
return content.toUpperCase() === content?content.toLowerCase:content.toUpperCase;
})
实现一个字符串匹配算法,从字符串S中查找是否存在字符串T,若存在则返回第一次所在位置,不存在返回-1(不能基于indexOf以及includes等内置方法)
- 思路一:循环原始字符串中的每一项,让每一项从当前位置街区T.length个字符和T比较,一样返回索引,一共循环S.length-T.length+1次
(function(){
function myIndexOf(T){
//this 原始的字符串,即S
let lenT = T.length,
lenS = S.length,
result = -1;
if(lenT>lenTS){
return -1;
}
for(let i = 0;i<lenS-lenT+1;i++){
let substr = S.substr(i,lenT)
if(substr === T){
result = i;
break;
}
}
return result;
}
String.prototype.myIndexOf = myIndexOf;
})()
let S = 'yqp27982348张三smile&&&smile',
T = 'smile'
console.log(S.myIndexOf(T))
- 思路二:正则处理
- 直接正则匹配这个字符串,如果结果为null返回-1,部位null直接可以在正则匹配的 结果中找到index就是索引
(function(){
function myIndexOf(T){
//this 原始的字符串,即S
let reg = new RegExp(T),
res = reg.exec(this);
return res === null ? -1:res.index
}
String.prototype.myIndexOf = myIndexOf;
})()
let S = 'yqp27982348张三smile&&&smile',
T = 'smile'
console.log(S.myIndexOf(T))
1、协议:http https ftp
2、域名 www.smileyqp.com smileyp.cn smile.yqp.smileyqp.com.cn
3、请求路径 index.html /stu. stu/index.html
4、问号传参 ?name=smileyqp&age=18
5、哈希值
协议、请求路径、问号传参、哈希可以省略
let str = 'http://www.smileyqp.com/index.html'
leg reg = /^((http|https|ftp):\/\/)?(([\w-]+\.)+[a-z0-9]+)((\/[^/]*)+)?(?:\?[^# ]+)?(#.+)?$/i;
function Foo(){
Foo.a = function(){
console.log(1)
}
this.a = function(){
console.log(2)
}
}
Foo.prototype.a = function(){
console.log(3)
}
Foo.a = function(){
cosnole.log(4)
}
Foo.a(); //4
let obj = new Foo(); //new Foo()的时候也会吧Foo当作一个函数执行;此时Foo上的属性a =>1,其中this指obj,obj.a =>2
obj.a(); //2
Foo.a(); //1
- 前端性能优化的重要方案,通过图片或者数据的延迟加载,可以加快页面加载速度,第一次加载的速度变快,并且只有滑动到图片部分才会进行加载
- 处理方案
- 将所有需要延迟加载的图片用一个盒子包起来,设置宽高和默认的占位图
- 开始让所有的image的src为空,将图片真实地址放到image的自定义属性上,让img隐藏
- 等到所有的其他资源加载完成之后我们才开始去加载图片
- 对于有很多图片,当页面滚动的时候,当前图片完全显示出来后,再加载图片
- 单张图片懒加载
//html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载</title>
<style>
*{
margin: 0;
padding: 0;
}
.imgBox{
margin: 1000px auto;
width: 300px;
height: 200px;
overflow: hidden;
background: pink;
}
.imgBox img{
width: 100%;
}
</style>
</head>
<body>
<div class="imgBox">
<img src="" alt="懒加载" data-img="https://gss0.baidu.com/9vo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/3ac79f3df8dcd100bbd10c8e738b4710b8122fcb.jpg"/>
</div>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="./delayImg.js"></script>
</body>
</html>
//delayImg.js
let $imgBox = $(".imgBox"),
$img = $imgBox.children('img'),
$window = $(window)
/**
* 加载的时机:
* 1、当页面其他的所有资源都加载完成的时候
* 2、当页面滚动到其位置的时候,图片完全出现在视野之中
*/
// $(document).ready();//dom结构加载完成
$(window).on('load scroll',function(){ //在load和scroll两个事件的时候都会触发;JQuery中事件绑定支持多事件绑定,两个事件触发的时候执行相同的事件;
if($img.attr('isLoad')==='true'){
return; //加载过之后不会重新加载
}
console.log('ok')
let $A = $imgBox.outerHeight() + $imgBox.offset().top;
let $B = $window.outerHeight() + $window.scrollTop()
if($A<=$B){
//加载真实图片
$img.attr('src',$img.attr('data-img'))
$img.on('load',()=>{
//加载成功
// $img.css('display','block')
console.log('图片加载成功!')
$img.stop().fadeIn() //fadeIn是jq中的渐现
})
$img.attr('isLoad',true) //attr存储的自定义属性值都是字符串格式'true'
}
});
- 多图片懒加载
//html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多图片懒加载</title>
<style>
*{
margin: 0;
padding: 0;
}
.container{
width: 800px;
margin: 0 auto;
}
.imgBox{
margin: 0px auto;
width: 300px;
height: 200px;
overflow: hidden;
background: pink;
margin-bottom: 20px;
}
.imgBox img{
width: 100%;
}
</style>
</head>
<body>
<div class="container">
<div class="imgBox">
<img src="" alt="懒加载" data-img="https://gss0.baidu.com/9vo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/3ac79f3df8dcd100bbd10c8e738b4710b8122fcb.jpg"/>
</div>
</div>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="./moredelayImg.js"></script>
</body>
</html>
//moredelayImg.js
let $container = $('.container'),
str = ``,
$imgBoxs = null,
$window = $(window)
new Array(20).fill().forEach((item)=>{ //new Array(20).fill() 创造长度为20的数组每一项用null填充
str+='<div class="imgBox"><img src="" alt="懒加载" data-img="https://gss0.baidu.com/9vo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/3ac79f3df8dcd100bbd10c8e738b4710b8122fcb.jpg"/></div>'
})
console.log(str)
$container.html(str);
$imgBoxs = $container.children('.imgBox');
//多张图片延迟加载
$window.on('load scroll',()=>{
//获取浏览器距离body的距离
let $B = $window.outerHeight() + $window.scrollTop()
console.log($imgBoxs)
//循环获取每一张图片区域,根据自己距离body的距离计算出里面的图片是否进行加载
$imgBoxs.each((index,item)=>{
console.log(index,item)
let $item = $(item),
$itemA = $item.outerHeight() + $item.offset().top,
isLoad = $item.attr('isLoad')
if($itemA <= $B && isLoad !== 'true'){ //如果这个盒子已经懒加载过依次那么就不再次进行懒加载处理
$item.attr('isLoad',true);
$img = $item.children('img')
$img.attr('src',$img.attr('data-img'))
$img.on('load',()=>{
//加载成功
// $img.css('display','block')
console.log('图片加载成功!')
$img.stop().fadeIn() //fadeIn是jq中的渐现
})
}
});
})
let nums1 = [1,2,3,2]
let nums2 = [2,2,2]
let arr = [];
for(let i = 0;i<nums1.length;i++){
let item1 = nums1[i]
for(let j=0;j<nums2.length;j++){
let item2 = nums2[j]
if(item1===item2){
nums1[i] = null;
nums2[j] = null;
arr.push(item1)
break;
}
}
}
console.log(arr) //[2,2]
let nums1 = [1,2,3,2]
let nums2 = [2,2,2]
let arr = [];
nums1.forEach((item,index)=>{
let n = nums.indexOf(item);
if(n>0){
nums1.splice(index,1)
nums2.splice(n,1)
arr.push(item)
}
})
给定一个数组,将数组中的元素向右移k个位置,其中k是非负数,例如:
输入:[1,2,3,4,5,6]和k=3
输出:[5,6,1,2,3,4]
- slice
输入:[1,-100,78,90] k = 2
输出:[90,1,-100,78]
(function(){
function rotate(key){
//参数处理,key>0
if(key<0||key === 0||key === this.length)return this;
if(key>this.length){key = key%this.length}
//slice支持负数索引,直接就是数组的后几位
return this.slice(-key).concat(this.slice(0,this.length-key))
}
Array.prototype.rotate = rotate;
})()
let arr = [1,2,3,4,5,6,7],
k = 3;
arr.rotate(3); // [5, 6, 7, 1, 2, 3, 4]
slice
参数:开始点,结束点,返回:切割的数组
splice
参数:开始点、长度,返回:删除的这部分返回
- splice
输入:[1,-100,78,90] k = 2
输出:[90,1,-100,78]
function rotate(key){
//参数处理,key>0
if(key<0||key === 0||key === this.length)return this;
if(key>this.length){key = key%this.length}
return this.splice(this.length-key,key).concat(this)
}
Array.prototype.rotate = rotate;
let arr = [1,2,3,4,5,6,7],
k = 3;
arr.rotate(3); // [5, 6, 7, 1, 2, 3, 4]
- 最后一项删除放到最开头,执行k
输入:[1,-100,78,90] k = 2
输出:[90,1,-100,78]
//写法一:
function rotate(key){
//参数处理,key>0
if(key<0||key === 0||key === this.length)return this;
if(key>this.length){key = key%this.length}
for(let i = 0;i<=key;i++){
this.unshift(this.pop()); //this.pop()最后一项;unshift首部插入
}
return this;
}
Array.prototype.rotate = rotate;
//写法二:
function rotate(key){
//参数处理,key>0
if(key<0||key === 0||key === this.length)return this;
if(key>this.length){key = key%this.length}
new Array(k).fill('').forEach((item)=>{
this.unshift(this.pop()); //this.pop()最后一项;unshift首部插入
})
return this;
}
Array.prototype.rotate = rotate;
let arr = [1,2,3,4,5,6,7],
k = 3;
arr.rotate(3); // [5, 6, 7, 1, 2, 3, 4]
- 函数科里化:预先处理的思想(利用闭包的机制)
let obj = {
name:'OBJ'
}
function fn(...arg){
console.log(this,arg)
}
document.body.onclick = fn; //this=>BODY
document.body.onclick = function(ev){
//=>ev 事件对象:给元素的某个事件绑定方法,当事件触发会绑定这个方法,并且把当前事件的相关信息传递给这个函数事件对象
}
实现:
- 点击时候,this指向修改成obj,并传入事件对象以及两个参数100,200
//bind就是一个最经典的柯里化
(function(){
//context就是传入的obj用来改变this指向的,如果没有就默认写的是window
function myBind(context=window,...outerArgs){
let _this = this;
return function(...innerArgs){
_this.call(context,...innerArgs.concat(outerArgs))
}
}
Function.prototype.myBind = myBind;
})()
let obj = {
name:'OBJ'
}
document.body.onclick = fn.myBind(obj,100,200)
函数的科里化:是利用闭包的保存思想,也就是函数执行形成一个闭包,存储一些变量值,当要使用的时候再使用
- 闭包的两大作用:
-
保护
-
保存
-
最简单科里化函数编程思想示例。科里化=》闭包。形成闭包,里面的参数供子集使用。
-
function fn(x){
//相当于预先在闭包中把值存储起来
return function(y){
return x+y
}
}
fn(100)(200)
//第一次执行fn(100),执行完成之后当前作用域销毁,但是形成闭包值保留,进行第二个方法执行
请实现一个add函数实现以下功能 add(1) //1 add(1)(2) //3 add(1)(2)(3) //6 add(1)(2)(3)(4) //10 add(1)(2,3) //6 add(1,2)(3) //6 add(1,2,3) //6
function currying(fn,length){ //函数的length是获取它有多少个形参
length = length || fn.length;
return function(...args){
if(args.length >= length){
return fn(...args)
}
return curring(fn.bind(null,...args),length-args.length)
}
}
let add = currying((...args)=>{
return eval(args.join('+')) //求args里面值相加的和
},5) //这个后面的5是总共要求几个字数的和,比如这里求五个的。这里是不管几次调用函数,只是参数的数量
function Dog(name){
this.name = name;
}
Dog.prototype.bark = function(){
console.log('bark')
}
Dog.prototype.sayName = function(){
console.log('my name is'+this.name)
}
要实现一个_new方法实现以下:
let dog = _new(Dog,'dooooog')
dog.sayName()
dog.bark()
1、像普通函数执行一样、形成一个私有的作用域
- 形参赋值
- 变量提升
2、默认创建一个对象,然函数中的this指向这个对象,这个对象就是当前实例 3、代码执行 4、默认把创建对象返回
function _new(Fn,...arg){
//Fn当前要new的类;arg是给构造函数传的参数
//let obj = {};
//Fn.call(obj,...arg)
let obj = Object.create(Fn.prototype); //创建原型链为Fn.prototype的对象实例
obj.__proto__ = Fn.prototype;
Fn.call(obj,...args)
return obj;
}
注意:
Object.create
是创建一个空对象,并且对象的原型链指向我们传入的那个参数(即:让我们创建的这个空对象作为我们传入的那个参数的实例)
- 题一:
let ary1 = ['A1','A2','B1','B2','C1','C2','D1','D2']
let ary2 = ['A','B','C','D']
//因为使用arr.sort((a,b)=>a.localeCompare(b)),时候没有Z,ABCD这些会在最前面
ary2 = ary2.map(item=>item+'Z')
let arr = ary1.concat(ary2)
arr.sort((a,b)=>a.localeCompare(b)).map(item=>{
return item.replace('Z','') //去除Z
})
console.log(arr) //["A1", "A2", "A", "B1", "B2", "B", "C1", "C2", "C", "D1", "D2", "D"]
- 题二:
let ary1 = ['D1','D2','A1','A2','C1','C2','B1','B2']
let ary2 = ['B','A','D','C']
//要求合并输出的数组为["D1", "D2", "D","A1", "A2", "A","C1", "C2", "C","B1", "B2", "B"]
let n = 0;
for(let i = 0;i<ary2.length;i++){
let item2 = ary2[i]
for(let j = 0;j<ary1.length;j++){
let item1 = ary1[j]
if(item1.includes(item2)){
n = j; //包含记录索引,后面有包含会更新这个值。保存最后一项匹配的索引
}
}
//把当前的值插入到索引的后面
ary1.splice(n+1,0,item2) //从n+1删除0项,将item2插入n+1的前面,也就相当于n后面插入n+1
}
console.log(ary1) //["D1", "D2", "D", "A1", "A2", "A", "C1", "C2", "C", "B1", "B2", "B"]
for(var i = 0;i<10;i++){
setTimeut(()=>{
console.log(i)
},10000)
}
//输出10次10,因为不是私有变量。setTimeout是异步的
//将var改成let,let定义的局部块的变量,每次循环都会在当前块作用域中形成私有变量i,定时器执行的时候所使用的i是所属作用域的i
for(let i = 0;i<10;i++){
setTimeout(()=>{
console.log(i)
},10000)
}
//输出0,1,2,3,...,因为不是私有变量。setTimeout是异步的
//使用闭包保存i
for(var i = 0;i<10;i++){
(function(i){
setTimeout(()=>{
console.log(i)
},10000)
})(i)
}
//使用闭包也可以这样写
for(var i = 0;i<10;i++){
setTimeout(((i)=>{
return ()=>{
console.log(i)
}
})(i),10000)
}
//也可以使用bind保存i的值,基于bind预先处理机制。循环的时候就将bind要预先处理的函数传过去
var fn = function(i){
console.log(i)
}
for(var i = 0;i<10;i++){
setTimeout(fn.bind(null,i),10000)
}
- 匿名函数如果设置了函数名,在外面是无法调用的,但是在函数里面可以调用执行
- 并且这个名字相当于一个常量,这个名字存储的值不能修改(非严格模式下不会报错,但是不会有任何的效果,严格模式下直接报错。可以理解为const创建的常量 )
let fn = function AAA(){
console.log(AAA) //当前函数
}
var b = 10;
(function b(){
b = 20
console.log(b) //function b;b此时相当于一个常量,不能被改变
})()
console.log(10) //10
va b = 10;
(function(){ //去掉匿名函数的名称b之后,里面的b就变成全局的了
b = 20
console.log(b) //20
})()
console.log(10) //20
现在要让上面的匿名函数中的b的值log变成20,并且全局b仍然是10,怎样实现?
- 将b变成私有变量,声明它或者改为形参
//方法一:改为形参
var b = 10;
(function b(b){ //形式参数
b = 20
console.log(b) //20
})()
console.log(10) //10
//方法二:声明它
var b = 10;
(function b(){
let b = 20 //或者使用var声明也可以
console.log(b) //20
})()
console.log(10) //10
#####==
&===
==
进行比较,如果左右两边的数据类型不相同的话那么先转换成相同的数据类型,然后再进行比较{}=={}
两个对象进行比较时比较堆内存的地址null==undefined
相等的,但是三个等号就不相等NaN == NaN
不相等,NaN和谁都不相等[12]=="12"
对象和字符串项比较,是把对象toString转换成字符串之后进行比较- 剩余所有的情况进行比较的时候都是转化成数字(对象转换成数字都是现toString转换成字符串,再转换成数字)(前提是类型不一样的时候)
- 对象转化成数字都是现转化成字符串,再转化成数字
- 字符串转化成数字,只要出现一个非数字字符,结果就是NaN
- 布尔转数字:true => 1 false=>0
null
转化成数字0undefined
转化成数字NaN
[12]==true 都是转化成数字. => Number([12].toString()) 输出12 == 1 不相等
[] == false []=> 0 false => 0 相等
[] == 1 []=>0 不相等
"1" == 1 相等
true == 2 不相等
给对象添加一个私有的toString方法,重构私有方法
- 方法一:
使得a==1&&a==2&&a==3成立
对象要先toString然后进行转化成数字
var a = {
n:0,
toString:function(){ //所有的值调用toString都是先看自己私有有没有,没有再原型链上找
return ++ this.n;
}
}
- 方法二
//shift删除数组第一项,返回删除的内容,原有的数组改变
var a = [1,2,3]
a.toString= a.shift
a==1&&a==2&&a==3
- 方法三
let n = 0;
Object.defineProperty(window,'a',{
get:function(){
return ++n;
}
})
//设置成全局变量实际并不好,可以优化
Object.defineProperty(window,'a',{
get:function(){
return this.val?++this.val:this.val=1;
}
})
console.log(a==1&&a==2&&a==3)
ES6新增方法
-
Array
- from将其他的转化成数组
- isArray判断是否为数组
-
String.fromCharCode(122)
=> z.'z'.charCodeAt()
=> 122 -
Object.create([obj])
创建空对象,原型链指向空对象 -
Object.defineProperty
用于定义某个对象中的参数,三个参数:对象、属性、值
let obj = {
name:'Jane'
}
//Object.defineProperty(obj,'name','smileyqp') //每次获取时候会触发get方法,于是可以从get方法着手
//监听获取和设置
Object.defineProperty(obj,'name',{
get:function(){
return 'smileyqp'
},
set:function(){
return 'Mary'
}
})
let obj = {
2:3,
3:4,
length:2,
push:Array.prototype.push
}
obj.push(1) //=> obj[obj.length] = 1 => obj[2]=1 length原来基础上加一 obj[length] => 3
obj.push(2) ////=》obj[obj.length] = 2 => obj[3]=2 length原来基础上加一 obj[length] => 4
console.log(obj)
//所以obj值如下
let obj = {
2:1,
3:2,
length:4,
push:Array.prototype.push
}
//数组push方法原理
Array.prototype.push = function(val){
this[this.length] = val;
return this.length; //现在这个长度是数组增加1之后了的长度
}
//push往数组末尾新增一个元素,返回的是数组的长度
let obj = { 1:2323, 4:3492, 8:2673 } 要求得到[2323, null, null, 3492, null, null, null, 2673, null, null, null, null]
- 方法一:直接new Array().fill(null)然后map使用index对比
let obj = {
1:2323,
4:3492,
8:2673
}
let arr = new Array(12).fill(null).map((item,index)=>{
return obj[index+1]?obj[index+1]:item
})
- 方法二:利用obj.length长度,然后Array.from
let obj = {
1:2323,
4:3492,
8:2673
}
obj.length = 13 //因为要截取后面的12个
//Array.from(obj) 后的值[undefined, 2323, undefined, undefined, 3492, undefined, undefined, undefined, 2673, undefined, undefined, undefined,undefined]
Array.from(obj).slice(1).map(item=>{ //slice是动某个索引值开始
return item?item:null
})
- 方法三:利用Object.keys
let obj = {
1:2323,
4:3492,
8:2673
}
//Object.keys,是遍历对象中的所有key并且以数组的方式返回
let arr = new Array(12).fill(null)
Object.keys(obj).forEach(item=>{
arr[parseInt(item)-1] = obj[item]
})
深拷贝
- 值类型
let a = 100;
let b = 100co;
a = 200;
console.log(b) //100
- 引用类型
let a = {age:10};
let b = a;
b.age = 20;
console.log(a.age) //20
-
常见值类型
- undefined
- string
- number
- boolean
- symbol
-
常见引用类型
-
obj
-
array
-
null 特殊引用类型,指向的地址为空地址
-
function 特殊引用类型,不用于存储数据,所以没有'拷贝、赋值函数'这一说法
const obj1 = {x:100,y:200}
const obj2 =obj1;
let x1 = obj1.x; //干扰作用,值类型直接赋值过去,之后再没有关系
obj2.x = 101;
x1 = 102;
console.log(obj1) //{x:101,y:200}
- 判断所有值类型
- 判断是否函数
- 判断是否引用类型(不可再细分)
function deepClone(obj = {}){
if(typeof obj !== 'object'||obj === null){
return obj;
}
//初始化返回结果
let result;
if(obj.instanceof Array){
result = []
}else{
result = {}
}
for(let key in obj){
if(obj.hasOwnProperty(key)){ //保证是这个对象自己拥有的私有属性,不是原型的属性
//调用递归
result[key] = deepClone(obj[key])
}
}
return result;
}
const a = 100+10; //110
const b = 100+'10'; //10010
const c = true+'10'; //true10
100 == '100'; //true
0 == ''; //true
0 == false; //true
false ==''; //true
null == undefined; //true
除了
==null
之外其他的一律用===
。并且,例如:a==null
相当于a===undefined||===null
-
- truely变量和falsely变量
- truely变量:两步非运算得到true。
!!a===true
- falsely变量:两步非运算得到false。
!!a===false
- truely变量:两步非运算得到true。
- truely变量和falsely变量
class Student{
constructor(name,age){
this.name = name;
this.age = age;
}
sayHi(){
console.log('hi'+this.name+this.age)
}
}
let stu = new Student('smileyqp',100)
- extends
- super
- 扩展或者重写方法
class Person(){
constructor(name,age){
this.name = name;
this.age = age;
}
eat(){
console.log(this.name+'eat food')
}
}
class Student extends Person{
constructor(name,age,number){
super(name,age)
this.number = number;
}
sayHi(){ //扩展方法
console.log('hi'+this.name+this.age)
}
}
class Teacher extends Person{
constructor(name,age,major){
super(name,age)
this.major = major;
}
teach(){
console.log(this.name+'teach'+this.major)
}
}
let smileyqp = new Student('smileyqp',20)
//补充
smileyqp.eat() //smileyqp eat food
smileyqp.__proto__.eat() //会爆错,因为它相当于在smileyqp.__proto__即Student的原型上去调用的,没有定义name所以会报错
- instanceof可以去判断引用类型
- Object是所有class的父类
smileyqp instanceof Student; //true
smileyqp instanceof Person; //true
smileyqp instanceof Object; //true
[] instanceof Array; //true
{} instanceof Object; //true
[] instanceof Object; //true
- 每个class都有显示原型
prototype
- 每个实例都有隐式原型
__proto__
- 实例的隐式原型
__proto__
都指向对应的class的原型prototype
//隐式原型和显式原型(案例demo接上个题目的案例)
console.log(smileyqp.__proto__) //隐式原型
console.log(Student.prototype) //显式原型
console.log(smileyqp.__proto__ === Student.prototype) //true
- 先在自身的属性和方法上进行查找
- 如果找不到就到
__proto__
中查找
console.log(Student.prototype.__proto__)
console.log(Person.prototype)
console.log(Student.prototype.__proto__ === Person.prototype)
- 顺着原型链进行查找,有返回true,没有返回false
- 如何判断一个变量是否是数组(类型判断instanceof,a instanceof Array)
- 手写一个简易的jQuery,考虑插件和扩展性(原型和原型链)
//jquery做dom查询的
class jquery{
constructor(selector){
cons result = documnent.querySelectorAll(selector)
const length = result.length;
for(let i = 0;i < length;i++){
this[i] = result[i]
}
this.length = length;
this.selector = selector;
}
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)
})
}
}
//1、插件机制
jQuery.prototype.dialog = function(info){
alert(info)
}
//2、造轮子
class myJQuery extends jQuery{
constructor(selector){
super(selector)
}
}
- class的原型本质怎么理解(class和继承)
- 原型和原型链的图示
- 属性和方法的执行规则
- this的不同应用场景,如何取值
- 手写bind函数
- 实际开发中闭包的应用场景,并举例说明
//创建10个<a/>标签,点击的时候弹出来对应的序号
let i,a;
for(i = 0;i<=10;i++){
a = document.createElement('a');
a.innerHTML = i+"<br/>";
a.addEventListener('click',function(e){
e.preventDefault();
alert(i) //10
})
document.body.appendChild(a)
}
let a;
for(let i = 0;i<=10;i++){ //let i是定义块作用域
a = document.createElement('a');
a.innerHTML = i+"<br/>";
a.addEventListener('click',function(e){
e.preventDefault();
alert(i) //1,2,3,4,5...
})
document.body.appendChild(a)
}
-
作用域:变量的合法的使用范围
-
作用域分类
- 全局作用域
- 函数作用域
- 块级作用域(ES6新增)
//ES6新增 if(true){ let x = 100; //这里用let声明的变量或者const声明的常量,他们的作用域都是这个块之内 } console.log(x); //会报错
- 自由变量:一个变量在当前作用域没有定义但是被使用了
- 向上级作用域一层一层寻找,直到找到为止
- 如果到全局作用域都没有找到的话那么就会报错
xx is undefined
//函数作为返回值,返回之后再执行
function create(){
let a = 100;
return function(){
console.log(a)
}
}
let fn = create();
let a = 200;
fn(); //100 函数执行是在全局作用域;函数的定义在create函数里面,往上级作用域寻找
f
- 作为普通函数
- 使用call、bind、apply
- 作为对象方法被调用
- 在class方法中调用
- 箭头函数
function fn1(){
console.log(this)
}
fn1(); //window
fn1.call({x:100}) //{x:100}
const fn2 = fn1.bind({x:200})
fn2(); //{x:200}
箭头函数没有this,其中this取上级作用域this
//demo1
const zhansan = {
name:'zhangsan',
sayHi(){
console.log(this) //this当前对象
},
wait(){
setTimeout(function(){
console.log(this) //this window
})
}
}
//demo1
const zhangsan = {
name:'zhangsan',
sayHi(){
console.log(this) //当前对象
},zuo
waitAgain(){
setTimeout(()=>{
console.log(this) //当前对象
})
}
}
function.prototype.mybind = function(){
//将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
//获取this(数组第一项)
const t = args.shift()
//fn1 bind中的fn1
const self = this;
return function(){
return self.apply(self,args)
}
}
- 隐藏数据
- 做一个简单的cache工具
function createCache(){
const data = {} //闭包中的数据,隐藏不被外界访问
return {
set:function(key,val){
data[key] = val;
},
get:(){
return data[key]
}
}
}
//隐藏不被外界访问,是指c只能通过set,get设置获取。只提供api访问,其他方式改不了
const c = createCache();
c.set('a',100);
c.get('a');
//异步
console.log(1)
setTimeout(()=>{
console.log(2)
},1000)
console.log(3)
//同步
console.log(1)
alert(2)
console.log(3)
- 同步和异步的区别是什么
- 基于js是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
- 手写Promise加载一张图片
const src = '../xxximg.png'
function loadImg(src){
return new Promise((resolve,reject)=>{
let img = document.createElement('img')
img.onload = function(){
console.log('loaded')
resolve(img)
}
img.onerror = function(){
reject(new Error(`图片加载失败${src}`))
}
img.src = src
})
}
loadImg(url).then(img=>{
console.log(img)
return img
}).catch((error)=>{
console.log(error)
})
-
前端使用的异步的场景有哪些
-
网络请求,比如:ajax图片加载
onsole.log('start') let img = document.createElement('img') img.onload = function(){ onsole.log('loaded') } img.src = './xxx.png' console.log('end')
-
定时任务,比如:setTimeout
-
响应事件
-
图片加载
-
-
单线程和异步(异步是由单线程这个背景来的)
-
JS是单线程语言,只能同时做一件事
-
浏览器和nodejs已支持Js启动进程如web worker。但是并不能改变js是单线程的本质
-
Js和dom渲染共用同一个线程,因为js可以修改dom结构
-
遇到等待(网络请求、定时任务)不能卡住
-
需要异步,解决单线程的问题
-
回调callback函数形式
-
- 变量类型和计算
- 原型和原型链
- 作用域和闭包
- 异步和单线程
-
typeof能判断哪些类型
- 基础类型
- 引用类型
-
何时使用===何时使用==
-
值类型和引用类型的区别
-
手写深拷贝
- 值类型vs引用类型,堆栈模型,深拷贝
- typeof运算符能干嘛
- 类型转换、truely和falsely变量
- 如何准确判断一个变量是不是数组(instanceof原型链查找)
- 手写一个jquery,考虑插件和扩展性
- class的原型本质怎样理解
- class和继承,结合手写jquery的示例理解
- instanceof表象和本质
- 原型和原型链:图示&执行规则
-
this的不同应用场景,如何取值
- this是在函数执行的时候确定,函数定义的时候不能确定
-
手写bind函数
-
实际开发中遇到的闭包场景,并且举例说明(eg:隐藏数据,只提供api)
//创建10个a标签,点击依次弹出1,2,3,4,5,6,7,8,9,10
let a;
for(let i = 0;i<=10;i++){
a = document.createElement('a')
a.innerHTML = i+'</br>';
a.addEventListener('click',function(e){
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
- 作用域和自由变量(自由变量是不是在当前块作用域定义,但是在这使用的)
- 闭包:常见两种形式&自由变量查找规则
- this
- 异步和同步的区别
- 手写Promise加载一张图片
- 前端使用异步的场景
- ajax请求
- setTimeout
- 单线程和异步、异步和同步的区别
- 前端异步应用场景(网络请求&定时任务)
- Promise解决callback hell问题
- JS基础知识,规定语法(ECMA 262标准)
- JS WEB API,网页操作API(W3C标准)
- 前者是后者的基础,两者结合才能真正的在实际中应用
- DOM操作
- BOM操作(浏览器上的事情:浏览器导航、浏览器url地址、浏览器跳转、浏览器宽高)
- 事件绑定
- ajax
- 存储
前言
- Vue和React框架应用广泛、封装了DOM操作
- 但是DOM操作一直都是前端工程师的基础必备知识
- 只会框架不会dom操作的程序员不会长久
-
DOM属于哪种数据结构
- 基于树形结构
-
DOM操作的常用API
-
attr(attribute)和property的区别
- property:修改对象的属性,不会体现在html结构中(不会对节点有什么影响)
- attr:直接修改html属性,会改变html的结构(改变标签结构)
- 两者都可能引起dom重新渲染(尽量用property去进行操作,因为设置property一定会重新渲染,attribute不一定。重新dom渲染是一件比较耗费性能的事情)
-
一次性插入多个dom节点,考虑性能
- dom节点缓存
- 创建片段,一次性插入createFragment
- DOM本质
- 从html解析出来的树
html实际上也是一种特殊的xml
-
DOM节点的操作
- 获取节点
- getElementById
- getElementByClassName
- getElementByTagName
- document.querySelectorAll
- attribute:直接修改dom结构,直接对标签有影响
const pList = ducument.querySelectorAll('p') const p = pList[0] p.getAttribute('data-name') p.setAttribute('data-name','smileyqp') p.getAttribute('style') p.setAttribute('style','font-size:30px')
- property:修改js变量的属性,设置不会对标签有什么影响
- 用js的属性操作的一种形式
const pList = ducument.querySelectorAll('p') const p = pList[0] console.log(p.style) console.log(p.style.width) console.log(p.style.className) console.log(p.nodeName) //nodeName节点的名称 console.log(p.nodeType) //nodeType节点的类型
- 获取节点
-
DOM结构的操作
- 新增/插入操作
- 获取子元素列表,获取父元素
- 删除子节点
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
//新建节点
const newP = document.createElement('newP')
newP.innerHTML = 'this is newP'
//插入节点
div1.appendChild(newP)
//移动节点
div2.appendChild(p1) //将一个已经存在于dom中的元素append到另外一个中去,那么就是将节点移动过去的
//获取父元素
console.log(p1.parentNode)
//获取子元素列表;类似乎组转化成数组Array.prototype.slice.call,然后过滤类型为1,即p的元素节点
div1.childNodes();
const div1childNodesP = Array.prototype.slice.call(div1.childNodes()).filter((child)=>{
if(child.nodeType === 1){
return true;
}
})
//删除子节点
div1.removeChild(div1childNodesP[0])
-
DOM性能
- dom查询做缓存(减少dom查询。将dom缓存,dom查询改成变量查询)
//不缓存dom查询结果 for(let i = 0;i<document.getElementByTagName('p').length;i++){ //每次查询都会重新去计算length,频繁进行dom查询 } //缓存dom查询的结果 const plist = document.getElementByTagName('p') const plength = plist.length; for(let i = 0;i<plength;i++){ //缓存dom查询,只需要进行一次dom查询 }
- 将频繁操作改成一次操作
- 创建文件片段,然后再一次插入(createFragment)
const listNode = document.getElementById('list') //创建一个文档片段,此时还没有插入dom const frag = document.createDocumentFragent(); //执行插入 for(let x = 0;<=10;x++){ const li = document.createElement('li'); i.innerHTML = 'list item'+x; frag.appendChild(li) } //都完成之后再插入dom树中 listNode.appendChild(frag)
- 如何识别浏览器类型
- 分析拆解url的各个部分
- navigator:浏览器信息
- screen:屏幕信息
- location:地址信息
- history:前行后退信息
//navigator
const ua = navigator.userAgent; //获取当前浏览器的信息
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)
//screen
onsole.log(screen.width)
cobsole.log(screen.height)
//location
console.log(location.href)
console.log(location.protocol)
console.log(location.pathname)
console.log(location.search) //获取url传的参数
console.log(location.hash) //获取哈希,也就是#后面的内容
//history
history.back();
history.forward()
- 编写一个通用的事件监听函数
- 描述事件冒泡的流程
- 基于dom树形结构
- 事件会顺着触发元素往上冒泡
- 应用场景:事件代理
- 无限下拉图片列表,如何监听每个图片的点击
- 事件代理
- 通过
e.target
来获取触发元素 - 用matches来判断是否触发元素
- 事件绑定(addEventListener)
const btn = document.getElementById('btn1')
btn.addEventListener('click',event=>{
console.log('click')
})
//普通绑定:简易通用的事件绑定函数;详细通用事件绑定函数在下面将会提到
function bindEvent(elem,type,fn){
elem.addEventListener(type,fn)
}
const a = document.getElementById('link1')
bindEvent(a,'click',e=>{
e.preventDefault(); //阻止默认行为;比如组织链接的点击跳转
console.log(e.target); //获取点击的元素
alert('this is aaa')
})
- 事件冒泡:顺着dom结构往上上冒泡
- 例如:在body上添加事件,如果点击它子元素,那么都会冒泡到body上去
stopPropagation
可以阻止冒泡
- 事件代理
- 事件代理: 不好挨个去绑定事件,就绑定事件到它父标签上。
- 事件代理是基于事件冒泡来做的,有了事件冒泡的机制才能在这个机制上去实现代理。
- 事件代理的场景:一般是通过某种形式,比如图片列表的无限下拉加载。可能并不能知道容器里面到底有多少个图片标签,也没法一个个去绑定事件。这个时候我们就可以通过冒泡去获取事件,我们就可以通过一些方式去拿到这个点击的图片
- 综合以上两种(加上冒泡的情况)
//代理绑定
function bindEvent(elem,type,selector,fn){ //selector要筛选的触发元素选择器
if(fn == null){ //说明传的是三个参数;也就是只是普通绑定,第三个参数就是fn
fn = selector;
selector = null;
}
elem.addEventListener(type,event=>{
//event.preventDefault();
const target = event.target;
if(selector){ //有selector也就是代理绑定(存在冒泡情况)
if(target.matches(selector)){ //判断当前元素是否符合我们传入的这个选择器
fn.call(target,event)
}
}else{ //没有selector,普通绑定情况
fn.call(target,event) //因为需要fn的this指向这个元素,所以要call绑定一下当前触发的元素
}
})
}
//代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3,'click','a',function(event){ //注意:这里不能用箭头函数,因为里面this指向
event.preventDefault();
alert(this.innerHTML)
})
- 手写一个简易的ajax
function ajax(url){
const p = new Promise((resolve,reject)=>{
const xhr = new XMLHttpRequest();
xhr.open('GET','data/test.json',true); //true的意思是异步请求
xhr.onreadystatechange = function(){
if(xhr.readystate === 4){
if(xhr.status === 200){
resolve(JSON.parse(xhr.responseText))
}else if(xhr.status === 404){
reject(new Error('404 not found!'))
}
}
}
xhr.send(null)
});
return p;
}
const url = '/data/test.json'
ajax(url).then(res=>{
console.log(res)
}).catch(err=>{
console.log(err)
})
- 跨域常用的实现方式
- XMLHttpRequest
//手写简易的ajax
//get请求;post请求差不多
const xhr = new XMLHttpRequest();
xhr.open('GET','data/test.json',true); //true的意思是异步请求
xhr.onreadystatechange = function(){
if(xhr.readystate === 4){
if(xhr.status === 200){
alert(xhr.responseText)
}else if(xhr.status === 404){
console.log('404 not found ')
}
}
}
xhr.send(null)
- 状态码
- 跨域:同源策略跨域解决方案
- 什么是跨域(同源策略)
- JSONP
- CORS(服务端支持)
-
ajax请求时,浏览器要求当前网页和server必须同源(安全)
-
同源:协议、域名、端口三者必须一致
-
加载图片、css、js可以无视同源策略
<img src=""/>
(注意:有的图片可能做了防盗链)<link src=""/>
<script src=""></script>
<img src=""/>
可以做统计打点,可使用第三方统计服务<link src=""/>
和<script src=""></script>
可以使用CDN,CDN一般都是外域<script src=""></script>
可以实现JSONP
- 所有的跨域都必须经过serve端允许和配合
- 未经serve端允许就实现跨域,说明浏览器有漏洞,危险信号
<script></script>
可以绕过跨域限制- 服务器可以任意动态拼接数据返回
- 所以,
<script></script>
可以获得跨域数据,只要服务端愿意返回
- 服务器允许跨域
- jquery
- fetch
- axios
- 描述cookie、localstorage、sessionStorage的区别
- 容量
- API易用性
- 是否跟随http 请求发出去
- cookie
- localstorage和sessionstorage
- 本身用于浏览器和server通信
- 被"借用"来本地存储
- 可用
document.cookie=...
来修改 - 存储大小,最大4kb
- http请求时候需要发送到服务端,增加请求数据量
- 只能用
document.cookie=…
来修改,太过简陋
- html5专门为存储而设计,最大可以存储5M
- API简洁易用,用setItem和getItem
- 不会随着http请求被发出去
- localstorage数据会永久存储,除非代码或者手动删除
- sessionstorage只会存在于当前会话浏览器关闭则清空
- 一般用localstorage多一些
- git
- 调试工具
- 抓包
- webpack babel
- linux常用命令
- 最常用的代码版本管理工具
- 大型项目多人协作,必须使用git
- elements
- console
- debugger
- network
- application
- 移动端h5页,查看网络请求
- win一般用fiddler
- mac一般用charles
- 抓包过程:
- 手机电脑连同一个局域网
- 手机代理到电脑上
- 手机浏览网页即可抓包
- 查看网址请求
- 网址代理
- https
- ES6模块化,浏览器暂不支持
- ES6语法,浏览器不完全支持
- 压缩代码、整合代码,让网页加载更快
- 公司上线的服务器一般是linux
- 测试机也一致,linux
- linux基础命令
- 运行环境即浏览器(server 端有nodejs)
- 下载网页代码,渲染出页面,期间执行若干js
- 要保证在浏览器中:稳定且高效
- 网页加载过程
- 性能优化(体验优化)
- 安全
- 从输入url到渲染出页面的整个过程(见下:资源形式、加载过程、渲染过程)
- 下载资源:各个资源类型、下载过程
- 渲染页面:结合html、css、js图片等
window.onload
和DOMContentLoad
的区别- window.onload页面全部加载完成包括图片
- DOMContentLoaded是dom渲染完成即可,此时图片视频可能还没有加载完
document.addEventListener('load',()=>{
console.log('window loaded')
})
document.addEventListener('DOMContentLoaded',()=>{
console.log('dom loaded')
})
- 加载资源的形式
- 加载资源的过程
- 渲染页面的过程
- html代码
- 媒体文件,如图片视频等
- js、css代码
- DNS解析:域名=>IP地址
- 浏览器更具IP地址向服务器发起http请求
- 服务器处理http请求,并返回给浏览器
- 根据html生成dom树
- 根据css生成cssom(css对象模型)
- 将dom树和cssom整合形成render tree(渲染树:像是dom树中挂了css属性)
- 根据render tree渲染页面
- 遇到
<script>
则暂停渲染,优先加载并执行js代码完成再继续(js进程和渲染进程共一个线程。script中可能有代码改了之前执行的结果,所以遇到script先暂停渲染)
- 防止重复渲染(本来按照没有css的默认样式渲染,完成之后,又发现有样式又重新结合这个cssom进行重新生成render tree重新渲染。并且网速慢的时候对用户可能会出现两种样式切换)
- js没有放到最后就会导致页面渲染的过程比较长。因为js会暂停渲染。我们是期望先渲染完成再修改
- 图片渲染并不会阻塞dom渲染,只不过可能先空着等图片加载完成之后显示
- 多使用内存、缓存或者其他方法
- 减少CPU计算量、减少网络加载耗时
- (适用于所有编程性能优化、空间换时间)
- 加载更快
- 减少资源体积:压缩代码
- 减少访问次数:合并代码(js、css、雪碧图)、ssr服务端渲染(数据一起给前端)、缓存
- 使用更快的网络:CDN(根据地域做静态文件访问服务)
- 渲染更快
- css放进head中,js放到body最下面
- 尽早执行js,用DOMContentLoad去触发
- 懒加载(图片懒加载、上滑加载更多)
- 对dom查询进行缓存
- 频繁dom操作合并到一起插入dom结构
- 节流throttle和防抖debounce (渲染更加流畅)
- 资源合并
- 缓存
- 静态资源加hash后缀,根据文件内容计算哈希
- 文件内容不变、则哈希不变,那么url不变
- url和文件不变,则会自动触发http缓存机制返回304(减少了资源请求)

-
SSR(server side render)
- 服务端渲染:将网页和数据一起加载一起渲染
- 非SSR(前后端分离):先加载网页、再加载数据、再渲染数据
- 早先的JSP、PHP、APS都属于ssr,现在的react vue ssr
-
懒加载
- 缓存dom查询
- 多个dom操作合并一起插入dom结构
- 尽早开始js执行
- 在dom渲染结束之后就可以开始js执行没必要等到图片、视频等多媒体资源都加载完成之后再去执行
- 函数节流:函数执行一次后,只有大于执行周期之后才会执行第二次(规定时间内只触发第一次生效)
//fn:要截流的函数;delay规定的时间
function throttle(fn,delay){
//记录上一次触发的时间
var lastTime = 0;
return function(){
var curTime = Date.now();
if(curTime-lastTime>delay){
fn.call(this); //修正this指向问题
lastTime = curTime; //闭包方式保存上次变量值lastTime
}
}
}
- 防抖函数:频繁触发的函数,规定时间内,只触发最后一次
function debounce(fn,delay){
var timer = null; //记录延时器
return function(){
if(timer){clearTimeout(timer)} //清除上一次延时器
timer = setTimeout(function(){
fn.apply(this)
},delay)
}
}
- 场景:监听一个输入框的文字变化后触发change事件
- 直接用keyup事件会频繁触发change事件
- 防抖:知道用户输入结束或者暂停之后才会触发change事件
function debounce(fn,delay=500){ //debounce是对函数的封装,最糊返回的是一个函数
var timer = null;
return function(){
if (timer){clearTimeout(timer);}
timer = setTimeout(function(){
fn.apply(this,arguments);
timer = null;
},delay)
}
}
//使用
input1.addEventListener('keyup',debounce(function(){
console.log('使用防抖函数:'+this.value)
}),1000)
- 场景:拖拽一个元素时候要随时拿到这个元素的位置
- 如果直接用drag事件那么会频繁触发,十分容易导致卡顿
- 保持一个频率连续触发
function throttle(fn,delay=500){
let timer = null;
return function(){
if(timer){return;}
setTimeout(()=>{
fn.apply(this,arguments); //apply:绑定this,绑定参数
timer = null;
},delay)
}
}
//使用
div1.addEventListener(throttle(function(e){
console.log(e.offsetX)
console.log(e.offsetY)
},100))
- XSS跨站请求攻击
- XSRF跨站请求伪造
- 博客前端界面嵌入script脚本
- 脚本内容:获取cookie发送到服务器(服务器配合跨域)
- 发布博客,有人查看,可以轻松获取查看人的cookie信息
- 替换特殊字符。例如:
<
变成&It
;>
变成>
,那么script就不会作为脚本执行 - 可以使用
https://www.npmjs.com/package/xss
的xss工具
- 比如:攻击者想要购买一件商品,知道了购买的请求url等
- 然后用发邮件形式,发送一个图片隐藏的链接
<img src='xxx.com/pay?id=100'/>
。图片可以跨域。 - 如果收到的人已经登陆过这个购物网站,收到的人点击打开链接,此时就会将用户信息带过去,就会发送用点击链接的那个人的用户信息去购买
- 使用POST接口,因为使用POST接口跨域是需要serve端进行支持的
- 增加验证,比如:密码验证码、指纹等
var num1 = 11;
var num2 = 22;
function fn(num,num1){
//num,num1块级作用域中变量,分别传入时候只为11,22
num = 100;
num1 = 100;
num2 = 100; //没有用var声明,不是块作用域中变量,那么修改的就是全局的num2
console.log(num) //100
console.log(num1) //100
console.log(num2) //100
}
fn(num1,num2)
console.log(num1) //11
console.log(num2) //100
console.log(num) //undefined报错
function Person(name,age,salary){
this.name = name;
this.age = age;
this.salary = salary;
}
function f1(person){
//执行f1(p)的时候person的地址和p的地址指向同一个内存地址,因此修改person.name会修改p.name
person.name = "ls"
person = new Person('aa',18,10) //此时重新将person指向另外一个引用地址,所以跟p无关,不修改p值
}
var p = new Person('zs',18,10000)
console.log(p.name) //zs
f1(p)
console.log(p.name) //ls
- 已知有字符串类似
get-element-by-id
格式,写一个function将其转化成驼峰命名表示法getElementById
function fn(str){
var arr = str.split('-');
for(let i = 1;i<arr.length;i++){
arr[i] = arr[i].charAt(0).toUpperCase()+arr[i].substr(1,arr[i].length-1)
}
return arr.join(''); //数组拼接成字符串
}
function bubble(arr){
for(let i = 0;i < arr.length;i++){
for(let j = 0;j < arr.length - 1 -i;j++){
if(arr[j]>arr[j+1]){
[arr[j],arr[j+1]] = [arr[j+1],arr[j]];
}
}
}
return arr;
}
- 示例:比如数组
[1,2,3,4,5,6,7,8]
反转数组之后的结果是[8,7,6,5,4,3,2,1]
- 类似于首位交换
0 len-1
1 len-1 -1
2 lem-1 -2
function fn(arr){
for(let i = 0;i < arr.length/2;i++){
[arr[i],arr[arr.length -1 -i]] = [arr[arr.length -1 -i],arr[i]]
}
return arr
}
- Set
function fn(arr){
return Array.from(new Set(arr))
}
- 一项一项去拿,然后和其后面形成的数组进行对比
function fn(arr){
for(let i = 0;i < arr.length;i++){
let val = arr[i],
compareArr = arr.slice(i); //取出i之后的所有项组成的数组
if(compareArr.indexOf(val)>-1){ //如果后面存在这个值
arr.splice(i,1)
arr.length--;
i--;
}
}
return arr;
}
function fn(arr){
for(let i = 0;i < arr.length;i++){
let val = arr[i],
compareArr = arr.slice(i); //取出i之后的所有项组成的数组
if(compareArr.indexOf(val)>-1){ //如果后面存在这个值
arr[i] = null;
}
}
arr = arr.filter((item)=>{return item != null})
return arr;
}
- 先排序再去重
function fn(arr){
arr = arr.sort((a,b)=>{return a-b}); //升序
for(let i = 0;i < arr.length-1;i++){ //倒数第一项不用去跟后面一项进行对比
if(arr[i] === arr[i+1]){
arr.split(i,1)
arr.length--;
i--;
}
}
return arr;
}
- 变量提升,函数提升。变量提升:变量名提升;函数提升:函数整体提升
- this指向问题
- 变量常用规则,变量沿着作用域链进行查找
- 运算符优先级关系:点运算优先级最高,但是遇到
()
没有办法进行运算,所以会记那个前面整体先进性运算 - 实例对象属性规则,原型原型链
//涉及变量提升、函数提升;
function Foo(){
getName = function(){alert(1)} //修改了全局的
return this;
}
Foo.getName = function(){alert(2)}
Foo.prototype.getName = function(){alert(3)}
var getName = function(){alert(4)}
function getName(){alert(5)}
//输出如下结果
Foo.getName() //2
getName() //4
Foo().getName() //1 this指向window,window.getName()
getName() //1
new Foo.getName(); //2
new Foo().getName(); //3
new new Foo().getName(); //3
-
libuv
-
事件轮询机制
- 定时器阶段:计时到点的计时器
- pending callback:做系统操作,tcp错误等
- idle、prepare准备工作
- 轮询阶段:轮询队列
- 轮询队列不为空:一次去除回调函数中的第一个函数,先进先出。知道轮询队列为空或者达到最大限制(轮询队列为空:设置过SetImmidiate函数直接进入下一个check阶段;没有设置过,那么就在当前poll等待,直到轮询队列添加进来新的函数,就会去第一个情况执行。如果定时器到点,那么也会去执行下一个阶段)
- check查阶段:执行setImmediate设置的回调函数
- close callback:关闭阶段。close事件的回调函数在这个阶段进行。
-
process.nextTick
能在任意阶段优先执行
setTimeout(function(){
console.log(1)
},0)
setImmediate(function(){
console.log(2)
})
process.nextTick(function(){
console.log(3)
})
//nextTick timeout immediate
-
DNS解析。域名解析成IP。先读取浏览器DNS缓存,读取系统DNS缓存,读取路由器DNS缓存,网络运营商缓存,递归搜索
-
TCP三次握手:
- 浏览器告诉服务器要发送请求
- 服务器告诉浏览器准备接收
- 浏览器告诉服务器马上就发送请求
-
发送请求:请求报文
-
接受响应:响应报文
-
渲染页面
- 遇见HTML标记,浏览器调用HTML解析器解析解析并且构建成dom树
- 遇见style/link,浏览器调用css解析起解析并且构建成cssom树
- 遇到script会调用js解析器,处理script代码(绑定事件,修改dom树/cssom树)
- 合并dom树和cssom树成渲染树
- 根据渲染树进行计算布局,计算每个节点的几何信息(布局)
- 将各个节点颜色绘制到屏幕上(渲染 )
- 注意:这五个不一定按照顺序执行,如果dom树或者cssom树被修改了,可能会执行多次布局和渲染。
-
断开链接:四次挥手。
- 浏览器发送给服务器:我请求报文完了,关闭
- 服务器告诉浏览器:我准备关闭
- 服务器告诉浏览器:响应报文发送完了,准备关闭
- 浏览器告诉服务器:客户端准备关闭
-
函数嵌套
-
内部函数使用外部函数的局部变量
-
优点:延长外部函数局部变量的生命周期;缺点:内存泄漏
-
合理使用闭包,及时销毁
function fun(n,o){
console.log(o) //执行var a = fun(0)时候o没有定义,undefined
return {
fun:function(m){
return fun(m, n); //这个fun是window的fun也就是全局的fun
}
}
}
//a中存的是同一个返回方法,是同一个fun(m,n),此时存的n都是第一次的0,后面也只是改m所以n都是0,o打印都为0
var a = fun(0) //结果给a赋值返回一个对象{fun:function(){...}}
a.fun(1) //0
a.fun(2) //0
a.fun(3) //0
//注意这里区别于上面,这里每次执行完之后保存的n都是上一次返回的n
var b = fun(0).fun(1).fun(2).fun(3) //undefined 0 1 2
//同理:
var d = fun(0).fun(1).fun(2).fun(3).fun(67).fun(45) //undefined 0 1 2 3 67
var c = fun(0).fun(1) //undefined 0 此后c上的n保存变成了1
c.fun(2) //1
c.fun(3) //1
- js引擎在代码执行之前会做预处理工作
- 收集变量:var声明的变量定义,但是不赋值
- 收集函数:function声明的函数就提前定义函数
console.log(username) //undefined
var username = 'smileyqp'
fun(); //正常执行函数
function fun(){
console.log('inner func')
}
- 理解:代码执行的环境
- 时机:代码正式执行之前会进入到执行环境
- 创建变量对象(变量提升)
- 变量
- 函数及函数的参数
- 全局:window
- 局部变量对象
- 确认this指向
- 全局window
- 局部:调用其的对象
- 创建作用域链
- 父级作用域链+当前的变量对象
- 扩展:执行上下文可以认为是一个大的对象,这个对象中主要包含
- 变量对象:变量、函数、函数的形参
- 作用域链Scopechain:父级作用链+当前作用域对象
- this:window||调用其的对象
- 创建变量对象(变量提升)
js是单线程的,所以我们可以称之为主线程,也就是我们的js代码都是在主线程上完成的。只不过我们区分任务是同步还是异步,如果是异步的话,那么它会有一个回调函数。 回调函数:定时器的回调、ajax请求的回调、对应事件的回调(比如点击事件)。不同的任务又会交给不同的模块去处理,比如:定时器模块、网络请求模块、事件处理模块。
js事件轮询机制,js是单线程的
- 宏任务:setTimeout 、setInterval、requestAnimationFrame
- 宏任务所处的队列就是宏任务队列
- 第一个宏任务队列中只有一个任务、执主线程的js代码
- 宏任务队列可以哟欧多个
- 微任务:New Promise().then(两个回调函数 )、process.nextTick
- 微任务所处的队列就是微任务队列
- 只有一个微任务队列
- 上一个宏任务执行完成之后如果有微任务就会执行微任务队列中所有任务
- 当宏任务重的任务执行完成之后会此案去查看微任务队列中是否有任务,如果有就先执行微任务队列中的任务,如果没有的话就继续执行下一个宏任务
//主线程宏任务执行打印1-5 微任务队列执行打印then中console setTimeout宏任务执行
console.log('start')
setTimeout(()=>{
console.log('setTimeout')
},0)
new Promise((resolve,reject)=>{
for(let i = 0;i<=5;i++){
console.log(i)
resolve()
}
}).then(()=>{
console.log('promise实例成功回调')
})
console.log('end')
//输出:
// start 1 2 3 4 5 end promise实例成功回调 setTimeout
- 都是组件化开发
- 虚拟dom
- 都支持通过属性的方式去进行父子组件之间通信
- 数据驱动:更新状态数据,界面会自动更新,不用直接操作dom
- 支持服务器渲染
- 支持原生应用方案:react native(react)、weex(vue)
-
数据绑定:vue中是双向数据绑定,也就是界面和内存的数据可以双向交流;而react只支持从内存到界面,不支持从界面到内存
-
组件书写方式不一样:react采用JSX(JSX是js的扩展语法,react提出all in js)而vue使用的是模版(也就是在html中去写js、css再通过webpack的loader去对他们进行打包)
-
状态变化:react中调用
setState
;vue中直接通过this.xxx
去更新data中的数据 -
虚拟dom的底层实现不是完全一样:vue会去跟踪每一个组件的依赖关系、不需要重新渲染整个组件树;而对于react而言,一旦状态改变,全部组件都会重新去渲染所以react中需要
shouldComponentUpdate
这个生命周期方法去进行控制 -
react严格上来说是MVC的view层,而view则是mvvm模式的
- redux是一个独立的状态管理js库,不是react的插件库
- 可以用在react、vue、angular等项目中,但是大多情况与react配合使用
- 作用:集中管理react组件中共享数据的状态
- react-redux简化编码
- redux-thunk实现redux的异步编程。以及redux-saga
- redux-devtools实现chrome中redux的调试
- 父向子通信
- 子向父通信
- 兄弟组件通信
- 隔代组件通信
- props
- 标一般属性(父向子)、函数属性(子向父)
- 隔代通信比比较麻烦:需要逐级传递;兄弟组件通信需要通过父组件传递
- Vue自定义事件:
- 绑定监听(子组件在父组件中调用的时候绑定监听)
<My-component @eveName="callback"/>
- 触发(分发)事件
this.$emit('eveName',data )
- 只适合子组件向父组件通信
- 绑定监听(子组件在父组件中调用的时候绑定监听)
- 消息订阅与发布(例如:pubsub-js )
- 订阅消息
- 发布消息
- 适用于任意关系的组件通信
- Vuex(vue的状态管理的插件库)
- slot:专门用来父向子传递带数据的标签
- 模板解析
- 解析大括号表达式
- 解析指令
- 数据绑定
- 更新显示实现:数据劫持
- 先把dom节点生成dom树(解析dom)
- 生成cssom(解析css)
- 布局:构成渲染树render tree(从根节点递归,布局)
- 绘制:绘制div等到页面上
重绘(repaint):一个元素外观的改变所触发的浏览器的行为,浏览器会根据元素的新属性重新绘制,使元素呈现新外观
回流(重排、重构reflow):当渲染树的一部分因为元素的尺寸、布局、隐藏等改变而需要重新构建称之为重排
重绘和重排的关系:在重排的过程中,浏览器会使渲染树中收到影响的部分失效,并且重新去构造这部分渲染树,完成重排之后,浏览器会重新绘制受影响的部分到屏幕中,该部分称为重绘。
重排(回流)一定会重绘,但是重绘不一定会重排。(重排=>布局 重绘=>样式)
- 页面渲染初始化
- 添加删除可见dom元素
- 元素位置改变或者使用动画
- 元素尺寸的改变——大小、外边距、边框
- 浏览器窗口尺寸的变化(resize事件发生时)
- 填充内容的改变,比如文本的改变导致的计算值的宽高改变
- 读取某些元素属性(读取宽高offsetTop/Top/Height/width等)
- 浏览器自己的优化:浏览器会维护一个队列,把所有会引起重绘或者重排的操作放入这个队列,等队列中的操作到了一定的时间间隔或者一定数量的时候,浏览器就会flush队列进行一个个处理,这样就会让多次回流和重绘变成一次
- 我们也可以合并多次对dom的操作以及对样式的修改为一次,并且减少对style的样式请求
- 直接改变元素的className
- 先设置元素的
display:none
然后进行页面布局等操作,设置完成之后再将display:block
这样的话就之后出现两次重绘和重排 - 使用cloneNode(ture or false)和replace技术只引发一次重绘和重排
- 将需要多次重排的元素的
position
设置成absolte或者fixed,使其脱离文档流,那么它的变化不会影响到其他元素 - 如果要插入多个dom节点可以创建一个documentFragment创建完成之后一次性加入document
var fragment = document.createDocumentFragment()
for(let i = 0;i<=1000;i++){
var li = document.createElement('i')
li.innerHTML = i + '</br>'
fragment.append(li)
}
document.getElementById('container').appendChild(fragment)
//class就是构造函数的一种新写法,可以视作语法糖
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
say(){
console.log(`${this.name}${this.age}岁`)
}
}
let p = new Person('smileyqp',18);
p.say()
console.log(p.name)
class Teacher extends Person{
constructor(){
super()
}
hello(){
console.log('say hi')
}
}
- 异步情况:定时器、ajax、事件处理、nodejs、读取文件也有异步
let p = new Promise((resolve,reject)=>{
resolve(1);
})
//这里的function就是promise中调用的resolve。那么上面掉哟哦那个resolve(1),data参数就是1
p.then(function(data){
console.log('success',data) //success 1
})
-
Promise减少嵌套关系,是一个链式编程的结果
-
axios就是把ajax用promise封装了一下
-
async、await最简单的使用就是省去then的步骤,改成类似同步的过程,等待成功才会执行下面。方便、可读性强、异步改成类似同步写法
-
async和await其实是generator和yield的语法糖。async、await使得代码清晰。
-
Generator里面的代码是分段执行。看到yield就分段暂停。
function *helloGenerator(){
yield 'hello';
yield 'world';
yield 'ending';
}
var hw = helloGenerator();
console.log(hw); //这个函数的结果并不是摁钉,因为代码是暂停的。是一个暂停标记,标记指向'hello'等结果
console.log(hw.next()) //hello
console.log(hw.next().next()) //world
console.log(hw.next().next().next()) //ending
console.log(hw.next().next().next().next()) //undefined
- 拓展
Promise.all
和Promise.race
Promise.all
必须数组里面的所有promise执行完毕,才成功。用在要很多结果都执行成功的情况。Promise.race
只要数组里面的一个成功,整个race就成功执行
Promise.all([p1,p2,p3...],function)
Promise.race([p1,p2,p3...],function)
- ts是js的一个超集,支持ES6标准
- ts比js有更严格的类型要求。有类型的约束,减少大型项目中的bug
- 示例:
msg!:string
msg是非空字符串;msg?:string
msg有可能有可能没有
- 装饰器:是一种与类相关的语法,用来注释和修改类和类相关的方法和属性。许多面向对象的语言都有这个功能。一般和类class 相关,普通的方法不要去使用
- 装饰器是一种函数,写法是
@函数名
,它可以放在类和类的方法定义前。装饰器就是执行函数,给类或者类下面的属性方法加一些控制条件 - 装饰器
- 给类或者类属性加上一些其他代码
- 可以代码复用
@decorator
class A(){
}
//等同于
class A(){
//decorator是一个函数,相当于调用它给A类可以加上一些其他代码,加上属性等
A = decorator(A)
}
//实例.在类或者类属性方法上写上@函数名,就相当于调用这个函数
function testfunc(target){
target.isok = true; //相当于Myclass.isok = true
console.log('i am test func',target)
}
@testfunc
class Myclass{
}
function readonly(target,name,descriptor){
console.log(target) //当前的class,即Person
console.log(name) //装饰的属性或方法名
//configuerable修改、enumerable枚举是否可for in循环、writeable、value对象属性默认值
console.log(descriptor)
return descriptor;
}
class Person{
@readonly
abc(){
console.log('add func ')
}
}
异步:定时器、ajax、onclick、promise(new Promise立刻执行,then异步)
- 宏任务:定时器(setTimeout、setInterval)、requestAnimationFrame、I/O
- 微任务:process.nextTick、Promise、Object.observe、MutationObserver
- 宏任务队列和微任务队列,执行主线程任务,宏任务放到宏任务队列,微任务放到微任务队列。只有在微任务队列中的微任务全部执行完成之后才会去执行下一个宏任务。注意:执行宏任务的时候,也会产生微任务,继续执行上面过程
console.log('1')
setTimeout(function(){
console.log('2')
new Promise(function(resolve){
console.log('3')
resolve()
}).then(function(){
console.log('4')
})
},0)
new Promise(function(resolve){
console.log('5')
resolve()
}).then(function(){
console.log('6')
})
setTimeout(function(){
console.log('7')
new Promise(function(resolve){
console.log('8')
resolve()
}).then(function(){
console.log('9')
})
console.log('10')
},0)
console.log('11')
//1 5 11 6 2 3 4 7 8 10 9
-
浏览器本身有缓存,浏览器可能会把上次代码缓存起来,再去访问不是去拿新代码,而是直接使用缓存
-
浏览器的缓存分成两种,强制缓存和协商缓存
- 强缓存:不会向服务器发送请求,直接从缓存中读取资源,每次访问本地资源直接验证看是否过期。强缓存可以通过设置两种http header实现:expire过期时间和cache-control缓存控制。
- 协商缓存(Last-Modified/If-Modefied-Since和E-tag/If-None-Match):发请求到服务器,服务器会告诉浏览器去拿缓存还是新的代码
拓展:
- 网站优化
- 雪碧图
- 懒加载
- 减少http请求(缓存:浏览器有缓存,现在h5的manifest也可以进行自定义缓存,优化网站)
Html5提出的一个新特性:离线存储。通过离线存储,我们可以把需要离线存储在本地的文件夹列举在manifest配置文件中,这样即使在离线的情况下,用户也可以正常的看见网页
- 在需要离线缓存的页面的html标签上加上
manifest='cache.manifest'
<html lang='en' manifest='cache.manifest'>
...
</html>
- 在根目录创建新文件名称为
cache.manifest
的文件,并写上代码
混合开发
- 一部分原生一部分js
- 内嵌浏览器壳
- web手机端网页:手机操作比较困难,一般没有手机操作权限
- weex:采用vue框架,可打包成app
- react-native:采用react框架
- react语法加上自己特定的标签
<Text></Text>
<View></View>
等
- react语法加上自己特定的标签
- uniapp:采用vue框架
补充:如何将vue项目打包成app?1、vue项目build 2、将build文件夹下面文件复制到新的hbuild创建的心项目中,覆盖原文件 3、直接打包
当web浏览器输入www.baidu.com
具体发生了什么?
- DNS域名解析,得到对应的IP
- 根据这个IP,找到对应的服务器,发起TCP三次握手
- 建立TCP链接之后发起HTTP请求
- 服务器响应HTTP请求,浏览器获取html代码
- 浏览器解析html代码,并且请求html代码中的资源(css、js、图片、视频等。得到html后才能去湖区这些资源)
- 浏览器对页面进行渲染并且呈现给用户
- 服务器关闭TCP链接(四次挥手)
-
怎样进行域名解析,DNS怎样找到域名的?
- DNS域名解析是采用递归查询的方式,过程是:先去找DNS缓存=〉缓存找不到就去找根域名服务器=〉根域名又会去找下一级,这样递归查找之后,找到了就给我们的浏览器
- 浏览器自身DNS缓存
- 操作系统DNS缓存
- 路由器DNS缓存(host文件中查找)
- 递归去各个域名服务器查找
- DNS域名解析是采用递归查询的方式,过程是:先去找DNS缓存=〉缓存找不到就去找根域名服务器=〉根域名又会去找下一级,这样递归查找之后,找到了就给我们的浏览器
-
为什么HTTP要基于TCP来实现?
- TCP是一个端到端的可靠的面相连接的协议,HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误的时候会重传)
-
浏览器对页面是如何进行渲染的?
- 解析html获得dom树
- 解析css生成cssom树
- dom树和cssom合成渲染树
- 边解析边渲染(布局:计算位置和尺寸;渲染:渲染样式)
- JS单线程运行的,js困难修改dom的结构
拓展:重排(回流):修改布局;重绘:修改样式。重排一定会重绘,重绘不一定重排。
- 缓存作用范围
- 第一次响应后到第二次请求
- 缓存分类
- 缓存命中率:从缓存中获取数据的请求与所有请求的比率。理想状态是越高越好
- 过期内容:超过设置的过期时间,被标记为陈旧的内容,必须重新向服务器请求新的内容或者验证缓存的内容是否仍然准备
- 验证:验证缓存中的内容是否仍然有效,验证通过的话就刷新过期时间
- 失效:把内容从缓存中清除,内容发生改变时就必须清除失效内容
- http缓存实现技术
- 强缓存:不用发请求到服务器就能拿到缓存(减少服务器压力)
- expire和cache-control两个请求头设置。两个字端都有以
cache-control
为主,因为expire绝对时间,服务器和浏览器可能有差异 - expire:定义过期时间,绝对时间,由服务器发给浏览器的绝对时间
- cache-control:定义过期时间,相对时间,一个时间段
- expire和cache-control两个请求头设置。两个字端都有以
- 协商缓存:请求到服务器,询问服务器缓存是否过期,没有就直接从缓存加载资源。返回304状态码。
- Last-Modified/If-Modefied-Since
- E-tag/If-None-Match
-
一台服务器,提高并发?可以使用浏览器缓存,直接使用浏览器缓存减少请求,提高并发。
- http缓存,能够帮助服务器提高并发,资源不需要重复请求,能够直接从浏览器中获取
- http缓存分类分为强缓存和协商缓存。
- 强缓存通过cache和cache-control来进行控制。协商缓存通过last-modefied以及etag来进行控制
-
为什么有expire需要cache-control ?
- 因为expire是绝对时间,有可能存在浏览器和服务器的时间不同步的问题
- cache-control 是相对时间,一定程度上弥补了expire导致的时间不同步问题
-
last-modify和etag
- last-modify存在精度问题,到秒
- e-tag没有精度问题,只要文件改变,e-tag值就会改变
- Promise概念
- 解决ajax回调地狱问题,使得代码更加简洁易懂
- 说Promise题目输出
const first = () =>(new Promise((resolve,reject)=>{
console.log(3)
let p = new Promise((resolve,reject)=>{
console.log(7)
setTimeout(()=>{
console.log(5)
resolve(6) //一个promise值能执行一个resolve;这个在定时器中,比下面后执行,因此是执行下面的
},0)
resolve(1)
})
resolve(2)
p.then((arg)=>{
console.log(arg)
})
}))
first().then((arg)=>{
console.log(arg)
})
console.log(4)
//3 7 4 1 2 5
- 如何实现链式调用
//每次then的时候返回一个promise
b.then().then().then()的链式调用如何实现的?
//方法一:返回this
class Test{
then(){
console.log('then')
return this;
}
}
var t = new Test();
t.then().then().then()
//方法二:返回改对象实例
class Test1{
then(){
console.log('then')
return new Test1();
}
}
var t1 = new Test1();
t1.then().then().then()
-
手写实现简易Promise
-
分析promise特点
- Promise参数函数会立即执行
- promise在then的回调函数中可以拿到resolve参数
- promise可以有多个then并且可以依次执行
- promise可以嵌套多个then,then回调中可以返回promise
- promise可以嵌套多个then,then的回调中可以返回普通值
- resolved状态的promise,调用then方法会立即执行的
- 二次调用resolve不会产生影响
-
实现思路
- promise是一个对象,一般是通过
new Promise
来实例化的 - promise的then是可以链式调用的,所以需要有链式调用的实现
- 逐个根据列出的promise的特点来实现
- 主要实现promise的构造方法和then方法
- promise是一个对象,一般是通过
-
//不完整,之后补充
let State = {
pending:'pending',
resolve:'resolved',
reject:'rejected'
}
const noop = () => {}
class MyPromise{
constructor(exclutor){ //回调函数,promise传入的回调函数exclutor
exclutor(this._resolve.bind(this),_this.reject.bind(this)); //回调函数立即执行
}
_state = State.pending;
_value;
_resolve(val){
this._value = val;
this._state = State.resolved;
//执行then传入进来的onRes;执行then传入进来的回调函数
while(_resArray.length>0){
const item = _resArray.shift();
item(this._value);
}
};
_reject(){
this.state = State.rejected;
};
_resArray = [];
then(onRes,onRej = noop){
const newPromise = new MyPromise(()=>{})
this._resArray.push(onRes); //存储then中回调方法
return newPromise;
}
}
export default MyPromise;
-
redux是一个独立的数据状态管理库,在angular、vue也都可以使用redux,只不过常与react一起使用
-
redux解决数据状态管理,跨层级问题。
-
redux就是一个经典的发布订阅器。事件绑定的过程其实也是一个发布订阅的过程。
-
redux使用方法
- 调用ctreateStore创建store对象
- 用Provide包裹根组件
- 使用connect获取链接
-
提供两个api
Provider
传递store到每个组件中去。Provider实际上是一个组件。getChildContext创建store。Provider就是通过React的context API把数据往下传。
-
connect
通过store传递进来的provider,将state绑定到当前组件。connect有两个参数mapstatetoProps订阅更新,mapdispatchtoprops 调用dispatch改变当前数据
拓展:
函数柯里化
:函数返回函数;高阶组件
:组件返回组件
-
- 明确react与redux的联系是react-redux这个库(Provider、connect)
- redux的原理其实就是一个发布订阅器,帮我们用一个变量存储所有state,并且提供了发布来修改数据,提供了订阅功能来触发回调
- react-redux的功能是订阅store中的数据更新,它包含两个重要的元素Provider和connect
- Provider的作用是通过context api把store对象注入到react组件中去
- connect方法就是一个高阶组件,在高阶组件中通过订阅store的更新,调用setState方法来触发组件更新
-
-
redux痛点是什么?
- 增加代码的复杂性。需要经过dispatch、调用reducer、触发回调、更新数据。redux在使用中最大的弊端就是样板代码(action、reducer)太多,修改数据链路较长
-
为什么还是要使用redux?
- redux可以解决跨组件间数据传递问题并且修改数据十分清晰。在复杂的大型项目中,状态数据较多,redux的映入可以较好对数据进行管理,使得数据流向组件状态变更更为清晰
-
redux在使用时候,有哪些比较好的实践方式呢?(可以使用一些手段减少模板代码从而简化redux api)
- 使用redux-action,在初始化reducer和action构造器时候减少样板代码
- 减少创建action时候创建的一堆固定的写法
- 减少创建reducer时候写的一堆固定的switch(封装)
- 使用cli工具,帮我们生成模板代码。比如yeoman工具
总结:redux最大的弊端是样板代码太多,修改数据链路太长
解决方式:可以借助一些工具来减少创建样板代码的过程
- 使用redux-action减少书写固定不变的代码使得我们代码更加清晰
- 使用cli工具自动生成模板文件和代码
- 使用redux-action,在初始化reducer和action构造器时候减少样板代码
-
redux的异步问题怎样处理(为什么redux处理不了异步问题?)
- dispatch默认只能接受一个object类型的action,因为reducer中要接收action.type来处理不同的数据
- 那怎样解决redux不能处理异步的问题呢?
- redux异步问题可以用中间件来解决
- redux-saga:让异步成为中架构中独立的一层
- redux-thunk
- redux异步问题可以用中间件来解决
-
redux总结:
- redux会增加代码的复杂性,使用前需要考虑当前项目的规模和要求
- 可以使用redux-action或者cli工具来帮我们减少模板代码的书写
- redux无法处理异步action是因为dispatch只能接收一个object类型的action,但是可以通过中间件的方式来解决异步问题
-
- React Hooks是一个新的API,可以用函数来写所有组件
- 可以让函数也拥有自己的状态管理(包括state和生命周期)
- 可以通过创建自定义的hooks来抽离可复用的业务组件
-
React组件类型
- 函数组件
- 一个函数就是一个组件
- 一个函数必须有return
- return的是一个react元素
- 类组件
- 一个Class声明就是一个类组件
- 所有的类组件都是继承于React.Component
- React.Component类有自带属性和方法,比如render、componentDidMount等等
- 函数组件
- React Hooks的作用以及有哪些特性
- react hooks是v16.8版本才引入的全新API,它算是一个颠覆性的变革
- 所有的React组件都可以是一个函数组件,再也不需要写类组件了
- 再也不需要记住react有哪些生命周期了
创建使用自定义hooks
-
-
解答
- 对于首页加载过慢的问题,一般是由于首页加载资源过多并且资源过大导致,所以应对的策略一般是减少资源的数量以及减少资源的大小
- 对于图片可以进行懒加载,减少首屏图片加载量,以及对于小图标和小图片分别可以使用Iconfont和雪碧图来解决,最大程度的减少首屏图片渲染量,提高首屏加载速度
- 对于其他资源可以通过打包(nginx combo资源合并或者webpack打包)来合并资源,或者通过路由懒加载的方式来减少首页js加载的数量
- 同时可以在服务器配置nginx开启gzip打包来最大化压缩静态资源体积,达到优化首页加载的目的
-
问题分析(资源多、大)
-
-
- 通过懒加载的方式处理非首屏图片
- 对于小图标可以使用Iconfont的方式来解决
- 对于小图片可以使用雪碧图的方式来解决
-
Q&A
- Q:首页加载图片过多怎样处理
- 懒加载:监听滚动条事件,如果滚动条的高度距离浏览器顶部的高度等于或者接近于图片到浏览器顶部的高度,那么就将data-src的属性赋值到src上
- Q:首页设置的小图标很多,比如有很多的小icon怎么办
- 对于纯色小图标可以用Iconfont来解决(减少资源请求)
- 设置font-famliy的css属性
- 对于一些彩色的小图标可以使用雪碧图
- 把所有小图标拼接到一张大图片上(减少资源请求)
- 并使用background-position的css属性来修改图片坐标
- 对于纯色小图标可以用Iconfont来解决(减少资源请求)
- Q:首页加载图片过多怎样处理
-
-
-
- 通过nginx服务器来做静态资源的合并或者通过webpack等打包工具进行物理的打包
- 在代码层面,对于u 一些需要引入大型的第三方库进行按需加载,比如可以按照babel来进行
- 还可以通过react lazy等动态导入方案进行前端路由层面的动态加载,从而减少首页的js和css加载的大小
-
可以通过减少资源请求量
- 通过nginx服务器(可用来做CDN,处理静态资源)用来做文件合并combo—将多个js、css合并成一个。逻辑上打包,通过拼接请求链接,将多个资源链接合并到一起
- 通过打包工具(webpack)来做资源文件的物理打包。没有第一种灵活。
-
Q&A
-
Q:只有合并资源的方式才能减少资源请求吗?
-
Q:为什么React lazy可以进行路由懒加载?
- 首先react lazy是使用了动态加载(dynamic import)的一个标准,webpack只要遇到了动态加载就会把import的内容单独打一个包
- 由于动态加载返回的是一个promise,所以可以利用promise的流程来做渲染流程的控制
- 如果当前promise是pending状态,那么就渲染loading组件,如果是resolve状态那么就渲染动态导入的组件
动态导入:代码执行到import这一行的时候才开始去下载组件。并且webpack会将其单独打包成一个文件
lazy懒加载
- 结论:
- import('xxx')返回的是一个promise
- webpack只要遇到了import('xxx')就会把括号里引入的内容单独打一个包
-
-
-
- 分析
- 要分资源文件、js、css等分开处理
- css和js可以通过webpack进行混淆和压缩
- 混淆:将js代码进行字符串加密(最大程度减少代码,比如将变量名称程度单个字母等)
- 压缩:去除注释空行以及console.log等调试代码
- 图片的压缩
- 自动化工具来压缩图片
- 图片进行转码,转成base64格式
- 使用webP格式
- 通过开启gzip进行全部资源的压缩
- gzip是一种压缩文件资源的格式,可以对任何文件进行压缩(类比于文件压缩),通过nginx服务器配置开启
- 分析
-
-
-
-
解答:图片优化业主要是从两个方面来进行,太多和太大
- 通过懒加载减少加载图片请求,或者通过雪碧图来合并图片,以及将小图转化成base64格式减少图片请求
- 图片过大问题可以通过图片自动化压缩工具或者使用webp格式的图片
-
问题分析:
- 减少图片加载请求
- 减少图片大小
-
Q&A
-
Q:用什么自动化工具对图片进行压缩?
-
Q:还有什么其他方式吗
- 将图片转码为base64,会增大图片体积,因此不建议把大图片转成base64格式,但是建议把小图片转成base64格式,因为它直接写在代码中,可以减少一个图片的请求
- 使用webp格式
-
-
-
实现webpack打包优化
-
解答:多&大
- 可以设置mode=production来默认实现webpack对代码的混淆和压缩,从而最大程度减少代码体积
- 使用webpack+dynamic import(动态加载)并结合路由的入口文件动态加载做拆包处理
- 并且可以设置一定的打包策略(分包压缩,node_modules、常改动、不常改动公共组件),配合网络缓存(cache-control等)进行加载性能优化
-
问题分析
- 少:使用webpack进行物理打包
- 小:使用webpack进行混淆和压缩,所有与webpack相关的配置都在optimization这个配置项进行管理
-
Q&A
-
Q:打包怎样小怎样少?
- A:使用webpack对代码进行混淆和压缩,并且可以使用react lazy进行拆包,结合路由进行按需加载
-
Q:对文件进行拆包处理,那么文件肯定会增多,会不会跟减少资源请求数量矛盾呢?
- A:并不矛盾,因为我们按需加载之后,拆包的文件不可能同时加载,所以不会造成同一时间请求过多的问题

-
-
打包策略
-
实现CDN加速
-
解答:
- CDN服务器主要是用来做静态资源的服务器,可以用来加速静态资源的下载
- CDN之所以能够加速是因为在很多地方都部署了CDN服务器,如果用户需要下载静态资源,会自动选择最近的资源节点进行下载
- 同时由于CDN的服务器地址一般都跟主服务器不同,所以可以破除http1.0中对同一个域名同时发送的请求的限制问题
-
问题分析
-
-
思路:
- 导致卡顿的远影一般都是dom操作太多太频繁
- 如果想要渲染很多数据又不造成浏览器卡顿,那么肯定是要减少dom的操作。比如react创建虚拟dom,本质上是用js来模拟真实的dom,从而减少dom的操作
- 还有在插入多个dom节点时候,可以使用
document.createDocumentFragment
先创建虚拟节点,再一次性插入 - 也可以采取分段式渲染的方式
requestAnimation
来进行逐帧渲染
-
渲染十万条数据不造成卡顿?
- 结论:
- 可以使用
document.createDocumentFragment
创建虚拟节点来避免不必要的渲染 - 当所有的li都创建完成之后,再一次吧虚拟节点中的li全部渲染出来
- 可以采用分段渲染的方式,比如一次只渲染一屏的数据
- 最后使用
- 可以使用
- 结论:
普通方式:
优化渲染(分段渲染 )
补充:
requestAnimation
逐帧渲染。1000/60=16,也就是16ms渲染一次。requestAnimation要保证浏览器是60帧,所以默认是16ms一次渲染。
拓展:服务器类别
- 应用服务器:弹性计算,存放运行后端代码等
- 存储服务器:存储文件
- CDN服务器:处理静态资源,做资源文件的合并。做静态资源分发
- 数据库服务器
- M:model代表数据模型,主要任务是操作数据。将新的数据发送到view改变视图
- V:view代表的是视图,主要任务是将数据模型转化成UI视图。传送指令到controller
- C:controller控制器,主要任务是负责处理业务逻辑。完成业务逻辑之后要求model改变状态
解决性能问题,可降低函数调用频率。事件频繁触发的时候,比如window.resize
以及window.onmousemove
等等,触发频率十分高,会造成浏览器性能问题。以及输入框搜索的时候会实时去后台请求数据,对服务器造成不必要的压力。
function throttle(fn,delay=500){
let pre = 0;
return function(event){
const cur = Date.now();
if(cur - pre > delay){ //只有与上次调用的时间差大于delay
fn.call(this,event)
pre = cur;
}
}
}
function debounce(fn,delay = 500){
let timer = null;
return function(e){
if(timer){clear(timer)}
timer = setTimeout(()=>{ //这里需要用箭头函数;因为需要改变this指向
fn.call(this,e)
},delay)
timer = null;
}
}
补充:高阶函数:1、参数是函数 2、返回值是函数。满足其中之一的就是高阶函数。
function addTen(arr){
return arr.map((item,index)=>{
return item+10;
})
}
function getAdd(arr){
arr.reduce((pretotal,item,index)=>{
return item%2 === 1 ? pretotal+item:pretotal;
},0)
}
function filterArr(arr){
return arr.filter((item,index)=>{
return item>8&&item%2===0;
})
}
function getArr(arr){
return arr.find((item,index)=>{
return item>8&&index%2===0;
})
}
function getArrindex(arr){
return arr.findIndex((item,index)=>{
return item>8&&index%2===0;
})
}
funcrion getConfirm(arr){
return arr.every((item,index)=>{
//下标为奇数或者下标为偶数值都是奇数的元素
return index%2 === 1 || (index%2 === 0 && item % 2 === 1)
})
}
funcrion getConfirm(arr){
return arr.some((item,index)=>{
//下标为奇数或者下标为偶数值都是奇数的元素
return index%2 === 1 || (index%2 === 0 && item % 2 === 1)
})
}
- map
Array.prototype.map = function(callback){
const arr = [];
//遍历当前数组,调用callback得到一个结果数据,添加到arr
for(let i = 0;i < this.length;i++){ //this就是原数组
const element = this[i];
arr.push(callback(element,i))
}
return arr;
}
- reduce
Array.prototype.redece = function(callback,initialVal){
let result = initialVal;
for(let i = 0;i < this.length;i++){
//recude三个参数,上一次的累加结果,value,index;上一次累加结果由callback决定
result = callback(result,this[i],i)
}
return result;
}
- filter
//调用callback得到一个boolean值,如果为true那么就将值push到结果中去
Array.prototype.filter = function(callback){
const result = [];
for(let i = 0;i < this.length;i++){
if(callback(this[i],i)){
result.push(this[i]);
}
}
return result;
}
- find
//遍历当前数组,调用callback,返回boolean值,如果为true那么就返回这个值
Array.prototype.find = function(callback){
for(let i = 0;i < this.length;i++){
const element = callback(this[i],i)
const result = this[i];
if(element){
return result;
}
}
return undefined; //没有匹配的返回undefined
}
- findIndex
//遍历当前数组,调用callback,返回boolean值,如果为true那么就返回这个值
Array.prototype.findIndex = function(callback){
for(let i = 0;i < this.length;i++){
if(callback(this[i],i)){
return i;
}
}
return -1; //没有匹配的返回undefined
}
- every
//遍历,一点callback返回false,那么返回false,都不为false,返回true
Array.prototype.every = function(){
let result = true;
for(let i = 0;i < this.length;i++){
if(!callback(this[i],i)){
result = false
}
}
return result;
}
- some
//一旦callback为true返回true否则返回false
Array.prototype.some = function(){
let result = false;
for(let i = 0;i < this.length;i++){
if(callback(this[i],i)){
result = true
}
}
return result;
}
数组相关声明式方法都是高阶函数,都没有改变原数组
- 数组静态方法
- from
- of
- isArray
//from 从一个类数组,或者可迭代对象创建一个新的,浅拷贝的数组实例
const set = new Set(['foo','smile','yqp'])
console.log(Array.from(set)) //['foo','smile','yqp']
//of 创建一个可变数量参数的数组实例,所有参数列表作为元素,创建一个新数组
console.log(Array.of(3)) //[3]
console.log(Array.of(3,5,7,8)) //[3,5,7,8]
//isArray 判断是否是数组
console.log(Array.isArray([1,2,3])) //true
console.log(Array.isArray({1,2,3,4})) //false
- 数组更新的方法
- push
- pop
- unshift
- shift
- sort
- reverse
- splice
const arr = [1,2,4,7,9,3]
//push
//1、数组最后添加6
arr.push(6) //[1,2,4,7,9,3,6]
//2、数组最后一次添加8和9
arr.push(8,8) // [1,2,4,7,9,3,8,9]
//2、数组最后添加包含8和9的数组
arr.push([8,9]) // [1,2,4,7,9,3,[8,9]]
//pop 删除数组最后一个元素
arr.pop() //9 pop方法返回的是删除的最后一个元素的值 原数组变成[1,2,4,7,9]
//unshift
//1、在数组最前面添加2
arr.unshift(2) //[2,1,2,4,7,9,3]
//2、在数组最前面添加4和3
arr.unshift(4,3) //[4,3,1,2,4,7,9,3]
//shift 删除数组的第一个元素
arr.shift() //1 返回被删元素值 原数组变成[2,4,7,9,3]
//sort 数组降序排列;sort的回调函数返回值为数值,分为dayu0,等于0,小于0;大于0item2放在左边
arr.sort((item1,item2)=>{return item2 -item1}) //sort改变原数组;返回值也是改变之后排序的数组
//splice
//1、删除第三个元素;两个参数,第一个index第二个是个数
arr.splice(2,1)
//2、删除第将第三个元素替换为10
arr.splice(2,1,10)
//3、将11插入为数组的第三个元素
arr.splice(2,0,11)
- 数组遍历相关的声明式方法
- forEach
- map
- reduce
- filter
- find
- findIndex
- some
- every
- slice返回一个新的数组,包含从 start 到 end。不会修改数组,而是返回一个子数组。
arrayObject.slice(start,end)
- 数组的其他方法
- length
- join
- contact被合并的可以是数组也可以是元素。
arr.concat([1,3],4)
,如果是数组就把数组中的元素取出来 - indexOf,找到元素下标
- lastIndexOf
- 栈的几个功能方法
- 进栈(压栈)push
- 出栈pop
- 产看栈顶peek
- 栈中元素个数size
- 是否空栈isEmpty
function Stack(){
//用于保存元素数据的数组
const arr = [];
this.push = function(element){
arr.push(element)
}
this.pop = function(){
arr.pop()
}
this.peek = function(){
return arr[arr.length-1]
}
this.size = function(){
return arr.length
}
this.isEmpty = function(){
return arr.length === 0 ?true:false
}
}
export default Stack;
- 栈应用(栈十进制转二进制:除2取余,倒序排列,高位补0)。数组实现十进制转二进制。
function edc2bin(decNum){
//创建一个用于保存二进制的array。Array改为Stack就是栈应用
const arr = new Array();
while(decNum>0){
const remainder = decNum % 2; //取余
arr.push(remainder);
decNum = Math.floor(decNum / 2); //向下取整数
}
//依次
let result = ''
while(arr.length !== 0){
result = result + arr.pop()
}
return result;
}
function Queue(){
const arr = [];
this.enqueue = function(element){
arr.push(element)
}
this.dequeue = function(){
arr.shift() //删除数组的第一个元素并且返回
}
this.front = function(){ //查看队列第一个
return arr[0]
}
this.size = function(){
return arr.length
}
this.isEmpty = function(){
return arr.length === 0 ?true:false
}
}
export default Queue;
- 队列击鼓传花(队列头依次放到后面去;并且删除指定的第几个 )
- 创建一个空queue
- 将数组中的所有元素依次放入queue
- 将队列中num-1元素依次转移到队列最后,将队头部元素删除(只要队列的size>1就不断做,直到剩下最后一个,就是目标元素 )
function passGame(names,num){
//创建一个空queue
const queue = new Queue();
//将数组中的所有元素(name和index)依次放入queue
names.forEach((name,index)=>{
console.log(name,index)
queue.enqueue({name,index})
})
while(queue.size() > 1){
for(let i = 0;i < num;i++){
queue.enqueue(queue.dequeue())
}
//删除头部元素
queue.dequeue(queue.front())
}
const {name,index} = queue.front()
return `${name}是最后一个,它的index是第${index}个`
}
var names = ['a','b','c','d','e']
passGame(names,3)
- 优先级队列
- 传入值优先级。标记优先级,优先级高的先出
二叉树BT:一个节点有最多两个子节点
二叉搜索树BST:满足二叉树的基础上,值左边小,右边大
元素唯一不能重复。ES6中集合的实现是用数组实现的。当然集合也可以用Object实现
- webpack:静态模块打包工具。webpack中的一切资源都是文件。
- webpack是通过从入口进行递归查询的方式直接或者间接相互依赖的文件,最后生成打包文件。
- webpack-cli处理打包命令,解析webpack命令。webpack包做文件打包工作
- webpack不用全局下载,只用局部下载即可。因为不同项目可能使用的webpack版本不一样
- webpack本身能解析打包各种模块规范的js代码
yarn init -y
yarn add -D webpack webpack-cli
//创建src/index.js因为webpack打包默认会找src目录下面的index.js文件
//此时执行webpack命令,可以看到webpack将其打了个基础包,在dist目录下
//package.json中配置打包命令,之后使用npm run build进行webpack打包
"scripts": {
"build":"webpack"
},
- 创建
webpack.config.js
文件,写入相关配置 [email protected]
上一次打包的文件清除html-webpack-plugin
解决手动引入webpack-dev-server
解决自动打包不需要手动打包
yarn add -D webpack-dev-server html-webpack-plugin [email protected]
cnpm install @babel/core @babel/plugin-transform-runtime @babel/polyfill @babel/preset-env @babel/runtime autoprefixer babel-eslint babel-loader copy-webpack-plugin css-loader eslint eslint-friendly-formatter eslint-loader file-loader html-webpack-plugin --save-dev
- mode:none、development、production。声明一些模式就是内部为我们预先加上一些配置
- 入口entry:一个或者多个(单页打包多页打包)
- 出口output:指定打包的文件夹
- modules模块加载器,用来指定loader:webpack本身只能打包js,不能处理css、图片等资源。因此就需要模块加载器。将css打包到js,引用地址
- 插件plugins:需要打包js之外做的事情,比如说压缩、拷贝、清除、引入页面等操作。插件名称一般是
xx-webpack-plugin
形式- clear-webpack-plugin清除插件
- html-webpack-plugin
- devtools开发工具,用来制定sourcemap
- devServer开发服务器。比如说配置代理、以及不带#路由(Broswer路由)404问题
- resolve解析别名和路径
- optimization指定优化处理的。由模式确定。生产环境和开发环境指定的优化方式不一样。
- externals外部某个包。配置了某个包,比如说用了lodash,不用npm下载也可以,在这里配置也是可以的
- webpack-dev-server开发服务器的包
- webpack-merge合并包
- cross-env
- css-loader
- style-loader
- postcss-loader
- autoprefixer自动浏览器适配。适配浏览器
- postcss-px2em写的px实际上自动转为rem,适配移动端
- less-loader
- styles-loader
- sass-loader
- file-loader
- url-loader
- image-webpack-loader压缩图片,不影响显示效果
- babel-loader
- @babel/core
- @babel/preset-env
- @babel/preset-react
- @babel/polyfill
- @babel/plugin-transform-runtime
- @babel/runtime
- vue-loader
- eslint-loader语法检查
- MiniCssExtractPlugin.loader单独提取打包css。
- thread-loader多线程打包
- html-webpack-plugin把打包的css或者js自动引入到界面中去
- clean-webpack-plugin删除文件
- mini-css-extract-plugin单独提取打包css
- optimize-css-assets-webpack-plugin压缩css
- copy-webpack-plugin拷贝文件
- terser-webpack-plugin压缩js
- add-asset-html-webpack-plugin
- webpack-bundle-analyzer分析打包文件的
- webpack.ProgressPlugin显示打包进度的
- webpack.HotModuleReplacementPlugin热模块低缓
- webpack.HashedModulesPlugin模块id哈希值
- webpack.DllPlugin多进程打包
- webpack.DllReferencePlugin多进程打包
- new webpack.ProvidePlugin
- 处理
- loader处理:js资源以外的资源、包括jsES6转ES5
- plugin插件处理
- devTool开发工具:
- devServer开发服务器:
babel本身是不能进行ES6编译到ES5的,它是提供了一个平台babel-core
,靠babel-core
去组织,每一个语法都有对应的插件去解析。
@babel/preset-env
预设包,是很多插件包的集合包,这里指的是很多js的ES6到ES5语法转换的插件包的集合包。并且不是插件能解决所有的语法转化问题,因为有些新语法是提供函数方法API,有些新语法是提供操作符。
@babel/polyfill
补丁包,处理一些浏览器不兼容的语法。
@babel/plugin-transform-runtime
处理
file-loader
url-loader
:处理图片主要是用这个,但是它是需要file-loader
做基础。并且不仅图片、音视频、字体文件、打包样式、格式处理都可以使用这个文件
- 基本css
- css预编译器
- less
- sass
- stylus
- postcss
- 抽取/单独打包css
- 压缩css
cnpm install style-loader css css-loader less less-loader node-sass sass-loader stylus stylus-loader --save
- postcss.config.js
module.exports = {
plugins: [
// require('precss'),
require('autoprefixer')()
]
}
- config.webpack.js中postcss配置
//模块加载器
module:{
rules:[
//处理css
{
test: /\.css$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader', //预处理css的,要在css前进行,因此由于loader加载顺序,放在css-loader的下面或者右边
]
},
//处理less
{
test: /\.less$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader',
'less-loader',
]
},
//处理stylus
{
test: /\.(styl|stylus)$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader',
'stylus-loader'
]
}
]
},
- mini-css-extract-plugin
npm install --save-dev mini-css-extract-plugin
webpack中配置
const MiniCssExtracPlugin = require('mini-css-extract-plugin')
//webpack中配置
plugins:[
//从js中抽取css单独打包;一旦抽取了css就不需要cssloader了,需要换成这个插件里面的loader。单独打包css
new MiniCssExtracPlugin({
filename:'css/[name].css'
})
]
//webpack中loader配置。使用MiniCssExtracPlugin.loader代替style-loader即可
{
test: /\.css$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
MiniCssExtracPlugin.loader, //代替style-loader
// 'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader', //预处理css的,要在css前进行,因此由于loader加载顺序,放在css-loader的下面或者右边
]
}
npm install --save-dev optimize-css-assets-webpack-plugin
- webpack配置
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
//优化配置
optimization:{
minimizer:[new OptimizeCssAssetsPlugin()]
}
//webpack中配置
module:{
rules:[
{
enforce:'pre', //前置loader最先执行;pre:最先执行;post:最后执行
test: /\.m?js$/,
include:resolve('src'),
loader:'eslint-loader',
options:{
formatter:'eslint-friendly-formatter' //友好格式化
}
},
]
}
//配置好之后,初始化配置文件,执行下面方法生成配置文件.eslintrc.js
npx eslint --init
//.eslintrc.js中进行相关配置
module.exports = {
"env": {
"browser": true,
"es2021": true,
"node":true
},
// "extends": "eslint:rall", 开启所有的规则检查
"extends": "eslint:recommended", //只开启推荐的规则检查
"globals":{ //定义全局变量;也就是这个变量及时不进行定义,全局也可以使用
"SharedArrayBuffer":"readonly"
},
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"rules": {
"no-unused-vars":"off" //不去检查未定义变量使用的情况
}
};
/**
* webpack配置要向外暴露一个对象,用commonjs的规范进行,因为webpack内部是基于node来进行的处理
*/
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const MiniCssExtracPlugin = require('mini-css-extract-plugin')
const webpack = require('webpack');
const { optimize } = require('webpack');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
function resolve(dir){ //传入一个目录名称,就返回当前目录名称所在的绝对路径
return path.resolve(__dirname,dir)
}
module.exports = {
//模式
mode:'production',
//入口:入口可以是字符串、对象、数组,都是可以的
entry:{
main:resolve('src/index.js')
},
//出口
output:{
path:resolve('dist'),
filename:'bundle.js',
publicPath:'/' //解决图片路径问题;所有生成的URL链接左侧用/开头;即相对路径
},
//模块加载器;按照顺序从下往上执行。enfore可以改变这个顺序
module:{
rules:[
{
enforce:'pre', //前置loader最先执行;pre:最先执行;post:最后执行
test: /\.m?js$/,
include:resolve('src'),
loader:'eslint-loader',
options:{
formatter:'eslint-friendly-formatter' //友好格式化
}
},
//处理ES6到ES5
{
test: /\.m?js$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
{
loader: 'babel-loader', //babel也可以专门用配置文件babel.config.js或者.babelrc进行配置,也可以这两个配置文件都不屑直接在wbpack的这个地方进行配置即可
// options: {
// presets: ['@babel/preset-env'] //这边的配置也可以在babel.config.js中写
// }
}
]
},{
//处理图片
test: /\.(jpe?g|png|webp|gif)$/, //指定对哪些文件进行处理,正则
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
{
loader: 'url-loader', //babel也可以专门用配置文件babel.config.js或者.babelrc进行配置,也可以这两个配置文件都不屑直接在wbpack的这个地方进行配置即可。处理图片、字体、音视频等
options: {
limit: 1024*15, //把小于15kb的进行base64处理
name:'img/[name].[ext]' //文件路径;相对于所有文件下面的;[name]文件名[ext]后缀扩展名
}
}
]
},
//处理css
{
test: /\.css$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
MiniCssExtracPlugin.loader, //代替style-loader
// 'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader', //预处理css的,要在css前进行,因此由于loader加载顺序,放在css-loader的下面或者右边
]
},
//处理less
{
test: /\.less$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
MiniCssExtracPlugin.loader, //代替style-loader
// 'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader',
'less-loader',
]
},
//处理stylus
{
test: /\.(styl|stylus)$/, //指定对哪些文件进行处理,正则
exclude: /(node_modules|bower_components)/, //不包括哪些文件
include:resolve('src'), //包括哪些文件
use:[ //使用;可以使用对象、数组;写对象只有 一个loader,并且loader还有配置以及指定一些额外信息;对象是数组的简写,数组中可以有任意多的loader,但是数组这种方式不可以写配置
MiniCssExtracPlugin.loader,
// 'style-loader', //style-loader将js中的css放到style标签中去 配置多个loader整体的顺序是从下往上,从右往左。所以应该是css-loader放在style-loader的下面
'css-loader', //css-loader将css内容打包到js中去
'postcss-loader',
'stylus-loader'
]
}
]
},
//插件
plugins:[
//向页面中引入打包的js或者css代码
new HtmlWebpackPlugin({
template:'public/index.html' //指定以哪个为模板
}),
//清除打包文件夹
new CleanWebpackPlugin(['dist']),
// new webpack.ProvidePlugin({
// jQuery: "jquery", //配置jquery
// $: "jquery"
// })
//从js中抽取css单独打包;一旦抽取了css就不需要cssloader了,需要换成这个插件里面的loader。单独打包css
new MiniCssExtracPlugin({
filename:'css/[name].css'
}),
],
//开发服务器
devServer:{
open:true //自动打开浏览器访问
},
//优化配置
optimization:{
minimizer:[new OptimizeCssAssetsPlugin()]
}
}
设计模式:针对特定的场景,在软件设计过程中常见的代码范式(通用解决方案,与语言无关)。
- 观察者模式(麦当劳点餐)
- 代理模式(花店送花)
- 单例模式:创建全局唯一的一个实例类。这个类只能创建全局的唯一一个实例。场景:产生唯一的序列号。
- 工厂模式:定义一个创建对象的借口,让其子类决定实例化哪一个对象。
- 装饰器:可以允许向一个对象添加新的功能,却不改变原有对象。比如:
- 给文件上传功能添加日志输出功能
- react中redux的
@connect
- 职责链模式
- 特点:代理对象与真实对象有相同的行为
- 真实对象->代理对象->用户
- 花店送花:男孩让花店送花给女孩
//定义女孩对象
var Girl = function(name){
this.name = name;
}
var Boy = function(girl){
//女同学
this.girl = girl;
//送花
this.sendGift = function(gift){
console.log(this.girl.name+gift) //花店下订单,花店人去送花
}
}
//代理对象 花店员工
var ProxyObj = function(girl){
this.girl = girl; //需要知道女孩信息
this.sendGift = function(gift){
(new Boy(this.girl)).sendGift(gift) //替人送花;直接调用
}
}
//调用 不能看出是谁送花
var girl = new Girl('Vicky')
var proxy = new ProxyObj(girl)
proxy.sendGift("flowers")
- 代理模式的应用:图片懒加载
- 真实图片:大、加载慢;代理图片:小,加载快
//常用图片懒加载
window.onload = function(){
var myImage = (function(){ //自执行函数
var imgNode = document.createElement('img') //创建真实图片节点
document.body.appendChild(imgNode) //图片节点加到body
var img = new Image() //代理对象 先展示代理图片 接着拉取实际图片
img.onload = function(){ //真实图片加载完毕后触发
imgNode.src = this.src; //把真实图片url给真实图片节点;替换等待图片;this指向代理对象
}
return { //返回一个对象
setSrc:function(src){
imgNode.src = 'https://th.bing.com/th/id/R20d0cd9f696181bbead3b250782239fc?rik=SozVXnglW3zBxg&riu=http%3a%2f%2fbpic.588ku.com%2felement_pic%2f01%2f35%2f08%2f79573bd17084b13.jpg&ehk=UPH3Ji9JG4ZMqBzH9qKIJwuFG2R31XoHzvp4NkK5vXc%3d&risl=&pid=ImgRaw'; //代理图片url给到真实对象;先展示等待图片
img.src = src; //把src给到代理对象
}
}
})()
//调用:把真实图片给到对象
myImage.setSrc("https://himg.bdimg.com/sys/portrait/item/pp.1.d64a7775.reheWDHF3vrjPLK3n-YgKw.jpg?tt=1623053822499"); //真实图片
}
使用代理模式重构图片懒加载
window.onload = function(){
var myImage = (function(){
var imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc:function(src){
imgNode.src = src;
}
}
})()
//代理对象:先展示等待图片,再拉取真实图片
var ProxyImage = (function(){
var img = new Image() //内存中的代理
img.onload = function(){ //加载完之后将触发这个方法
myImage.setSrc(this.src) //this指向img;将真实图片给真实对象展示
}
return {
setSrc:function(src){
myImage.setSrc('https://th.bing.com/th/id/R20d0cd9f696181bbead3b250782239fc?rik=SozVXnglW3zBxg&riu=http%3a%2f%2fbpic.588ku.com%2felement_pic%2f01%2f35%2f08%2f79573bd17084b13.jpg&ehk=UPH3Ji9JG4ZMqBzH9qKIJwuFG2R31XoHzvp4NkK5vXc%3d&risl=&pid=ImgRaw') //代理图片
img.src = src;
}
}
})()
//调用;直接给代理对象设置真实图片 ProxyImage.setSrc('https://himg.bdimg.com/sys/portrait/item/pp.1.d64a7775.reheWDHF3vrjPLK3n-YgKw.jpg?tt=1623053822499') //真实图片
}
场景:双十一购买商品。购买者:观察者;商品:被观察者;商家(商品价格修改):发布者。发布者发布,关注着会收到改变。
观察者、被观察者、发布者
//观察者
var shopObj = {}
//商品列表 ['huawei':['订阅人1','订阅人2'],'apple':['订阅人3','订阅人2'],'oppo':['订阅人1']]
shopObj.list = []
//订阅;根据特定的商品推给特定人
shopObj.listen = function(goodskey,fn){ //商品订阅方法;fn订阅的行为; 建立商品和观察者的联系
if(!this.list[goodskey]){
this.list[goodskey] = []
}
shopObj.list[goodskey].push(fn) //往特定商品列表中添加订阅
}
shopObj.publish = function(){
var goodskey = arguments[0]
var fns = this.list[goodskey]
for(var i = 0,fn;fn = fns[i++];){
//执行订阅的fn
fn.apply(this,arguments)
}
}
//用户1:添加订阅
shopObj.listen('huawei',function(brand,model){
console.log('user 1'+brand,model)
})
//用户2:添加订阅
shopObj.listen('apple',function(brand,model){
console.log('user 2'+brand,model)
})
//商家发布订阅
shopObj.publish('huawei','P40')
观察者模式优化
//订阅发布放一起;之后再初始化
var event = {
list:[],
listen:function(key,fn){
if(!this.list[key]){
this.list[skey] = []
}
shopObj.list[key].push(fn) //往特定商品列表中添加订阅
},
publish:function(){
var goodskey = arguments[0]
var fns = this.list[goodskey]
for(var i = 0,fn;fn = fns[i++];){
//执行订阅的fn
fn.apply(this,arguments)
}
}
}
//观察者对象初始化
var initEvent = function(obj){
for(var i in event){
obj[i] = event[i];
}
}
//定义空发布者
var shopObj = {} //空对象
initEvent(shopObj) //将我们定义的方法属性都给它
//用户1:添加订阅
shopObj.listen('huawei',function(brand,model){
console.log('user 1'+brand,model)
})
//用户2:添加订阅
shopObj.listen('apple',function(brand,model){
console.log('user 2'+brand,model)
})
//商家发布订阅
shopObj.publish('huawei','P40')
- 给对象添加额外行为但是没有改变原有对象
- 简单装饰器的实现
class Circle{
draw(){
console.log('draw a circle')
}
}
//使用装饰器添加一个边框
class Decorator{
constructor(circle){
this.circle = circle;
}
draw(){
this.circle.draw() //圆自己绘制
this.setBorder() //增加绘制方法
}
setBorder(circle){ //装饰器方法
console.log('draw border')
}
}
var circle = new Circle()
var dec = new Decorator(circle)
dec.draw()
- 装饰器--注解形式
class Boy {
@run
speak(){
console.log('speak')
}
}
function run(target,key,descriptor){ //装饰器参数。
//target是Boy对象;key是被装饰的方法speak;descriptor是描述方法(writeable可写;enumerable可枚举,即是否可以for循环出来;configuerable可配置)
console.log('run')
}
var boy = new Boy()
boy.speak()
需要配置babel
,使用transform-decoarors-legacy
将@
转化成ES5代码;安装之后使用babel xxx.js -o /build/xxx.js
进行转化。
cnpm i -S babel-cli babel-preset-env babel-plugin-transform-decoarors-legacy
//babel-preset-env用于ES6转ES5
//babel-plugin-transform-decoarors-legacy用于注解翻译的
- 装饰器参数
- target 对象
- key被装饰的方法
- descriptor 描述对象
//给方法添加日志输出功能;不改变原有功能,添加新功能
class Math{
@log
add(a,b){
return a+b;
}
}
//日志装饰器
function log(target,name,descriptor){
var oldValue = descriptor.value; //descriptor.value是被修饰的方法,add
descriptor.value = function(){
console.log(`调用${name}`参数是`${arguments}`) //arguments是js的内置对象,包含所有实参的类数组
return oldValue.apply(target,arguments)
}
return descriptor;
}
var math = new Math();
math.value(1,3)
装饰器添加参数
//给方法添加日志输出功能,并且带上参数,加到a、b上;
class Math{
@log(100)
add(a,b){
return a+b;
}
}
//日志装饰器
function log(num){
return function log(target,name,descriptor){
var _num = num || 0;
var oldValue = descriptor.value;
descriptor.value = function(...args){
arg[0] += _num; //改变原有参数值
arg[1] += _num;
console.log(`调用${name}`参数是`${args}`)
return oldValue.apply(target,args)
}
return descriptor;
}
}
var math = new Math();
math.value(1,3)
- 场景:充值抽奖
开闭原则:原有代码对现有需求时关闭的,对扩展时开放的
充值500抽100rmb;充值200抽20rmb;否则无奖
function(orderType,isPay,count){ //订单类型;是否支付成功;金额
if(orderType === 1){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
if(count>0){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
}else if(orderType === 2){ //200rmb
if(isPay){
console.log('中奖20rmb')
}else{
if(count>0){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
}else if(orderType === 3){
console.log('谢谢参与')
}
}
- 使用职责链模式重构
function order500(orderType,isPay,count){
if(orderType === 1 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
order200(orderType,isPay,count)
}
}
function order200(orderType,isPay,count){
if(orderType === 2 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
orderdefault(orderType,isPay,count)
}
}
function orderdefault(orderType,isPay,count){
if(count>0 && isPay){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
- 使用职责链再次重构(不关自己的事情就交给下一个)
function order500(orderType,isPay,count){
if(orderType === 1 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
return "nextSuccessor"
}
}
function order200(orderType,isPay,count){
if(orderType === 2 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
return "nextSuccessor"
}
}
function orderdefault(orderType,isPay,count){
if(count>0 && isPay){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
//创建职责链关系对象
function Chain(fn){
this.fn = fn;
this.successor = null;
}
//设置下一个关系对象
Chain.prototype.setnextSuccessor =function(successor){
this.successor = successor;
}
//传递给下一个
Chain.prototype.passRequest = function(){
var ret = this.fn.apply(this,arguments)
if(ret === 'nextSuccessor'){ //传给下一个
this.successor && this.successor.passRequest.apply(this.successor,arguments)
}
}
//实例化
var chain500 = new Chain(order500)
var chain200 = new Chain(order200)
var chaindefault = new Chain(orderdefault)
chain500.setnextSuccessor = chain200;
chain200.setnextSuccessor = chaindefault;
//调用
chain500.passRequest(1,true,100) //类型1 支付成功 金额500
面向对象:抽象、多态、继承、封装