前端CSS、Javascript等知识点总结(持续更新)

537 阅读12分钟

这是我参与更文挑战的第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-wrapflex 子项是否换行 nowrap | wrap | wrap-reverse
4flex-flow 复合属性(flex-direction flex-wrap5、指定主轴对齐方式 justify-contentflex-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、子项排列顺序,数值越小,越靠前,可以为负数 order1 | ...
2、某一子项对齐方式 align-self:auto | flex-start | flex-end | center | baseline | stretch
3、子项分配剩余空间 flex-grow0(默认值:不扩展) | 123...
4、子项收缩比例 flex-shrink1(默认值:表示所有子项在剩余空间为负数时,等比例收缩)
5、子项基础占据空间 flex-basis:auto(默认值) | 长度/宽度 | 百分比 | content  flex-growflex-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

类型转换

转布尔值:
只有false0、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)}

类数组和数组相互转换

类数组=>数组
1Array.prototype.slice.call(arguments)或者[].slice.call(arguments)
2Array.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])
  }
}
3Number
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
负数:将正数10互换再加上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、无法拷贝函数。

内存泄漏

虽然浏览器会有垃圾回收机制当某块无用的内存,却无法被垃圾回收机制认为是垃圾时,也就发生内存泄漏了

www.cnblogs.com/dasusu/p/12…

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协议的基本过程是这样的:客户端向服务器端索要并验证公钥=> 双方协商生成"对话密钥"=> 双方采用"对话密钥"进行加密通信。前两步,又称为"握手阶段"。

其他

URL、URI、URN