本文将详细讲解 HTML 、CSS 、JavaScript 、计算机网络知识等方面的内容,Vue 、React 、git 、项目开发实例等内容放在下篇,预祝各位成功上岸!
1、模板字符串 变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式
2、箭头函数
3 class 类的继承
class Animal {}
class Dog extends Animal {
constructor(name) {
super();
// this.name = name
}
}
3、module
4、promise
5、const / let
6、 扩展运算符(...)
7、 解构赋值
let a, b, rest;
[a, b] = [10, 20];
console.log(a);
// expected output: 10
console.log(b);
// expected output: 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// expected output: Array [30,40,50]
8、 Symbol 数据类型 一种新的原始数据类型,主要是为了避免属性名的重复
9、Map 一种新的数据结构,类似对象,Set 一种新的数据结构,类似数组
1、箭头函数没有自己的 this 指向,它的 this 指向来源于它的上级,并且继承而来的 this 指向是无法改变的。
2、 箭头函数由于没有自己的 this,所以不能作为构造函数。
3、箭头函数中没有 arguments(形参数组),但是可以访问外围函数的arguments对象
补充:ES6 箭头函数中的 this 和所在环境(外层)中的 this 指向一致
可以使用call()、apply()、bind() 来改变 this
- call 方法 call()是 apply()的一颗语法糖,作用和 apply()一样,同样可实现继承,唯一的区别就在于 call()接收的是参数列表,而 apply()则接收参数数组。
- bind 方法 bind()的作用与 call()和 apply()一样,都是可以改变函数运行时上下文,区别是 call()和 apply()在调用函数之后会立即执行,而 bind()方法调用并改变函数运行时上下文后,返回一个新的函数,供我们需要时再调用.
1、第一个参数都是 this 的指向对象
2、 apply 的第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
3、call 传入的第二个参数是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
4、 bind 传入的第二个参数也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数,供我们需要时再调用.。
1、promise是异步编程的一种解决方案,解决多个异步方法串行的问题,比如回调地狱等;
2、promise,简单地说就是一个容器,里面保存着某个未来才会结束的事件,从语法说promise是一个对象,从他可以获取异步操作的消息。promise提供统一的api,各种操作都可以用相同的方法进行处理.;
3、它接受一个 function 作为参数。function 中有两个形参,第一个表示请求成功的回调,第二个表示请求失败的回调,分别是 resolve 和 reject ;
4、.then 在成功的时候触发 .catch 在失败的时候触发
5、promise 的状态不受外界影响不可逆,三个状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
6、有两个很重要的 api:
返回一个新的promise,只有所有的promise都成功才能成功,只要有一个失败了就直接失败;
返回一个promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝;
回调函数中嵌套回调函数的情况就叫做回调地狱。它就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。
1、Promise是 ES6 中处理异步请求的语法,使用 .then() 来实现链式调用,使用 .catch() 来捕获异常。
2、async/await 是对 Promise 的升级,async用于声明一个函数是异步的,await是等待一个异步方法执行完成
3、async/await 用同步的写法写异步(await一个Promise对象),async/await 的捕获异常可以使用 try/catch 语法。(也可以使用 .catch 语法)用同步的方法写异步的代码;
详细分析查看:https://blog.csdn.net/leoxingc/article/details/127813133
// for
var arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]); // 1,2,3
}
// for...of...
var arr = [1, 2, 3];
for (var i of arr) {
console.log(arr[i]); // 1,2,3
}
// for...in...
var arr = [1, 2, 3];
for (var i in arr) {
console.log(arr[i]); // 1,2,3
}
// forEach( item, index, arr )
var arr = [1, 2, 3];
arr.forEach((item, index, arr) => { // item为arr的元素,index为下标,arr原数组
console.log(item); // 1, 2, 3
console.log(index); // 0, 1, 2
console.log(arr); // [1, 2, 3]
});
// map() 映射
var arr = [1, 2, 3];
var arr1 = arr.map((item, index, arr) => {
// item为arr的元素,index为下标,arr原数组
console.log(item); // 1, 2, 3
console.log(index); // 0, 1, 2
console.log(arr); // [1, 2, 3]
return item * 2; // 返回一个处理过的新数组[2, 4, 6]
});
console.log(arr);
console.log(arr1); // 处理过的新数组[2, 4, 6]
var arr = [1, 2, 3];
var arr1 = arr.filter(function (item, index, arr) {
// item为arr的元素,index为下标,arr原数组
console.log(item); // 1, 2, 3
console.log(index); // 0, 1, 2
console.log(arr); // [1, 2, 3]
return item > 2; // 返回一个过滤过的新数组[3]
});
console.log(arr);
console.log(arr1); // 过滤过的新数组[3]
// some()
var arr = [1, 2, 3];
arr.some((item, index, arr) => {
// item为数组中的元素,index为下标,arr为目标数组
console.log(item); // 1, 2, 3
console.log(index); // 0, 1, 2
console.log(arr); // [1, 2, 3]
});
// every()
var arr = [1, 2, 3];
var arr1 = arr.every((item, index, arr) => {
// item为数组中的元素,index为下标,arr为目标数组
console.log(item); // 1, 2, 3
console.log(index); // 0, 1, 2
console.log(arr); // [1, 2, 3]
return item > 0; // true
});
console.log(arr); // [1, 2, 3]
console.log(arr1); // true
// every()
var arr = [1, 2, 3];
var arr1 = arr.reduce((sum, item, index, arr) => {
// item为数组中的元素,index为下标,arr为目标数组,sum为上一次调用回调函数时的返回值,
console.log(item);
console.log(index);
console.log(arr);
return sum + item;
}, init);
前端遍历数组的方法:
1、利用数组的 indexOf 方法,新建一个空数组,循环遍历旧数组,判断空数组中是否有当前的元素,如果有就跳过,如果没有就执行 push 方法。
let arr = [1, 1, 2, 2, 3, 3, 4, 5];
let newArr = [];
arr.forEach(item => {
if (newArr.indexOf(item) < 0) {
newArr.push(item);
}
});
2、利用数组的 splice 方法,先利用 sort 方法对数组进行排序,然后循环遍历数组,比较前一项与后一项是否相同,如果相同就执行 spilce 方法删除当前项。
3. 利用 ES6 中 Set 不能存放相同元素的特点,配合...展开运算符进行去重。
let arr=[1,2,3,4,3,2,1,5,3];
let set=new Set(arr);
//因为set结构并不是数组,所以需要转为数组
set=[...set];
4. lodash 插件也可以去重。
let a = [1, 3, 4];
Array.isArray(a); //true
a instanceof Array; //true
a.constructor === Array; //true
Object.prototype.toString.call(a) === "[object Array]"; //true
for...in 只能获得对象的键名,对数组来说是下标,对象是属性名。并且手动添加的属性也能遍历到;
for...of 只能获得键值(数组),遍历对象会报错;
(1)冒泡排序:数组中的元素两两进行比较,如果前一个比后一个大,交换值,第一轮结束
(2)选择排序:选出一个位置,这个位置上的数,和后面所有的数进行比较,如果比较出大小就交换两个数的位置
(3)/*sort排序 */
arr.sort((a,b)=>{
return a-b;
})
1、Array.from() 将伪数组转成真数组
arr=Array.from(arr);
2、arguments 将伪数组转成真正的数组
function test (){
arguments.__proto__ = Array.prototype;
arguments.push(10)
console.log(arguments)
}
test(1,2,3,4)
数据中有 800 个元素,随机取一个
生成一个 0-800 之间的随机数作为数组下标
const arr = [1,2,3,4,5,6,7...]
// 随机获取一个下标
// Math.random的取值范围 0=
数组中有 800 个元素,删除第 701 个
arr.splice(700, 1); // splice参数一表示开始位置 参数二表示个数 后面的参数序列是用来替换的内容,如果没有就只做删除处理
filter, slice + concat;
相加的时候会做隐式转换,数字转换为字符串,最终的结果是字符串拼接;
let str = "123";
console.log(Number(str));.
console.log(str \* 1);
浅拷贝:只拷贝第一级实现方式:...扩展运算符 Object.asign
浅拷贝:基本数据类型拷贝值,复杂数据类型,拷贝地址
深拷贝:逐层拷贝对象的每一级
实现方式:`JSON.parse(JSON.stringify(obj))`或者使用插件 lodash
深拷贝:拷贝的是值,相互之间没有影响 地址指向堆
深浅拷贝是因为引用数据类型数据存储的问题,引用数据类型会把指针存放在栈内存中,真正的值存放在堆内存中,浅拷贝只是拷贝了地址,而深拷贝则是完全拷贝,并且拷贝后的对象和原对象没有任何关联。
let obj = {name:'zhangsan',age:18}
let newObj = JSON.parse(JSON.stringfy(obj))
每个构造函数都有一个原型,每个原型对象又有一个construtor属性指向构造函数,每个实例都有__proto__指向原型对象,原型对象上的属性方法能被实例访问。
在JS中,用 __proto__ 属性来表示一个对象的原型链。当查找一个对象的属性时,JS会向上遍历原型链,直到找到给定名称的属性为止。
JavaScript prototype(原型对象)
所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法。
所有 JavaScript 中的对象都是位于 原型链顶端的 Object 的实例。
每个对象都有一个`__proto__`属性,并且指向它的`prototype`原型对象
每个构造函数都有一个`prototype`原型对象
`prototype`原型对象里的`constructor`指向构造函数本身
原型链的终点 Object.prototype.__proto__ === null //true
原型和原型链的详细介绍:http://t.csdn.cn/zQZMc
原型(prototype): 一个简单的对象,用于实现对象的属性继承。可以简单的理解成对象的爹。每个JavaScript对象中都包含一个–proto-- (非标准)的属性指向它爹(该对象的原型),可obj.–proto–进行访问。
构造函数: 本质上是一个普通的函数,只是可以通过new来 新建一个对象的函数。
实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数。
1、创建一个新对象
2、将构造函数的作用域赋给新对象(因此this指向了这个新对象)
3、执行构造函数中的代码(为这个新对象添加属性)
4、返回新对象
闭包就可以在全局函数里面操作另一个作用域的局部变量!
在函数外部可以访问函数内部的变量。
使用场景:一时半会想不到了-------(必须答时 1.函数作为参数传递。2.函数作为返回值)
function foo(){
var local = 1
function bar(){
local++ //可以访问这个函数词法作用域中的变量
return local
}
return bar //一个函数被当作值返回
}
var func = foo() //外层函数执行完毕时销毁
func() //这个通道可以访问这个函数词法作用域中的变量,即函数所需要的数据结构保存了下来,而且无法直接访问,必须通过返回的函数。
- 虽然可以保护私有变量,但用多了可能造成内存泄露。
闭包的好处:减少全局变量的定义,减少全局空间的污染;形成命名空间;
闭包的坏处:容易造成内存泄漏:一块内存空间既不能被销毁,也不能被访问,通常出现 IE 低版本;
闭包优化:由于闭包会一直占用内存空间,直到页面销毁,我们可以主动将已使用的闭包销毁,
将闭包函数赋值为null 可以销毁闭包;
闭包 this 执行问题:this指向window对象(因为匿名函数的执行具有全局性,所以其this对象指向window);
当不断向堆中存储数据,而不进行清理,这就是内存泄漏;
当页面中元素被移除或替换时,若元素绑定的事件仍没被移除,在IE中不会作出恰当处理,此时要先手工移除事件,不然会存在内存泄露;
1、全局变量引起的内存泄露
2、闭包引起的内存泄露:慎用闭包
3、dom清空或删除时,事件未清除导致的内存泄漏
4、循环引用带来的内存泄露
因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
轮询:定时发送请求,响应请求
websocket
1. js 是单线程的
所谓单线程就是一次只能执行一段代码,在执行的时候如果遇到比较耗时的操作,默认就会停下来等待这个操作完成之后继续走。这种情况下,就会出现页面卡在那里不动。为了解决这个问题 js 一般把比较耗时的操作做异步处理;
2. js 中的异步处理
js 中存在一个异步队列,所有比较耗时的操作都会被丢在这个异步队列中。当主线程空闲(同步代码)之后会执行异步队列中的代码,这个就是 js 中的 eventloop(事件循环);
宏任务,是运行环境提供的异步操作,例如:setTimeout;
微任务,是 js 语言自身的异步操作,例如:Promise;
在一个宏任务周期中会执行当前周期中的所有微任务,当所有的微任务都执行完成之后会进入下一个宏任务周期;
1、原型链继承
核心:将父类的实例作为子类的原型
2、构造继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
3、实例继承
核心:为父类实例添加新特性,作为子类实例返回
4、组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
5、寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
super()方法用来调用父类的构造函数;
constructor 是原型对象上的一个属性,默认指向这个原型的构造函数;
- Map
- Map 的键可以是任意值。包括对象{ {a:1}:xxx }
- .set() var myMap = new Map(); myMap.set(0, "zero");
- .get()
- .delet()
- Set
- Set 的键名是唯一的,不能重复,undefined 和 NaN 也不重复
- 用来去重
var mySet = new Set([1, 2, 3, 4, 4]);
[...mySet]; // [1, 2, 3, 4]
- Map 是 es6 新增的一种数据结构,继承自 Object,并对 Object 做了一些拓展。
- Map 的键可以是任意的数据类型,Object 不行。
- Map 是可迭代(iterable)的,可以使用 for...of 遍历,而 Object 不行。
- Map 的长度可以通过 size 直接拿到,Object 不行。
- Map 使用拓展运算符是可以直接展开的,Object 不行。
一般数据类型:string number boolean null undefined symbol bigint
`BigInt`数据类型的目的是比`Number`数据类型支持的范围更大的整数值。
复杂数据类型:对象、数组、function
- 值为一个地址,地址指向真正的数据
typeof 如果是基本数据类型,使用typeof来判断,但是typeof不能用来判断引用数据类型的数据
不能判断Null、Array等
instanceof 不能检测Null和undefined
contructor
Object.prototype.toString.call( )
typeof原理: 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息。
typeof null 为"object", 原因是因为 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位都为0的话会被判断为Object类型,null的二进制表示全为0,自然前三位也是0,所以执行typeof时会返回"object"。
用 typeof 检测**null**返回是 object。在 JavaScript 中 null 表示 "什么都没有"。null 是一个只有一个值的特殊类型。表示一个空对象引用。
typeof 一个**没有值的变量**会返回 **undefined**。
null 和 undefined 的值相等,但类型不等:
typeof "John"; // string
typeof 3.14; // number
typeof false; // boolean
typeof [1, 2, 3, 4]; // object 在JavaScript中,数组是一种特殊的对象类型。 因此 typeof [1,2,3,4] 返回 object。
typeof { name: "John" }; // object
typeof undefined; // undefined
typeof null; // object
null === undefined; // false
null == undefined; // true
- obj instanceof Object ,可以左边放你要判断的内容,右边放类型来进行 JS 类型判断
- 需要知道是什么类型才能正确判断
[1, 2] instanceof
Array(
// true
function () {}
) instanceof
Function(
// true
{ a: 1 }
) instanceof
Object(
// true
new Date()
) instanceof
Date; // true
Object.prototype.toString.call(999); // "[object Number]"
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(Symbol()); // "[object Symbol]"
Object.prototype.toString.call(42n); // "[object BigInt]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(true); // "[object Boolean]
Object.prototype.toString.call({ a: 1 }); // "[object Object]"
Object.prototype.toString.call([1, 2]); // "[object Array]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(function () {}); // "[object Function]"
toString(); // 转化为字符串,不可以转null和underfined
String(); // 转换为字符串,
Number(); // 转换为数字,字符串中有一个不是数值的字符,返回NaN
parseInt(); // 转换为数字,第一个字符不是数字或者符号就返回NaN
Boolean(); // 转换为布尔值
赋值式函数需要给变量赋值,以 var、let;声明式是不需要给变量赋值,以 function 开头;
Object.is('foo', 'foo')// true
Object.is(window, window)// true
Object.is('foo', 'bar'); // false
Object.is([], []); // false
var test = { a: 1 };
Object.is(test, test); // true
Object.is(null, null); // true
// 特例
Object.is(0, -0); // false
Object.is(-0, -0); // true
Object.is(NaN, 0 / 0); )// true
- 返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 (两者的主要区别是 一个 for-in 循环还会枚举其原型链上的属性)
let arr = ["a", "b", "c"];
Object.keys(arr); //["0", "1", "2"]
let obj = { age: 20, sex: "男" };
Object.keys(obj); //["age", "sex"]
var obj = { 10: "a", 1: "b", 2: "c" };
Object.values(obj); // ['b', 'c', 'a']
var obj1 = { 0: "a", 1: "b", 2: "c" };
Object.values(obj1); // ['a', 'b', 'c']
const obj = { foo: "bar", baz: 42 };
Object.entries(obj); // [ ['foo', 'bar'], ['baz', 42] ]
// array like object
const obj = { 0: "a", 1: "b", 2: "c" };
Object.entries(obj); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]
// string
Object.entries("abc"); // [['0', 'a'], ['1', 'b'], ['2', 'c']]
Object.entries(100); // []
1、通过JSON.stringify(obj)来判断两个对象转后的字符串是否相等
2、getOwnPropertyNames方法返回一个由指定对象的所有自身属性的属性名组成的数组。先进行长度的比较,然后进行遍历
3.Object.is(a,b)
// 手写:
function diff(obj1,obj2){
var o1 = obj1 instanceof Object;
var o2 = obj2 instanceof Object;
// 判断是不是对象
if (!o1 || !o2) {
return obj1 === obj2;
}
//Object.keys() 返回一个由对象的自身可枚举属性(key值)组成的数组,
//例如:数组返回下表:let arr = ["a", "b", "c"];console.log(Object.keys(arr))->0,1,2;
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return false;
}
for (var o in obj1) {
var t1 = obj1[o] instanceof Object;
var t2 = obj2[o] instanceof Object;
if (t1 && t2) {
return diff(obj1[o], obj2[o]);
} else if (obj1[o] !== obj2[o]) {
return false;
}
}
return true;
}
// 他俩不相等,因为对象的存储位置不一样,所以永远不相等
function compareTwoObj(a, b) {
return JSON.stringify(a) == JSON.stringify(b);
}
Object.keys(obj); // 数组,所有属性名组成的数组
for (let k in obj) {
}
js对象转字符串
JSON.stringify
// json字符串转对象
JSON.parse
如何删除对象中的一个属性
var obj = { a: 1, b: 2, c: 3 };
delete obj.c; // 删除一个属性
var strJson = `{"a": "123", "1-a": "456"}`;
面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成类
面向对象的三大基本特征:封装、继承、多态
//多态演示
// 构造函数
function Behavior () {}
Behavior.prototype.sound = function (sound) {
return sound
}
function Man () {}
Object.setPrototypeOf(Man.prototype, Behavior.prototype)
function Woman () {}
Object.setPrototypeOf(Woman.prototype, Behavior.prototype)
let man = new Man()
console.log(man.sound('哈哈哈')) // 哈哈哈
let woman = new Woman()
console.log(woman.sound('嘻嘻嘻')) // 嘻嘻嘻
- 获取所有的属性名,Object.keys、for in
- 获取所有的属性值,Object.values
- 删除一个属性
- 动态获取属性,可以使用数组下标的方式
1. 在DOM元素中直接绑定
2. 在JavaScript代码中绑定
3. 绑定事件监听函数
三个阶段:捕获、事件源、冒泡
捕获是从 html 标签开始,逐层往内进行传递,到触发事件的标签为止
事件源是在当前触发事件的标签上进行传递
冒泡是从当前触发事件的标签开始逐层往外进行传递到 html 节点结束
addEventLister 的第三个参数,是一个 bool 值。默认是 false,表示在冒泡阶段触发
event 对象中的 target 和 currentTarget 有什么区别?
target 触发事件的标签
current Target 绑定事件的标签
addEventListener,第三个参数是一个 bool 值,默认是 false 表示在冒泡阶段触发事件
- 事件监听
使用事件监听器方式添加事件,可以给同一个 DOM 对象添加多个相同的事件名,对应事件处理函数会依次执行
普通事件添加方式,如果添加多个相同的事件名,后面的会覆盖前面,只会执行最后一个
on类型的事件在事件冒泡阶段触发
target(触发事件的标签)/currentTarget(绑定事件的标签)
**DOM标准事件流的触发的先后顺序为:先捕获再冒泡**
**阻止冒泡**:stopPropagation || cancelBubble
#### 事件默认行为
a标签的跳转,input框的输入,Submit按钮的提交,右键菜单
**阻止默认事件**:returnValue=false || preventDeafault
- 事件委托
onClick 表示添加一个点击事件,属于属性赋值。属性多次赋值会出现覆盖,只有最后一次的赋值是有效果的
addEventlistener 事件委托,可以添加多次,每一次添加都会执行,它可以接收三个参数('事件名字', 回调函数, bool)
bool 默认是 false 表示在冒泡阶段触发
01-在允许拖拽的节点元素上,使用 on 来监听 mousedown(按下鼠标按钮)事件,鼠标按下后,克隆当前节点
02-监听 mousemove(鼠标移动)事件,修改克隆出来的节点的坐标,实现节点跟随鼠标的效果
03-监听 mouseup(放开鼠标按钮)事件,将原节点克隆到鼠标放下位置的容器里,删除原节点,拖拽完成。
BOM 是 browser object model 的缩写,简称浏览器对象模型。是用来获取或设置浏览器的属性、行为,例如:新建窗口、获取屏幕分辨率、浏览器版本号等。 比如 alert();弹出一个窗口,这属于 BOM,核心对象是 window。
DOM 是 Document ,简称文档对象模型。是用来获取或设置文档中标签的属性,例如获取或者设置 input 表单的 value 值。document.getElementById("").value; 这属于 DOM,核心对象是 标签。
是为标签添加的属性 data-开头的这些,在 js 中可以直接通过使用 dom 对象的 dataset 属性获取
工厂模式、单例模式、原型模式,组合模式,发布-订阅模式
优点:出色的浏览器兼容性;出色的DOM操作的封装,可以进行快速的DOM元素操作;支持链式操作,支持插件
缺点:不能向后兼容,一个页面使用多个插件容易冲突,对动画和特效的支持差;
Ajax 核心使用XMLHttpRequest对象,多个请求之间如果有先后关系的话,就会出现回调地狱
axios支持 Promise API,提供了一些并发请求的接口,自动转换JSON数据,体积也较小;
mvc 是一种开发模式
model,用来存储数据
view,用来展示数据
controller [kənˈtrəʊlə(r)] ,用来控制数据的展示方式
https,使用 ssl 证书进行加密传输 防止网络抓包 保证信息安全
js 中发起请求的方式:xhr、fetch、websocket
请求行------请求行有请求方法、URL和HTTP协议版本3个字段
请求头(携带数据) cookies token content-type
请求体 传输数据 发起post或get请求
数据传输的格式
状态码:200 多,300 多,400 多,500 多
200:成功
301:永久重定向、302:临时重定向
401:未授权
403:服务器拒绝访问
404:页面找不到
500:服务器错误
- 单例模式
- 观察者模式
- 工厂模式
- 发布订阅模式
get 用来获取数据,参数传递的时候在 url 中可以直接看到,不太安全。传递的数据量比较少,可以加入收藏夹
post 用来传递大量数据,数据在请求体中进行传递,url 地址中看不到,相对于 get 请求更安全
1、常见的请求方式
2、 CRUD(增删改查)
3、SSR 服务器端渲染
4、get和post的区别
5、get请求传递的参数为什么有大小限制?
get请求在url中传递,不同浏览器对url长度有限制
- 第一次握手是客户端给服务端发送请求,请求建立连接。
- 第二次握手是服务端给客户端发送请求,表示已经收到客户端的请求,并且同意建立连接。
- 第三次握手是客户端向服务端发送请求,表示确认收到服务端发送的信息。
- 三次握手的原因是为了确认客户端和服务端都有收发信息的能力,少一次确认不了,多一次浪费资源。
1. 解析 HTML 文档,构建 DOM 树(DOM 节点树。
2. 解析 CSS 文档,生成 CSS 规则树。
3. 合并 DOM 树和 CSS 规则树,生成渲染树 render 树。
4. 布局 render 树,计算每个元素的大小,位置等信息(重排会走这一步)。
5. 绘制 render 树,绘制页面的像素信息,(根据 render 树上每个节点的几何属性计算每个节点的像素数,重绘会走这一步)
6. 浏览器会将各层节点的像素信息发给 GPU,GPU 将各层进行合成和渲染,最终展示到页面上。
- HTTP 协议工作在 80 端口,HTTPS 协议工作在 443 端口
- HTTPS 需要申请证书(用于验证服务器身份)
- HTTP 在 TCP 三次握手建立连接之后即可开始传输数据;HTTPS 协议则需要在建立 TCP 连接之后客户端与服务器在进行 SSL 加密,确定对话密钥,完成加密后才开始传输数据。
- HTTPS 协议协议:是具有安全性的ssl加密传输协议,为浏览器和服务器之间的通信加密,确保数据传输的安全;
HTTP 协议:是超文本传输协议,信息是明文传输;
- HTTP2 使用的是二进制传送,HTTP1.X 是文本(字符串)传送
- HTTP2 支持路复用
- XSS(跨站脚本攻击),是一种代码注入攻击,是通过在网站中注入恶意代码达到劫持用户 cookie 或其他信息的一种攻击。
- 客户端:限制输入长度
- 上线前:使用扫描工具自动检测 XSS 漏洞
- CSRF(Cross-site request forgery)跨站请求伪造
- 服务端做一些防御操
域名协议端口号只要有一个不一致就会引起跨域,跨域是浏览器的安全机制导致的,只有在浏览器中有;
跨域就是通过某些手段来绕过同源策略限制,实现不同服务器之间通信的效果。
1、前端我们解决不了,都是服务器端解决的;
2、在开发的时候:我可以通过配置代理服务器或者浏览器安装插件的方式临时解决,但是上线之后还是需要服务器做配置;
3、通过 jsonp 跨域
jsonp是一种跨域通信的手段,它的原理其实很简单:首先是利用script标签的src属性来实现跨域。通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务器端向客户端通信。由于使用script标签的src属性,JSONP 使用简单且兼容性不错,但是只限于 get 请求。
4、postMessage 跨域
5、跨域资源共享(CORS)
需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。浏览器会自动进行
CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
//谷歌插件 corsf
如果面试官问:“CORS 为什么支持跨域的通信?”
答案:跨域时,浏览器会拦截 Ajax 请求,并在 http 头中加 Origin。
6、nginx 代理跨域
7、nodejs 中间件代理跨域
8、WebSocket 协议跨域
同步叫阻塞模式,异步叫非阻塞模式
异步的实现原理:js 中的事件轮训(eventloop),微任务和宏任务
异步实现的原理:
在js中有一个异步回调队列,当遇到异步任务的时候会把这个任务直接转到异步队列中。等待所有的同步任务都完成之后在执行异步队列
异步队列中的任务会分为微任务和宏任务
每一个宏任务周期中会把当前周期中的所有微任务都执行完成之后,在执行下一个宏任务周期
宏任务:所有的运行环境提供的异步任务
微任务:所有的语言自带的异步任务
异步加载时相对于同步加载而言的,我们平常书写的代码就是同步加载,代码自上而下执行,是阻塞式的,而异步加载是非阻塞式的,在执行同步代码时,并不会阻塞我后续代码的执行,(例如定时器,发送 ajax 请求),而延迟加载则是一开始并不加载,在我需要的时候再进行加载(例如图片的懒加载)
(1)回调函数callback
(2)Promise
(3)Generator
(4)async/await
js 会自动的进行垃圾回收,就是那些无用的代码和运行任务会在结束之后从内存中清理出来。js 中的垃圾回收使用的是标记清除法
防抖:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时(本质是一个定时器)。
例如:搜索框的联想功能,不断输入值的时候,使用防抖来节约资源。(短信验证码、提交表单、resize 事件等)
节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
例如:scroll 事件,单位时间后计算一次滚动位置,input 事件(上面提到过),播放事件,计算进度条
两者的共同点:
函数防抖和函数节流都是防止某一时间频繁触发,但是这两兄弟之间的原理却不一样。
函数防抖是某一段时间内只执行一次,而函数节流是单位时间执行一次。
回流一定会触发重绘,但是重绘不一定会引起回流
改变页面布局或者大小尺寸的时候会引起回流,反之就是重绘
重绘:当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新 ,UI 层面, 例如改变元素颜色
回流:又叫重排(layout)。当元素的尺寸、结构或者触发某些属性时,浏览器会重新渲染页面,称为回流
- 在浏览器中输入 url
- 应用层 DNS 解析域名
- 应用层客户端发送 HTTP 请求
- 传输层 TCP 传输报文
- 网络层 IP 协议查询 MAC 地址
- 数据到达数据链路层
- 服务器接收数据
- 服务器响应请求
- 服务器返回相应文件
- cookie:可设置失效时间,没有设置的话,默认是关闭浏览器后失效
- localStorage:除非被手动清除,否则将会永久保存。
- sessionStorage: 仅在当前网页会话下有效,关闭页面或浏览器后就会被清除。同源不共享。
- cookie:4KB 左右 localStorage 和 sessionStorage:可以保存 5MB 的信息。
- cookie:每次都会携带在 HTTP 头中,如果使用 cookie 保存过多- 数据会带来性能问题
- localStorage 和 sessionStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信
token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token
的前几位以哈希算法压缩成的一定长度的十六进制字符串)
特点:服务端无状态化、可扩展性好支持移动端设备.安全.支持跨程序调用
每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。**用解析
token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库**
token 完全由应用管理,所以它可以避开同源策略
navigator.userAgent 用户代理
location.href
location.search
- 通过字符串截取获取 url 中的参数
- 减少 http 请求次数
- 资源文件压缩合并,js 和 css 文件压缩合并
- 使用精灵图片
- 如果图片特别多,使用图片懒加载
- 使用 cdn 的方式加载第三方资源【服务器端运维的范畴】
- 开启 web 服务器的 GZIP 压缩【服务器端运维的范畴】
- CDN
属于运维的范畴,和前端没有关系。我们以后如果要开启服务器的 CDN 加速,只需要买节点就好
就是把小图标集中放置在一张图片上,使用背景定位的方式展示每一个小图标
- ajax,异步的 javascript 和 XML;
- websocket,长连接。建立起链接之后,服务器端可以主动的向客户端推送数据;
https,使用 ssl 证书进行加密传输
- IE 浏览器:Trident 内核
- Chrome 浏览器:webkit(过去)、blink(现在)
- FireFox:Gecko 内核
- Edge:chormium 内核
- Safari:webkit 内核
- 国内主要兼容 360 安全浏览器,极速模式和兼容模式,分别对应谷歌的 blink 内核和 IE 的 trident 内核
- 电脑中浏览器的兼容性(360、qq 浏览器、搜狗)
- 手机浏览器的浏览器兼容性(一般手机,小米手机,ios 手机),微信浏览器。
- 答:我们都是浏览器挨个去调试,或者加浏览器前缀,或者 jQuery 来解决 js 的兼容问题,避免使用 h5 和 css3 新增的标签和样式。
tcp 是面向连接的;udp 不是
div dl form h1 h2 h3 h4 h5 h6 hr ol li p pre table td tr
a b s i u span br img input textarea sub sup
行内元素:
1.无法设置宽高;
2. 对margin仅设置左右有效,上下无效;
3. padding上下左右有效;不会自动换行
块级元素:
1.可以设置宽高
2. margin和padding的上下左右均对其有效
3. 超出当前行会自动换行
4. 多个块状元素标签写在一起,默认排列方式为从上至下
img:属于行内块元素(inline-block),即有行内元素的属性也有块级元素的属性
元素之间的转化可以通过设置样式:display:block/inline/inline-block来改变自身的元素属性
空元素:
没有闭合标签的标签被称作为空标签。
在我们使用的标签中,有的具有闭合标签
- header、nav、footer、main、section、article、address 等等
- 语义化标签就是那些看到单词就知道是做什么的标签,它的优势是便于 seo 优化和可读性更强
- 如何在网页中添加一个多媒体资源
audio、video
我们在实际开发适合可能需要使用到插件:flv.js、video.js、dplayer、hls.js
* 新的表单属性(time、email、url、search)
- 阴影:文字阴影 text-shadow 盒子阴影:box-shadow
- 图片背景:background-size background-origin
- 边框:border-radius border-image:CSS3 边框图片
- 伪类(:link,:visited,:active,:hover,:focus,:before,:after)
* 渐变 background-image: linear-gradient(#e66465, #9198e5);
* 过渡 transition: width 2s, height 2s;
* 动画 animation @keyframes
:checked | input:checked | 选择所有选中的表单元素 |
:link | a:link | 选择所有未访问链接 |
:visited | a:visited | 选择所有访问过的链接 |
:active | a:active | 选择正在活动链接 |
:hover | a:hover | 把鼠标放在链接上的状态 |
:focus | input:focus | 选择元素输入后具有焦点 |
:before | p:before | 在每个 元素之前插入内容 |
:after | p:after | 在每个 元素之后插入内容 |
content 属性
选择器 | 示例 | 示例说明 |
---|---|---|
:before | p:before | 在每个 元素之前插入内容 |
:after | p:after | 在每个 元素之后插入内容 |
display:flex; 设置为弹性盒(父元素添加)
flex-direction:row(默认)/column(子元素竖向显示)
justify-content:; 主轴对齐方式()
fle-start 开头
flex-end 结尾
center 居中
space-around 空间在子元素两边平均分布
space-between 空间在子元素之间分布
space-evenly 空间相等
align-items:; 侧轴对齐
flex-start;
flex-end;
center;
baseline;
flex-wrap:wrap; 换行
align-content:; 多轴对齐
flex-start 开头
flex-end 结尾
center 居中
space-around 空间在子元素两边空间分布
space-between 空间在子元素之间分布
space-evenly 空间相等
- 流式布局,盒子自上而下排列。
- 弹性布局,flex 弹性盒。
- grid 网格布局。
- 自适应(响应式布局),使用 rem 单位。
媒体查询,@media 作用是设置一个不同的显示范围,在指定的范围中显示我们对应的 css 代码
1. 页面布局
2. 移动端300ms延迟问题
3.一些情况下对非可点击元素如(label,span)监听click事件,ios下不会触发,css增加cursor:pointer就搞定了。
4.CSS动画页面闪白,动画卡顿
解决方法:
1.尽可能地使用合成属性transform和opacity来设计CSS3动画,不使用position的left和top来定位
2.开启硬件加速
px,像素单位
em,相对单位。相对于父节点的字体大小
rem,相对单位。相对于 html 标签的字体大小
vw/vh,是视口单位,1vw=0.01 个视口宽度。默认情况下 1vw=3.75px
禁止用户双指缩放 “maximum-scale”属性的值为“1.0”,“user-scalable”属性的值为“0”
user-scalable:用户是否可以手动缩放
maximum-scale:允许用户缩放到的最大比例。
- 对于只需要适配少部分手机设备,且分辨率对页面影响不大的,使用 px 即可 。
- 对于需要适配各种移动设备,使用 rem,例如只需要适配 iPhone 和 iPad 等分辨率差别比较挺大的设备。
absolute:绝对定位。基于离自己最近的一个具有定位属性的父级元素进行定位
relative:相对定位,相对于自身应该出现的位置进行偏移
fixed:固定定位。相对于浏览器窗口进行定位
sticky 粘性定位 基于用户的滚动位置来定位。(一般用于页面导航的吸顶效果)
标准盒模型:标准模式下总宽度=width+margin(左右)+padding(左右)border(左右)
怪异盒模型:怪异模式下总宽度=width+margin(左右)
1. W3C 标准盒模型:
属性width,height只包含内容content,不包含border和padding。
2. IE (怪异)盒模型:
属性width,height包含border和padding,指的是content+padding+border。
块级格式化上下文 一个封闭 的空间 外面的元素不受影响
规则:
触发条件:
解决的问题:
解决方法:
1、为父元素添加overflow:hidden; 缺点:隐藏超出部分
2、在所有浮动起来的元素后面添加空元素块,为空元素添加 clear:both 缺点:容易造成代码冗余.
3、直接给父元素单独设置高度(height);缺点:只适合高度固定的布局,要给出精确的高度,如果高度和父级div不一样时,会产生问题。对于响应式布局会有很大影响。
div{
zoom:1; 兼容ie6 ie7
}
div:after{
content:"";
dispiay:block;
clear:both;
height:0;
visibility:hidden; 将元素隐藏 但空间还在
}
1.在标签尾部添加空块级标签,设置样式属性为:clear:both;缺点:如果页面浮动布局多,就要增加很多空div,不利于页面的优化。
2. 父级定义伪类after和zoom,.box:after{display:block; clear:both; content:""; visibility:hidden; height:0;} .box{ zoom: 1 }
3. 简单粗暴,父级设置overflow:hidden,缺点是不能和position配合使用
4. 直接给父元素单独设置高度(height);缺点:只适合高度固定的布局,要给出精确的高度,如果高度和父级div不一样时,会产生问题。对于响应式布局会有很大影响。
1、父元素使用 display:flex;
2、定位加位移
3、定位+margin: auto;
1、不同浏览器下的padding和margin不同 解决方法:使用通配符(*)将padding和margin设置为0;
2、块属性标签float之后,又有横向的margin值,在IE6中显示会比设置的大(IE6双边距bug) 解决方法:在float标签样式控制中加入display:inline;
3、设置较小的高度标签(一般小于10px),在IE6,IE7,遨游中超出自己设置的高度 解决方法:给超出高度的标签设置overflow:hidden;或者设置行高line-height小于你设置的高度。
4、行内标签设置display:block;后又采用float布局,再设置横向margin值时,在IE6中显示会比设置的大(IE6双边距bug) 解决方法:在display:block;后面加上display:inline;display:table; #滚动条到顶端的距离(滚动高度) var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; #获取非行内样式兼容 IE:currentStyle 标准:getComputedStyle #阻止事件冒泡的兼容 e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true; #获取事件源的兼容 var target = event.target || event.srcElement;
5、图片默认有间距 解决方案:使用float 为img 布局;
6、IE9一下浏览器不能使用opacity 解决方案: opacity: 0.5;filter: alpha(opacity = 50);filter: progid:DXImageTransform.Microsoft.Alpha(style = 0, opacity = 50);
7、边距重叠问题;当相邻两个元素都设置了margin 边距时,margin 将取最大值,舍弃最小值; 解决方案:为了不让边重叠,可以给子元素增加一个父级元素,并设置父级元素为overflow:hidden;
8、cursor:hand 显示手型在safari 上不支持 解决方案:统一使用 cursor:pointer;
9、两个块级元素,父元素设置了overflow:auto;子元素设置了position:relative ;且高度大于父元素,在IE6、IE7会被隐藏而不是溢出; 解决方案:父级元素设置position:relative;
- 使用 rem 单位
- 使用媒体查询
- 使用 flex 结合百分比布局
media: 媒体查询 screen :计算机屏幕 All :默认,适合所有设备
max(min)-width :规定目标显示区域的宽度
css合并写法: @media screen and (min-width:xxxpx) {}
- 流式布局,盒子自上而下排列。
- 弹性布局,flex 弹性盒。
- grid 网格布局。
- 自适应(响应式布局),使用 rem 单位。
- src 是 source 的缩写,表示将外部资源加载到文档内
- href 用于在当前文档和引用资源之间确认联系(侧重点)
- href 会在页面加载时同时加载,src 会在页面加载完加载。
(1)@import是 CSS 提供的语法规则,只有导入样式表的作用;link是HTML提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等。
(2)加载页面时,link标签引入的 CSS 被同时加载;@import引入的 CSS 将在页面加载完毕后被加载。
opacity属性的值,可以被它的子元素继承,所有内容透明度都会改变
rgba设置的元素,只对这个元素的背景色有改变,并且,这个元素的后代不会继承这个属性
1. 使用锚点,页面顶部放置一个锚点链接,然后在页面下方放置一个返回到该锚点的链接,用户点击该链接即可返回到该锚点所在的顶部位置。
//锚点方式
回到顶部
2. 监听浏览器的 scollTop 事件,当用户滚动到页面底部时,出现回到顶部按钮,点击之后重置浏览器滚动条的高度;
//兼容性获取并监听浏览器滚动条
var osTop=document.documentElement.scrollTop|| document.body.scrollTop;
document.documentElement.scrollTop=document.body.scrollTop = 0;
3.第三方插件,例如 jquery 中:
$("html,body").animate({ srollTop: 0 }, 500);
- flex 是一种 css 盒子的布局方式,通过改变父元素的属性来控制子元素的排列方式,其常用属性有:
- flex-direction 主轴的方向
- flex-wrap 子元素空间不足时候是否换行
- justify-content 主轴对齐方式
- align-items 交叉轴的对齐方式
- align-content 多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用
- flex:1 是 flex-grow、flex-shrink、flex-basis 三个属性的缩写,一般默认为 flex:1 1 auto(存在剩余空间就放大,空间不足就缩小);自动填满剩余空间
- flex-grow:元素的放大比例,默认为 1,即使存在剩余空间,也不会放大;
- flex-shrink:元素的缩小比例,默认为 1,即如果空间不足,该元素将缩小;
- flex-basis:项目占据的主轴空间,默认为 auto,即项目原本的大小。
visibility: hidden----将元素隐藏,但是在网页中该占的位置还是占着
display: none----将元素的显示设为无,即在网页中不占任何的位置
关于这个问题一定要去搜集一下资料,好好的准备一下
1、 如何设置,都需要设置什么
content="width=device-width, initial-scale=1.0"
2、如何禁止双指缩放
移动端开发时、给页面头部添加一个meta标签、在标签内添加上 user-scalable = no,initial-scale=1,maximum-scale=1, minimum-scale=1, 四个属性即可(代码如下:)
但有一些移动浏览器为了更好的用户体验、并没有遵循这个开发者禁止双指缩放的指定,比如:Safari、UC、QQ浏览器也就是说设置这四个属性已经实现不了这个功能了、这是可以通过 **touchmove** 事件判断多个手指(**touches.length**),并通过阻止事件冒泡 **event.preventDefault()** 来实现 (代码如下:)
document.documentElement.addEventListener('touchmove', function(event) {
if (event.touches.length > 1) {
event.preventDefault();
}
}, false);
但是在我们多次双指操作之后、还是会突破限制按照用户的意愿进行缩放、所以要通过web代码完全实现禁止双指缩放、暂时还不能实现。
手机设备上看到的 1px 比实际的宽度高 (由于不同的手机有不同的像素密度导致的)
(1)一般是采用伪元素模拟的方式,原理:`把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。`
.scale {
position: relative;
border: none;
}
.scale:after {
content: "";
position: absolute;
bottom: 0;
background: #000;
width: 100%;
height: 1px;
transform: scaleY(0.5);
transform-origin: 0 0;
}
(2)可以使用 box-shadow 来模拟边框
absolute:绝对定位,基于离自己最近的一个非默认定位的元素进行定位 完全脱离标准文档流
relative:相对定位,相对于自身应该出现的位置进行偏移
fixed:固定定位,针对浏览器进行定位
默认定位,,由左到右,从上往下流式布局
transition 表示过渡,表示一个元素从一种状态到另一种状态所需要的时间;
1. transition 需要事件触发,所以没法在网页加载时自动发生。
2. transition 是一次性的,不能重复发生,除非一再触发。
3. transition 只能定义开始状态和结束状态,不能定义中间状态,也就是说只有两个状态。
animate 表示动画;
1. 动画不需要事件触发。
2. 动画可以通过设置 infinite 来循环播放。
3. 可以通过 animation-duration 来设置动画的持续时间,animation-delay 来设置动画开始前的时间等等。
//创建名为myfirst的动画
@keyframes myfirst
{
from {background: red;}
to {background: yellow;}
}
//执行动画
keyframes myfirst
{
from {background: red;}
to {background: yellow;}
}
b{
font-size .12rem
font-weight 400
transform scale(0.8)
}
Sass和Less都属于CSS预处理器
Less环境较Sass简单,Less基于JavaScript,Less使用较Sass简单
从功能出发,Sass较Less略强大一些 有函数,变量和作用域
Less与Sass处理机制不一样,前者是通过客户端处理的,后者是通过服务端处理,相比较之下前者解析会比后者慢一点;
float、absolute、fixed
- 用来声明文档类型,告知浏览器应该以何种规范来解析
- 这叫 W3C 标准,如果不加浏览器会用自己的规范去解析文档,可能在不同浏览器会造成不同的效果
- 渐进增强,写样式的时候从低版本的浏览器开始兼容,从下向上兼容。
- 优雅降级,构建页面的时候,先不考虑兼容问题,先构建出完整的页面,再由上向下进行兼容。
#组件化
就是基础库或者基础组件,意思是把代码重复的部分提炼出一个个组件供给功能使用
#模块化
就是业务框架或者业务模块,也可以理解为框架,意思是把功能进行划分,将同一类型的代码整合在一起,所以模块的功能相对复杂,都属于同一个业务。
#区别:
使用:组件的使用能在不同项目(模块)重复应用的代码,而模块按照项目功能需求划分成不同类型的业务框架 目的:组件是复用,解耦,模块是为了隔离、封装 依赖:组件之间低依赖,比较独立,模块之间的依赖可通过路由进行耦合 架构定位:组件位于架构底层,被其它层所依赖,模块位于架构业务层
模块化开发:指文件的组织、管理、使用的方式。即把一个大的文件按类型拆分成几个小的文件,他们之间相互引用、依赖
优点:1.可以加速渲染页面,所有资源加载的时间不会因为模块化而加速,但是模块化能加速渲染 2.避免命名冲突 3.代码重用高 4.思路更为清晰,降低代码耦合;
冒泡排序、选择排序、快速排序,二分法排序
ie、火狐、谷歌
360 两种模式,极速和兼容
如有不同见解,欢迎讨论,更多内容,正在更新中。。。。。。