0%

设计思路:用父盒子包含四个子盒子,父盒子使用相对定位,子盒子使用绝对定位,此时这四个盒子会全部定位在父盒子的左上角,统一对四个子盒子设置关键帧动画,使得子盒子会围绕父盒子四个角依次移动,然后将四个盒子的动画延迟时间(即animaiton-delay属性)设置为总动画时间的0(第一个不用设置),-1/4s, -2/4s, -3/4s,这里设置为负数为了让在显示的时候,其他三个圆提前位于父盒子的其余角位置。

props-render模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import img from './logo512.png' //图片是不能从public文件夹里面导入的,只能从src里面导入
import PropoTypes from "prop-types"
//使用render-props和高阶组件实现组件的复用
//复用组件首先要获取组件的状态和操作组件的方法
//问题1:如何拿到该组件复用的state
//可以通过添加一个值为函数的props,通过函数参数来获取(需要组件内部实现)
//问题2:如何渲染任意的UI===》使用该函数的返回值作为要渲染的内容

class Mouse extends React.Component{
state = {
x: 0,
y: 0,
}

handleMouseMove=(e)=>{
this.setState({
x: e.clientX,
y: e.clientY
})
}

componentDidMount(){
window.addEventListener('mousemove',this.handleMouseMove)
}

componentWillUnmount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}

render(){
return this.props.render(this.state)
}
}

Mouse.PropoTypes={
render: PropoTypes.func.isRequired
}

class App extends React.Component {
render(){
return(
<div>
<p>props模式</p>
{/* <Mouse/> */}
<Mouse render={(mouse)=>{ return(

<p>当前坐标为{mouse.x}:{mouse.y}</p>
)}

}/>
<Mouse render={(mouse)=>{
return <img src={img} alt="猫"
style={{position: "absolute",
top: mouse.y, left: mouse.x,
transform: 'translate(-50% ,-50%)',
width:"200px",
height:"200px"
}}/>
}} />
</div>
)
}
}

ReactDOM.render(
<App/>,
document.getElementById('root')
);

Mouse组件的render属性必须是一个函数,且可以用childre属性来代替,这样的话可以将函数直接作为Mouse组件的一个文本节点(文本节点拥有props.children,而不用作为Mouse的属性。

高阶组件

使用步骤:

1、创建一个函数,名称约定以with开头;

2、指定函数参数,参数应该以大写字母开头(作为要渲染的组件)

3、在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回

4、在该组件中,渲染参数组件,同时将状态通过props传递给参数组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import img from './logo512.png'

function withMouse(WrappedComponent){
class Mouse extends React.Component{
state={
x: 0,
y: 0,
}

handleMouseMove=(e)=>{
this.setState({
x : e.clientX,
y : e.clientY
})
}

componentDidMount(){
window.addEventListener('mousemove', this.handleMouseMove)
}

componentWillMount(){
window.removeEventListener('mousemove', this.handleMouseMove)
}


render(){
return (
<WrappedComponent {...this.state} {...this.props}></WrappedComponent>
)
}
}

return Mouse;

}

const Position = props=>
<p>
鼠标当前的位置:x:{props.x} ,y:{props.y}
</p>

const catPosition = props=>(
<img src = {img} alt="猫" style={{
position: "absolute",
top: props.y,
left: props.x,
width: "100px",
height: "100px",
transform: 'translate(-50% ,-50%)',

}} />
)



let Mouseposition = withMouse(Position);
let CatPosition = withMouse(catPosition)

class App extends React.Component {
render(){
return(
<div>
<p>高阶组件</p>
<Mouseposition />
<CatPosition></CatPosition>
</div>
)
}
}

ReactDOM.render(
<App/>,
document.getElementById('root')
);

组件的生命周期可分为挂载、更新和卸载三个阶段,其中还包含有对错误的处理

挂载

当组件实例被创建并插入到DOM中,其生命周期调用顺序如下:

1
2
constructor()
在react组件挂载之前,会调用它的构造函数,构造函数有两种用途:1、通过给this.state赋值来初始化内部的state 2、为事件处理函数绑定实例
1
2
static getDrivedStateFromProps()
会在render()方法被调用之前被调用,并且载初始化挂载及后续更新时都会被调用。它返回一个对象来更新state,如果返回null则不更新任何内容
1
2
3
render()
是class组件中唯一必须实现的方法
当它被调用的时候,会检查this.props和this.state的变化并返回react元素
1
2
componentDidMount()
会在组件挂载后(插入DOM树中)立即调用,依赖DOM节点的初始化应该放在这里,比如说通过网络请求数据,另外对DOM节点的操作,添加订阅也放这里

更新

当组件的props和state发生改变时会出发更新。组件更新的生命周期调用顺序如下:

1
static getDerivedStateFromProps()
1
2
shouldCompnentUpdate()
可根据该方法的返回值来判断react组件的输出是否受当前state或props更改的影响,默认行为是state每次发生改变时组件都会重新渲染
1
render()
1
getSnapshotBeforeUpdate()
1
2
3
4
5
6
7
8
9
componentDidUpdate()
//当组件更新后,可以在此处对Dom进行操作,如果对更新前前后的props进行了比较,也可以选择在这里进行网络请求(例如,当props未发生改变的时候,不会执行网络请求)
componentDidUpdate(prevProps){
if(this.props.userID !== prevProps.userID){
this.fetchData(this.props.userID)
}
}

//如果想要在这个函数里面调用setState(),则也要将它放在条件语句里,要不然会导则死循环。

卸载

当组件从Dom中移除时会调用以下方法:

1
2
componentWillUnmount()
会在组件卸载及销毁之前直接调用。可用于清除定时器、取消网络请求或清除在componentDidMount()中创建的订阅。

错误处理

渲染过程、生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

static getDrivedStateFromError()

componentDidCatch()

一、两栏布局,右侧自适应

方法一:使用flex布局

将box父盒子的display属性设置为flex,左栏的宽度按需设计,右栏添加flex:1属性

关于flex:1的解释

flex :1 是flex-grow:1 flex-shrink: 1 flex-basis:0的缩写。

flex-grow:1 将自身的宽度扩大2倍,如果其余的同级盒子没有设置该属性,最后一个盒子如果设置该属性后则会自动填满父盒子。

flex-shrink:1 将自身的宽度缩小为原来的二分之一,如果第一个子盒子的宽度特别大,超出的父盒子的宽度,剩余盒子设置flex-shrink:1 属性之后,会自动缩小自身的宽度来适应前面的盒子宽度。

flex-basis:0(这个属性不是很明白能干嘛)

方法二:使用float布局

1、使用flaot左浮左边栏

2、右边栏用margin-left隔开

3、为父元素田间BFC,防止下方的元素飞到上方内容

关于BFC的解释

BFC是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素

一个HTML元素要创建BFC,则满足下列的任意一个或多个条件即可:

1、float的值不是none。
2、position的值不是static或者relative。
3、display的值是inline-block、table-cell、flex、table-caption或者inline-flex
4、overflow的值不是visible

创建一个BFC的通常使用 overflow:hidden来创建

应用场景

1、属于同一BFC的两个相邻块元素的margin会发生重叠,要避免这种情况,可以新创建一个BFC,让这两个相邻块元素处于不同的BFC;

2、当父盒子的宽度没有设置的时候,设置父盒子的背景颜色是无法显示的,即使父盒子里面有子盒子占据空间,这时候给父盒子设置一个BFC,父盒子就可以显示了。

3、对于浮动元素,可能会造成文字环绕的情况(Figure1),但这并不是我们想要的布局(Figure2才是想要的)。要解决这个问题,我们可以用外边距,但也可以用BFC。

4、在多列布局中使用BFC

如果我们创建一个占满整个容器宽度的多列布局,在某些浏览器中最后一列有时候会掉到下一行。这可能是因为浏览器四舍五入了列宽从而所有列的总宽度会超出容器。但如果我们在多列布局中的最后一列里创建一个新的BFC,它将总是占据其他列先占位完毕后剩下的空间。

如果没有在最后一个设置BFC,则最后一个会掉下来落在第一个盒子的下面。

二、三栏布局中间自适应

方法一:使用flex布局

为父盒子添加display:flex,设置左右两边盒子的宽度,中间盒子设置flex:1或者width:100%即可架构剩下的空间填满。

方法二: 两边使用float,中间使用margin

注意:中间的盒子要放在最后面,否则右边的盒子会掉下来

缺陷:右边在主题内容之前,如果是响应式布局,则不能实现简单的换行展示。

方法三:两边使用绝对定位,中间使用margin

和flaot布局相似

方法四: 使用grid布局

一、扩展运算符

核心概念:将数组变成参数序列

1、可用于函数调用时将一个数组变成参数序列

1
2
3
function push(array, ...item){
return array.push(...item);
}

2.将某些数据结构转化为数组

1
[...document.querySelectorAll('div')]

3、数组的合并

1
2
3
4
let arr1=[2,3,4,5];
let arr2 = [4,5,6,7];
let arr=[...arr1, ...arr2];
console.log(arr); //[2, 3, 4, 5,4, 5, 6, 7]

4、与解构赋值结合起来使用

注意:扩展运算符自能放在参数的最后一个位置,否则会报错

1
2
3
4
const [first, ...rest] = [1,2,3,4,5,6];
console.log(first); //1
console.log(rest);//[ 2, 3, 4, 5, 6 ]
console.log(...rest); // 2 3 4 5 6

5、将字符串转化为数组

1
2
3
console.log(...'javascript'); // j a v a s c r i p t
console.log([...'javascript']);//[ 'j', 'a', 'v', 'a', 's', 'c', 'r', 'i','p', 't']
相当于字符串的split函数(将字符串分割成字符串数组)

可见如果扩展运算符后面不是数组而是字符串的时候也可以正常扩展

二、数组的构造函数新增方法

1、Array.from

将类似数组的对象和可遍历的对象(iterable)包括Set和Map转化成数组

1
2
3
4
5
6
7
let arrayLike = {
'0': 'first',
'1': 'second',
'2':'third',
length: 3
}
console.log(Array.from(arrayLike)); //[ 'first', 'second', 'third' ]

注意,如果key从0开始,如果key值得存在,则生成的数组对应的index处的值为undefined

2.Array.of()

将一组值转换为数组

1
2
3
4
console.log(Array(2,3,4)); //[ 2, 3, 4 ]
console.log(Array.of(2,3,4,4)); //[ 2, 3, 4, 4 ]
console.log(Array.of(3)); //[ 3 ]
console.log(Array(3)); //[ <3 empty items> ] 包含3个空值的数组

三、实例对象新增方法

  • copyWith()

  • find()
  • findindex
  • fill()
  • enteries()
  • values()
  • keys()
  • includes()
  • flat()
  • flatMap()

1.copyWithin()

将指定位置的数组元素复制到其他的位置,返回覆盖后的新数组

1
2
3
let arr= [3,4,9,5,6,7];
let result = arr.copyWithin(0, 2, 3);
console.log(result); //[ 9, 4, 9, 5, 6, 7 ]

第一个参数表示要开始替换的数组索引号,第二个参数表示从那个位置开始复制数组,最后一个表示那个位置结束(但是不包括这个位置的值)

2.find()

返回第一个符合条件的数组元素,find的参数是一个函数,函数参数可包含(value,index,array)

3.findIndex()

返回第一个符合条件的数组元素对应得索引号,如果所有的元素都不符合条件,则返回-1.

4.fill()

将给定值作为函数的参数,覆盖原有数组的值,可接受第二个和第三个参数,表示覆盖的初始位置和终始位置(不包含终始位置)

1
2
3
4
5
let arr=[1,2,3,4];
arr.fill(2,0,2);
console.log(arr); //[ 2, 2, 3, 4 ]
arr.fill(3);
console.log(arr); //[ 3, 3, 3, 3 ]

5、flat()

将数组扁平化处理,返回一个新数组,对原数组没有影响

1
2
3
4
5
6
7
8

let arr = [3,4,[4,5,[4,2,2]]];
//flat()参数默认值为1,只处理一层
console.log(arr); //[ 3, 4, [ 4, 5, [ 4, 2, 2 ] ] ] 不改变原数组
console.log(arr.flat()); //[ 3, 4, 4, 5, [ 4, 2, 2 ] ]

处理两层
console.log(arr.flat(2)); //[3, 4, 4, 5,4, 2, 2]

flatMap()

对每个数组成员执行map方法,然后再将返回的结果执行flat方法

1
2
3
let arrs = [3,3,4,6,7,8];
let newarr = arrs.flatMap(value=>[value, value*3]);
console.log(newarr); [3, 9, 3, 9, 4,12, 6, 18, 7, 21,8, 24]

TolocalTimeString()

根据本地时间(电脑)把Date对象的时间部分转换成字符串

1
2
var n=new Date();
var time=n.toLocalTimeString(); //下午2:29:45

可以使用setinterval 实现时间的实时显示。

fill()方法

使用固定值来填充数组

1
2
3
4
5
let food=['1', '2', '3'];

food.fill('rice')

此时food=['rice','rice','rice']

apply、call和bind的区别

1、相同点:

这三个的作用就是改变他们前面的函数执行时的上下文,即改变函数运行时的this指向,第一个参数都表示是this的指向,在this为null或者为undefined的时候,this默认指向window。

2、不同点:

apply的第二个参数是函数执行时传入的参数,apply时以数组的方式传入,一次传多个参数,只传一次,且函数是立即执行。

call 的第二个参数在函数执行的时候以参数列表的形式传入,一次传多个参数,只传一次,函数时立即执行的。

bind的第二个参数是动态传递参数,可以分多次传,但不会立即执行, 且会返回一个函数。

1
2
3
fn.bind(obj,1,2);
或者
fn.bind(obj,1)(2)

实现apply、call和bind

简单说一下他们的实现原理,fn.call(o) 它的实现原理就是o上定义一个新的属性m,m的值为要执行的这个函数fn,

即 :o.m=fn; 执行这个函数 fn(); 最后删除m这个属性。

那么这个函数是怎么获取呢,可以通过this来获取,this指向这个调用的函数fn

Function.prototype.apply()

1
2
3
4
5
6
7
8
9
10
Function.protoptype.apply=function(context=window, args){
if(typeof this!='function'){
throw new TypeError('type errror'); //因为this要指向apply前面要调用的函数,所以必须是一个函数
}
const fn = Symbol('fn');//保证fn作为属性时的唯一性
context[fn] =this;//this获取调用apply的函数(this会指向调用它的对象,所以会获取到到调用函数),fn是执行上下文的一个属性,这样能够保证fn的作用域是在context中的,从而实现传入context的绑定。
let result = contetx[fn](..args);//将参数传进去然后执行
delete contet[fn]; //删除新建的属性;
return result;//将调用的结果返回回去
}

Function.prototype.call()

1
2
3
4
5
6
7
8
9
10
11
12
//传入的参数是参数离列表
Function.prototype.call = function(context,=window, ...args){
if(typeof this !="function"){
throw TypeError('type error!');
}

const fn = Symbolp('fn');//此属性不可更改
context[fn]= this;
let result = context[fn](..args);
delete context[fn];
return result;
}

Function.prototype.bind()

1

以前对通过new一个构造函数生成实例对象不是很理解,今天终于弄明白了,嘿嘿。

首先要明白实例对象生成的过程中它经过了哪些步骤。

1、它既然是一个对象,所以要先创建一个空对象。

1
const obj={}

2.实例对象可以使用构造函数原型上的属性和方法,所以要将实例对象的__proto__指向构造函数的原型

1
obj.__proto__ =Func.prototype;

3、实例对象可以访问到构造函数的属性,所以要将构造函数的this绑定到实例对象上,让this.name 变成obj.name(举个栗子)

1
let result = Func.apply(obj, args) //args为构造函数的参数

现在这个空对象就可以访问到构造函数的构造函数原型上的所有属性和方法。

1
2
3
4
5
6

result={
__proto__ = Person.prototype,
name='Tom',
age =30
} //感觉这地方表述有点问题???

4、构造函数没有return语句,则需要把新创建的对象给返回回去(这里补充一个知识点,构造函数的返回值如果是原始类型的值,则直接被忽略,如果返回的是对象,则创建的实例对象具有的属性是返回对象的属性,并不具有构造函数没有返回的属性),所以我们要把所有的属性给返回回去;

1
return result instaneOf Object ? result: obj;

最后,附上完整new 函数代码

1
2
3
4
5
6
7
function myNew(Func, ...args){
const obj={};
//为什么这一句要在绑定this的前面呢,因为构造函数原型上的方法可能会用到this,这样可以把this都找出来,后面统一绑定
obj.__proto__ = Func.prototype;
let result = Func.apply(obj,args)
return result instanceof Object ? result: obj;
}

获取表单

1
documen.forms.my;  //my是form的name属性值

获取表单里的元素

form.element.one; //one为表单元素中的name属性值

也可以直接通过form.name直接获取

通过元素反向引用表单

1
name.form  // name为元素的name属性值

表单控件

input 和textarea

我们可以通过 input.value(字符串)或 input.checked(布尔值)来访问复选框(checkbox)中的它们的 value

select和option

select 有三个重要的属性:options、value、selectedIndex

  1. select.options ——
  2. select.value —— 当前所选择的
  3. select.selectedIndex —— 当前所选择的
1
2
3
4
5
6
7
8
9
10
11
12
<select id="select">
<option value="apple">Apple</option>
<option value="pear">Pear</option>
<option value="banana">Banana</option>
</select>

<script>
// 所有这三行做的是同一件事=>将Banan设置为预选中状态
select.options[2].selected = true;
select.selectedIndex = 2;
select.value = 'banana';
</script>

表单的focus和blur事件

focus事件可用于再input中输入内容时使用,blur事件用于鼠标离开input,并点击了页面的其他地方。

tabindex属性

tabindex属性可以将不支持聚焦得元素变成可聚焦的元素,它的值为可以为-1 0 ,1,2,3。。。

且在按tab键去遍历的时候,tabindex的优先级会大于可聚焦元素(input a button),tabindex为0和聚焦元素同级。

label标签的 for 属性

for 属性规定 label 与哪个表单元素绑定。 for属性的值就是要绑定元素的id。

在html中,

JSdocument.execCommand 实现复制功能

点击button,复制input的内容

1
2
input.select(); //select() 方法用于选取文本域中的内容。
document.execCommand('copy');

箭头函数只有行的时候如果要加上{}则必须用return返回

赋值和对象的转换

1
2
3
4
let i=1;
console.log({i});
//输出结果
{i:1}

value和innerHtml的使用场景

对于表单控件(输入类),都可以用value属性,对于非表单控件(div, span ,em),可以用innerHTML;

input标签都有value属性,但都没有innerHTML属性,只能用value;

SELECT标签和OPTION标签,即有value属性也有innerHTML属性,但是一个是取回值,一个是取回文本,这两个可能相同也可能不同,具体要看你想要哪个值.

TEXTAREA标签也没有innerHTML属性,有value属性和innerText属性.

padStart 和padEnd函数

可用于自动编号的补0操作

1
2
3
4
let a= '1';
let b = a.padStart(3,'0') //b的值为001,3表示位数,‘0’表示要补充的字符串

padEnd()是在数字的后面补。

验证码输入一个数字后自动转移光标到下一个输入框,按回车键后自动将光标转移到前一个输入框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const inputs = document.querySelectorAll('input');
inputs[0].focus();
inputs.forEach((input ,index) => {
input.addEventListener('keydown', (e)=>{
if(e.key>=0 && e.key<=9){
inputs[index].value =''; //每次输入后将值置空,保证下次输入的时候不会保存原先输入的值。
setTimeout(() => {
if(index<inputs.length-1) //防止所以的验证框输入完毕后,找不到下一个输入框会报错。
inputs[index+1].focus();
}, 10);
}
else if(e.key=='Backspace'){
setTimeout(() => {
inputs[index-1].focus();
}, 10);
}
})
});

将一个数字范围映射到另一个数字范围

学习链接: https://stackoverflow.com/questions/10756313/javascript-jquery-map-a-range-of-numbers-to-another-range-of-numbers

1
2
3
4
5
6
7
8
9

function scale (number, inMin, inMax, outMin, outMax) {
return (number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}
使用该函数,如下所示:

const num = 5;
console.log(scale(num, 0, 10, -50, 50)); // 0
console.log(scale(num, -20, 0, -100, 100)); // 150

Location reload()

eload()方法用于刷新当前文档。

reload() 方法类似于你浏览器上的刷新页面按钮。

如果把该方法的参数设置为 true,那么无论文档的最后修改日期是什么,它都会绕过缓存,从服务器上重新下载该文档。这与用户在单击浏览器的刷新按钮时按住 Shift 健的效果是完全一样。

1
<button onclick="location.reload()">Reload</button>

contextmenu事件

当按下鼠标右键时触发

1
2
3
4
li.addEventListener('contextmenu' ,(e)=>{
e.preventDefault(); // preventDefault()可阻止按下右键后浏览器默认的触发事件。
li.remove();
})