这是我参与更文挑战的第1天,活动详情查看: 更文挑战
本文章可能引入其他作者文章而没有备注,因为是把很久之前的知识也纳进去了,所以可能有遗漏文章引用,如果发现了,务必告知,我会贴上文章引用!
CSS
css单位
单位分绝对单位和相对单位
绝对单位:in、cm、mm、pt、pc、px、q、ch
q:1/4毫米
pt:point
pc:Picas
ch:数字0的宽度
960px=720pt=60pc=25.4cm=254mm=1016q=10in
相对单位:相对长度单位是相对于其他单位(元素[]或[]字体的大小)或(视口的大小)
相对单位有:em、rem、ex、ch、vw、vh、vim、vmax、(ic、vi、vb)
em:font-size: 14px => margin-left: 1em(14px)
rem:font-size(html): 14px => margin-left: 1em(14px)
ex:字体小写 x 的高度 通常为字体高度的一半
vmin/vmax:基于vw和vh中的最小值/最大值来计算
html {
font-size: 62.5%; /* 基于默认浏览器字体大小,让 font-size 1em = 10px */
}
什么是BFC?
BFC(Block formatting context)-------"块级格式化上下文"
CSS2.1 中只有B(Block)FC和I(Inline)FC CSS3中还增加了G(grid)FC和F(flex)FC
(Block/Inline/Grid/Flex) fomatting context:是一个渲染区域,决定了内部子元素的定位、关系、相互作用,与这个区域外部毫不相干。
如何生成BFC?
1、根元素
2、float的值不为none
3、overflow的值不为visible
4、display的值为inline-block、table、table-cell、table-caption
5、position的值为absolute或fixed
BFC规定了什么?
1、BFC区域内部的Box会在垂直方向上一个接一个的放置
2、BFC区域内两个相邻Box的margin会发生重叠,以最大的为准
3、BFC区域不会与float的元素区域重叠
4、计算BFC的高度时,浮动子元素也参与计算
flex布局
设置flex对象:
1、开启弹性伸缩盒:display: flex | inline-flex;
2、主轴方向 flex-direction: row | row-reverse | column | column-reverse
3、是否换行 flex-wrap:flex 子项是否换行 nowrap | wrap | wrap-reverse
4、flex-flow 复合属性(flex-direction flex-wrap)
5、指定主轴对齐方式 justify-content:flex-start | flex-end | center | space-between | space-around
6、指定侧轴对齐方式 align-items:stretch | flex-start | flex-end | center | baseline
7、中间间距 align-content:stretch | flex-start | flex-end | center | space-between | space-around
设置flex子项:
1、子项排列顺序,数值越小,越靠前,可以为负数 order:1 | ...
2、某一子项对齐方式 align-self:auto | flex-start | flex-end | center | baseline | stretch
3、子项分配剩余空间 flex-grow:0(默认值:不扩展) | 1、2、3...
4、子项收缩比例 flex-shrink:1(默认值:表示所有子项在剩余空间为负数时,等比例收缩)
5、子项基础占据空间 flex-basis:auto(默认值) | 长度/宽度 | 百分比 | content flex-grow、flex-shrink都是在flex-basis的基础上进行的。
grid布局
容器属性:
1、开启网格布局 display: grid | inline-grid
2、每一列的列宽:grid-template-columns
每一行的行高:grid-template-rows
3、grid-row-gap
less
//值变量
@width: 50%;
#wrap {
width: @width;
}
//选择器变量、属性变量、url 变量
@Wrap: wrap;
@Soild:solid;
@borderStyle: border-style;
.@{Wrap}{ 必须使用大括号包裹
color:#ccc;
@{borderStyle}: @Soild;//变量名 必须使用大括号包裹
}
JS
var
var/let toString = 111; <-------------
let obj = {
show() {console.log(this.toString)}
};
let a = obj.show; a(); //var 111/ let fn(){...}
如果var toString = 111改为let toString = 111 this.toString就是原型上的toString因为var一个变量,会在Window对象上新增一个属性
var a = xxx或者function xxx(){} 都会在全局Window添加对应的属性
暂时性死区
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
编译型语言和解释型语言
编译型语言:在运行前,编译器将编程语言转换成机器语言,在编译后能直接运行。
解释型语言:在执行前需要环境中安装的解释器 在运行时,解释器将编程语言转换成机器语言。
自执行函数
(function(item){})(i)
~/-/+/!function(){}
BOM、DOM
类型转换
转布尔值:
只有false、0、NaN、空字符串、空值、null、undefined为false,其他全为true
转数字:
{…}、[…]、function(){}先调用自己原型上的toString(),再转数字
//[1,2,3,…]:"1,2,3"
//{}: "[object Object]"
//function(){}: "function(){}"
转字符串:调用自己原型上的toString()
typeof
String
'string'
Number、NaN、Infinity
'number'
Boolean
'boolean'
Undefined
'undefined'
Object
'object'
function
'function'
Symbol
'symbol'
bigint
'bigint'
ss是
typeof是通过机器码判断类型,而对象的机器码为000,null的机器码为0,所以就不能够判断区分对象和null和具体对象了。
instanceof
instanceof 主要的实现原理就是只要右边的 prototype 在左边的原型链上即可。
leftVal. __ proto __ . __ proto __ …… === rightVal.prototype?true:false
原型链
防抖
const debounce = (fn, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
};
节流
const throttle = (fn, delay) => {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
};
继承
//call继承、原型链继承、组合继承、extends继承
call继承:Parent.call(this)
原型链继承:
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
组合继承:call继承 + 原型链继承
function Parent () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child() {
//call继承
Parent.call(this);
this.type = 'child';
}
//原型链继承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
//extends继承
class Animal {
//构造函数,里面写上对象的属性
constructor(props) {
this.name = props.name || 'Unknown';
}
eat() {
console.log(this.name + " will eat pests.");
}
}
class Bird extends Animal {
//构造函数
constructor(props,myAttribute) {//props是继承过来的属性,myAttribute是自己的属性
super(props)
this.type = props.type || "Unknown";//父类的属性,也可写在父类中
this.attr = myAttribute;//自己的私有属性
}
fly() {
console.log(this.name + " are friendly to people.");
}
myattr() {
console.log(this.type+'---'+this.attr);
}
}
//通过new实例化
var myBird = new Bird({
name: '黄鹂',
type: 'Egg animal'//卵生动物
},'Bird class')
实现call/apply
//实现apply只要把下一行中的...args换成args即可
function call(context = window, ...args) {
let func = this;
let fn = Symbol("fn");
context[fn] = func;
let res = context[fn](...args);//重点代码
delete context[fn];
return res;
}
this
this的指向只有四种情况:
1、作为函数调用:fn() this指向全局对象,严格模式下事undefined
2、作为方法调用:obj.fn() this指向这个对象
3、作为构造函数调用:new fn() this指向一个新对象
4、特殊调用:fn.call()、fn.apply()、fn.bind() this指向参数指定成员
四种绑定的优先级为:
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
当写一个函数时,就要想到该函数被调用的几种情况:
function fn(){ console.log(this)}
let obj2={
a:1,
fn
}
let obj3={a:1}
fn写在全局环境中:
1、fn() this -> Window
2、fn().call(obj) this -> obj
fn写在对象:
1、obj.fn() this -> obj
2、let a = obj.fn;a() this -> Window
3、obj.fn.call(obj3)/obj2.fn() this -> obj2
function sayHi(){
console.log('Hello,', this.name);
}
var person1 = {
name: 'YvetteLau',
sayHi: function(){
setTimeout(function(){
console.log('Hello,',this.name);
})
}
}
var person2 = {
name: 'Christina',
sayHi: sayHi
}
var name='Wiliam';
person1.sayHi();//Hello, Wiliam
setTimeout的回调函数中,this使用的是默认绑定,非严格模式下,执行的是全局对象
setTimeout(person2.sayHi,100);//Hello, Wiliam
setTimeout(fn,delay){ fn(); },相当于是将person2.sayHi赋值给了一个变量,最后执行了变量,这个时候,sayHi中的this显然和person2就没有关系了
setTimeout(function(){
person2.sayHi();
},200);//Hello, Christina
虽然也是在setTimeout的回调中,但是我们可以看出,这是执行的是person2.sayHi()使用的是隐式绑定,因此这是this指向的是person2,跟当前的作用域没有任何关系。
this <-----------全局Window对象
Window {window: Window, self: Window, document: document, name: "", location: Location, …}
this.__proto__
Window {TEMPORARY: 0, PERSISTENT: 1, Symbol(Symbol.toStringTag): "Window", constructor: ƒ}
this.__proto__.__proto__
WindowProperties {Symbol(Symbol.toStringTag): "WindowProperties"}
this.__proto__.__proto__.__proto__
EventTarget {Symbol(Symbol.toStringTag): "EventTarget", addEventListener: ƒ, dispatchEvent: ƒ, removeEventListener: ƒ, constructor: ƒ}
this.__proto__.__proto__.__proto__.__proto__ <-------顶级Object对象
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
闭包
MDN 对闭包的定义为:闭包是指能够访问自由变量的函数。
(其中自由变量,指在函数中使用的,但既不是函数参数arguments也不是函数的局部变量的变量,其实就是另外一个函数作用域中的变量。)
var a = 1;
//回调函数
function foo(fn){
var a = 2; <------自由变量:既不是函数foo参数arguments,也不是函数fn的局部变量的变量
fn(a);
}
foo((item)=>console.log(item))
//函数返回函数
function foo(){
var a = 2;<------自由变量:既不是函数foo参数arguments,也不是函数fn的局部变量的变量
return ()=>console.log(a)
}
foo()()
arguments
arguments 是一个类数组对象
严格模式下,参数与 arguments 对象没有联系,修改一个值不会改变另一个值。而在非严格模式下,两个会互相影响。
function printArgs() {
console.log(arguments);
}
printArgs("A", "a", 0, { foo: "Hello, arguments" })//["A", "a", 0, Object]类数组对象
arguments 转换成数组:Array.prototype.slice.call(arguments)或者[ ].slice.call(arguments)
参数从一个函数传递到另一个函数的推荐做法:function foo() {bar.apply(this, arguments)}
类数组和数组相互转换
类数组=>数组
1、Array.prototype.slice.call(arguments)或者[].slice.call(arguments)
2、Array.from()
3、[...arguments]
4、[].concat.apply([], arguments)
const obj = { 0: "A", 1: "B", length: 2 };
console.log([].slice.call(obj)); // ["A", "B"]
数组=>类数组
function Foo() {}
Foo.prototype = Object.create(Array.prototype);
const foo = new Foo();
foo.push('A');//["A"]
console.log(foo, foo.length);// 1
console.log(Array.isArray(foo));//false
扩展操作符
function func() {
console.log(...arguments);//1 2 3
}
func(1, 2, 3);
function func(...restArgs) {
console.log(restArgs);//[1, 2, 3]
}
func(1, 2, 3);
[...arr]
实现instanceof
function instanceof(left, right) {
let proto = Object.getPrototypeOf(left);
while(true) {
if(proto == null) return false;
if(proto == right.prototype) return true;
proto = Object.getPrototypeof(proto);
}
}
实现flat(数组扁平化)
let ary = [1, [2, [3, [4, 5]]], 6];
1、正则表达式 JSON.parse('[' + JSON.stringify(ary).replace(/(\[|\])/g, '') + ']')
2、递归
let result = [];
let flat = (ary)=> {
for(let i = 0; i < ary.length; i++) {
Array.isArray(ary[i])? flat(ary[i]):result.push(ary[i])
}
}
3、Number
let flat = (arr)=>arr.reduce((pre,cur)=>pre.concat(Array.isArray(cur)?flat(cur):cur),[])
4、map
ary.join(',').split(',').map(item => Number(item));
数组方法总结
//shift()、unshift()、pop()、push()、splice()、slice()、reverse()、sort()、valueOf()
//valueOf()
相当于数组本身
arr1===arr1.valueOf() //true
let str = 'abcd';
let arr1 = [1,2,3,4];
let arr2 = [{age:12},{age:13},{age:14}]
//findIndex()和indexOf()和lastIndexOf()
str.indexOf('a')//indexOf也是str的方法。
arr1.indexOf(2)
arr2.findIndex((item)=>item.age>13)
都是通过遍历返回符合要求的下标,找到就停止,没有则返回-1。
遍历基本数据类型就用indexOf查询在数组当中的下标。
遍历复杂数据类型就用findIndex查询数组对象中的下标。
//find()和includes()
arr2.find((item)=>item.age>13) //true
[1, 2, 3].includes(3, 3); // false
和indexOf一样,也是只能遍历基本数据的数组
//some()和every()
//reduce()和reduceRight()
//from()
将类数组转成数组的首选方法
var arr = Array.from([1, 2, 3]/类数组/字符串, x => x * 10/每个item执行的函数);
//keys()和values()和entries()
都返回一个数组迭代对象,可以用for…of循环进行遍历。
区别是keys()是对键名的遍历,values()是对键值的遍历,entries()是对键值对的遍历。
let fruits = ["Banana", "Orange", "Apple", "Mango"];
let x = fruits.keys(); //Array Iterator {}
x.next()//{value: 0, done: false}
x.next()//{value: 1, done: false}
...
x.next()//{value: undefined, done: true}
//isArray()
用于判断一个对象是否为数组
Array.isArray(obj)
//fill()
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.fill("Runoob", 2, 4); //Banana,Orange,Runoob,Runoob
fruits.fill("Runoob"); //Runoob,Runoob,Runoob,Runoob
//copyWithin()
用于从数组的指定位置拷贝元素到数组的另一个指定位置中
var fruits = ["Banana", "Orange", "Apple", "Mango", "Kiwi", "Papaya"];
fruits.copyWithin(2, 0, 2);
常用的
forEach()、map()、filter()、concat()、toString()、join()
位运算符
二进制:512 256 64 32 | 16 8 4 2 1
负数:将正数1和0互换再加上1 -9 =>1001=>0110+1=> 1111 1111 1111 1111 1111 1111 1111 0111
按位非(NOT)
~25 => -(25)-1 => -26
~~25 => -(25)-1 => -26 => -(-26)-1 => 25
按位与(AND)
25 & 3 => 11001 & 00011 => 找同时存在1的位数,才取1 => 00001 => 1
按位或(OR)
25 | 3 => 11001 & 00011 => 找存在1的位数,就取1 => 11011 => 27
按位异或(XOR)
25 ^ 3 => 11001 & 00011 => 找存在1的位数,就取1,同时存在,则不取 => 11010 => 26
var a=10,b=9; a ^= b, b ^= a, a ^= b; //a=9,b=10
<<左移
-2<<5 => 2的二进制为10,全部向做移5位 => 0000010-> 1000000 => -64
>>右移
该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃
-9 >> 2 => 1001 -> 10 => -2
>>>有符号右移
正数>>>和>>结果一样,负数>>>数值会很大
取整:
~~3.9 => 3
3.9 | 0 => 3
3.9 ^ 0 => 3
3.9 << 0 => 3
https://blog.csdn.net/u014465934/article/details/91412762
深拷贝和浅拷贝
浅拷贝手动实现:
const shallowClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) cloneTarget[prop] = target[prop];
}
return cloneTarget;
} else {
return target;
}
}
2、Object.assign({}, obj)3、arr.concat()4、arr.slice()5、[...arr]
--------------------------------------------------------------------
深拷贝手动实现:
const deepClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) cloneTarget[prop] = deepClone(target[prop]);
}
return cloneTarget;
} else {
return target;
}
}
还有JSON.parse(JSON.stringify())
缺点:
1、const a = {val:2};a.target = a;出现了无限递归的情况
2、无法拷贝一写特殊的对象,诸如 RegExp, Date, Set, Map等。
3、无法拷贝函数。
内存泄漏
虽然浏览器会有垃圾回收机制当某块无用的内存,却无法被垃圾回收机制认为是垃圾时,也就发生内存泄漏了
webSocket
WebSocket是一种建立在 TCP 协议之上,不存在跨域问题、无http状态的请求、全双工通讯(双向同时通信)网络通信协议,WebSocket可以让服务器将数据主动推送给客户端,浏览器和服务器只需要完成一次握手(采用 HTTP 协议),两者之间就可以建立持久性的连接,并进行双向数据传输。
HTTP协议只能实现客户端请求,服务端响应的这种单项通信、无法实现服务器主动向客户端发起消息,而webSocket使得浏览器具备了实时双向通信的能力。
WebSocket协议标识符是ws(如果加密,则是wss)。
如果我们不使用WebSocket与服务器实时交互,一般有两种方法:AJAX轮询和Long Polling长轮询。
AJAX轮询也就是普通的定时发送请求,无限循环发送,服务端一旦有最新消息,就可以被客户端获取。
Long Polling长轮询是客户端和浏览器保持一个长连接,等服务端有消息返回,断开,然后再重新连接,如果没有数据要返回的话,会停住请求,等到有数据,才返回给客户端。
这两种方式假设并发很高的话,这对服务端是个考验。
//简单实现WebSocket连接
//执行npm init -y
//执行npm i ws express
//创建app.js
//执行node app.js
//app.js(服务器端webSocket)
var app = require('express')();
var server = require('http').Server(app);
var WebSocket = require('ws');
var port = 3000;
var ws = new WebSocket.Server({ port: 8080 });
ws.on('connection', function connection(ws) {
console.log('server: receive connection.');
ws.on('message', function incoming(message) {
console.log('server: received: %s', message);
});
ws.send('world');
});
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
//在浏览器任意页面f12,发起WebSocket请求
var ws = new WebSocket('ws://localhost:8080');
ws.onopen = function () {
console.log('ws onopen');
ws.send('from client: hello');
};
ws.onmessage = function (e) {
console.log('ws onmessage');
console.log('from server: ' + e.data);
};
WebSocket通过TCP三次握手建立连接,握手阶段采用HTTP协议,客户端通过HTTP请求与WebSocket服务端协商升级协议。协议升级完成后,后续的数据交换则遵照WebSocket的协议。
请求头:
-
Connection: Upgrade 表示要升级协议
-
Upgrade: websocket 要升级协议到websocket协议
-
Sec-WebSocket-Version 表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
-
Sec-WebSocket-Key 对应服务端响应头的Sec-WebSocket-Accept,由于没有同源限制,websocket客户端可任意连接支持websocket的服务。这个就相当于一个钥匙一把锁,避免多余的,无意义的连接。
响应头:
Sec-WebSocket-Accept: 用来告知服务器愿意发起一个websocket连接, 值根据客户端请求头的Sec-WebSocket-Key计算出来
WebSocket与TCP、Http的关系
WebSocket与http协议一样都是基于TCP协议,WebSocket和Http协议一样都属于应用层的协议,WebSocket在建立握手连接时,数据是通过http协议传输的,正如我们看到的“GET/chat HTTP/1.1”,这里面用到的只是http协议一些简单的字段。但是在建立连接之后,真正的数据传输阶段是不需要http协议参与的。
具体关系可以参考下图:
Proxy
let target = {
name: 'Tom',
age: 24
}
let handler = {
get:(target, key)=> target[key], // 不是target.key
set(target, key, value) {
target[key] = value;
}
}
let proxy = new Proxy(target, handler)
proxy.name // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
//可继承
let obj = Object.create(proxy);
obj.name // Getting name
Vue
this.$nextTick()
this.$nextTick()是在真实DOM渲染完成后执行。
因为vue的渲染真实DOM是异步的,所以想要获取真实DOM,如果需要获取DOM的相关属性,就要等真实DOM渲染完成后才能拿到最新的DOM,而this.$nextTick()就是在真实DOM渲染完成后才执行的。
v-for和v-if同时用问题
vue2中,v-for比v-if优先级高,先全部遍历出来再去判断
vue3则相反。
vue-router的原理
其核心是更新视图但不重新请求页面。
vue-router中通过mode属性指定模式,根据不同的mode创建不同的history对象,主要有3种模式:
Hash模式(默认)、History模式、abstract模式(node环境下)
相当于创建了new HashHistory(...)、new HTML5History(...)、new AbstractHistory(...)
HashHistory有两个方法:HashHistory.push() 和 HashHistory.replace()
this.$router.push()和this.$router.replace()相当于调用这两个方法。
push:将新路由添加到浏览器访问历史的栈顶
replace:替换掉当前的路由
HTTPS
HTTP之所以不安全,是因为协议属于明文传输协议,通信双方没有进行认证,那么就存在着几个问题:窃听风险(可获知通信内容)、篡改风险(可修改通信内容)、冒充风险(可以冒充他人身份通信)。
而HTTPS是在HTTP基础上加了SSL(安全套接字层)或TLS(安全传输层协议 ,SSL升级版),加密传播,无法窃听、校验机制,一旦被篡改,通信双方会立刻发现、身份证书,防止身份被冒充。
SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。
如何保证公钥不被篡改?
将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。
公钥加密计算量太大,如何减少耗用的时间?
每一次对话,都用对称加密(运算速度非常快)的对话密钥(session key)来对话,而公钥只用于加密"对话密钥"本身,这样就减少了加密运算的消耗时间。
因此,SSL/TLS协议的基本过程是这样的:客户端向服务器端索要并验证公钥=> 双方协商生成"对话密钥"=> 双方采用"对话密钥"进行加密通信。前两步,又称为"握手阶段"。