前端语法+基础知识


javascript、ES6查漏补缺网址

链接)

知识点

组件运行流程

export default function App() {
  const [state, setState] = React.useState(0);
  
  useEffect(() => {
    console.log("第一次render时执行");
  }, []);
  useEffect(() => {
    if (state > 0) {
      // console.log("第二次之后render时执行");
      console.log('第 %i 次之后render时执行',state);
    }
  }, [state]);
  useLayoutEffect(() => {
    console.log("我比useEffect先执行");
  });
  console.log("我是前面的打印语句");
  return (
    <div className="App">
      <h1>{state}</h1>
      <button
        onClick={() => {
          setState((x) => x + 1);
        }}
      >
        按钮+1
      </button>
    </div>
  );
}

未点击按钮

点击一次

从上面我们可以看出执行顺序是这样:
外部打印语句(console.log) -> useLayoutEffect() -> render() -> useEffect()

  1. 当组件出现变动,这个顺序会重来一遍。

  2. 外部的console.log不论是放在useLayoutEffect()前后,都是优先输出的。

TS类型

详见TS与JS区别

箭头函数

总结
在来看一遍 箭头函数 与 普通函数,除了写法上的区别

1、箭头函数没有自己的this。箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值。
2、箭头函数 this不可变。call()、apply()、bind()、这些方法也 无法改变 箭头函数 this 的指向。
3、箭头函数 不能用 new关键字来实例化对象,不然会报错。
4、箭头函数没有arguments对象。

可以看出,最重要的区别还是 在 this 上,所以要想用好 箭头函数,还是要对 this 有一定认识的。

箭头函数本质还是函数,我们来看看他与JavaScript中普通函数的区别,先看看写法上的区别。

解释
写箭头函数,我们记住一个顺序就好,参数、箭头、函数体、这个顺序记住就足够了,参数、箭头、函数体、这三个是必须的,函数名可以没有,但这三项必须有,一些简写的方式也是简写这三项里的东西。

简写
1、只有一个参数时,() 可省略

//不简写
var demo = (x) =>{
    console.log(x);
}

//简写
var demo = x =>{
    console.log(x);
}

2、函数体只有一句时, {} 可以省略

//不简写
var demo = (x) =>{
    console.log(x);
}

//简写
var demo = x => console.log(x);

3、函数体只有一条返回语句时,{} 和 return 都可以省略

//不简写
var demo = (x) => {
     return x;
}

//简写
var demo = (x) => x;   

//注意别写成这样  
var demo = (x) =>{ x };  
//或者 这样  
var demo = (x) => return  x;  
//要省略就都省略,不省略就都不省,别省一半,不然会出错的。

注意:
箭头函数放 参数 的地方就在 () 内,
没有参数,() 必须写,
一个参数,() 可写可不写,
多个参数,() 必须写。

箭头函数放 函数体 的地方在 {}内,
函数体 就 一句 {} 可写可不写,
函数体 不止一句,{} 必须写。

如果不知道,() {} 写不写,该不该省略,那就写,写了不会错。

箭头函数 如果要返回一个对象,要简写的话, 需要用()包住这个对象

//不简写
var demo = () =>{ 
    return {x:1};
}  

//简写
var demo = () =>({x:1});

为什么会这样?因为如果不加 () ,那{ } 就表示的是语法块,不是表示一个对象,而加上(),按照规范来说,() 里面 { } 就会被解析为对象了。

对于 {x:1} 这个情况,他不仅可以表示一个对象,这个对象有个x属性,值为1,也可以表示为语法块中含有 名为 x 的 label,忘记 label语法的话,可以看这里
如果不是很明白,可以看看这个回答,应该会理解的更加深刻。
所以这也解释了为什么会出现下面代码中的情况

// 不报错
var demo = () =>{x:1};

// 报错
var demo = (y) =>{y,x:1};

对象的方法用 箭头函数写时,this 的指向 可能和你想的不一样

window.name='window';
var obj = {
    name:'obj',
    show_name:() =>{
        console.log(this.name);
    }    
}
obj.show_name(); //window

JavaScript使用的是函数作用域,在上面这段代码中对象的括号是不能封闭作用域的,所以此时的this还是指向window。
我们换成普通函数看看

window.name='window';
var obj = {
    name:'obj',
    show_name: function (){
        console.log(this.name);
    }    
}
obj.show_name();  //obj

换成普通函数,this 就不是指向window,而是指向 obj 对象了

箭头函数 与 普通函数 其他的区别

1、箭头函数没有自己的this。箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值。
2、箭头函数 this不可变。call()、apply()、bind()、这些方法也 无法改变 箭头函数 this 的指向。
3、箭头函数 不能用 new关键字来实例化对象,不然会报错。
4、箭头函数没有arguments对象。

1、箭头函数没有自己的this。箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值。

window.name = 'window';
var obj = {
    name:'obj',
    show_name:function (){
        function fn (){
            console.log(this.name);
        }
        fn();
    },
}
obj.show_name();  // window

声明一个 obj 对象,有一个name属性 与 show_name方法,上面这段代码,我的本意是想显示 obj对象的name, 但是没和我想的一样,一般我们会用 一个变量 self 或者 that 之类的留住this,像这样

window.name = 'window';
var obj = {
    name:'obj',
    show_name:function (){
        //留住this
        var that = this;
        function fn (){
            console.log(that.name);
        }
        fn();
    },
}
obj.show_name();  //obj

通常来说,箭头函数内部的this就是外层代码块的this

window.name = 'window';
var obj = {
    name:'obj',
    show_name:function (){
        var fn = () => {
            console.log(this.name);
        }
        fn();
    },
}
obj.show_name(); //obj

2、箭头函数 this 不可变。call()、apply()、bind()、这些方法也 无法改变 箭头函数 this 的指向。

window.name = 'window';
var obj = {
    name:'obj',
}
function show_name(){
    //这里 show_name 是一个普通的全局函数,所以他的this指window
    console.log(this.name);
}
//用了 call 方法,把 show_nam 的this 指向了 obj 对象
show_name.call(obj);  //obj

箭头函数 this 不可变

window.name = 'window';
var obj = {
    name:'obj',
}
var show_name = () => {
    //这里 show_name 是箭头函数,他的this指window,并且不会变
    console.log(this.name);
}
//用了 call 方法,但是 this 没变,所以打印了 window
show_name.call(obj);  //window

3、箭头函数 不能用 new 关键字来实例化对象,不然会报错,箭头函数的this 不可变,new 也改变不了 this的 指向,而且更为重要的是,箭头函数内部并没有 [[Construct]] 方法,所以会没有原型属性(prototype),所以箭头函数没法当构造函数。


4、箭头函数没有arguments对象,不能通过arguments对象访问传入参数,但是可以用rest参数实现
不了解的看这里

var demo = (...theArgs) => theArgs;
demo(1,2,3); //[1,2,3]

链判断运算符

作用:当访问一个对象属性是用来判断对 象 是 否 存 在
为什么要判断?????
在项目中如果你不去判断对象是否存在在去访问属性,项目就崩了

在es5要这样判断

const userName = Obj &&
	Obj.userInfo && 
	Obj.userInfo.userName ;//这样判断是不是很繁琐

es6写法

const userName = Obj?.userInfo?.userName;  //对比上面少了一大串

在es5的是否我们经常使用”||” 来设置默认值

let a = a.name || 'default'

es6提供了null判断符 ? ? 两个问号来判断,只有当左边的值为null、undefined右边的值才会生效

let a = undefined ?? 'default'
let a2 = null ?? 'default';
console.log(a); //default
console.log(a2); //default

扩展运算符-三个点

对象的扩展运算

对象中的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中

let bar = { a: 1, b: 2 };
let baz = { ...bar }; // { a: 1, b: 2 }

上述方法实际上等价于:

let bar = { a: 1, b: 2 };
let baz = Object.assign({}, bar); // { a: 1, b: 2 }

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。

同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

let bar = {a: 1, b: 2};
let baz = {...bar, ...{a:2, b: 4}};  // {a: 2, b: 4}

利用上述特性就可以很方便的修改对象的部分属性。在redux中的reducer函数规定必须是一个纯函数(如果不是很清楚什么是纯函数的可以参考这里),reducer中的state对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。

这里有点需要注意的是扩展运算符对对象实例的拷贝属于一种浅拷贝。肯定有人要问什么是浅拷贝?我们知道javascript中有两种数据类型,分别是基础数据类型和引用数据类型。基础数据类型是按值访问的,常见的基础数据类型有Number、String、Boolean、Null、Undefined,这类变量的拷贝的时候会完整的复制一份;引用数据类型比如Array,在拷贝的时候拷贝的是对象的引用,当原对象发生变化的时候,拷贝对象也跟着变化,比如:

let obj1 = { a: 1, b: 2};
let obj2 = { ...obj1, b: '2-edited'};
console.log(obj1); // {a: 1, b: 2}
console.log(obj2); //  {a: 1, b: "2-edited"}

上面这个例子扩展运算符拷贝的对象是基础数据类型,因此对obj2的修改并不会影响obj1,如果改成这样:

let obj1 = { a: 1, b: 2, c: {nickName: 'd'}};
let obj2 = { ...obj1};
obj2.c.nickName = 'd-edited';
console.log(obj1); // {a: 1, b: 2, c: {nickName: 'd-edited'}}
console.log(obj2); // {a: 1, b: 2, c: {nickName: 'd-edited'}}

这里可以看到,对obj2的修改影响到了被拷贝对象obj1,原因上面已经说了,因为obj1中的对象c是一个引用数据类型,拷贝的时候拷贝的是对象的引用。

数组的扩展运算

1.可以将数组转换为参数序列

function add(x, y) {
  return x + y;
}

const numbers = [4, 38];
add(...numbers) // 42

2.可以复制数组

如果直接通过下列的方式进行数组复制是不可取的:

const arr1 = [1, 2];
const arr2 = arr1;
arr2[0] = 2;
arr1 // [2, 2]

原因上面已经介绍过,用扩展运算符就很方便:

const arr1 = [1, 2];
const arr2 = [...arr1];

还是记住那句话:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。

3.扩展运算符可以与解构赋值结合起来,用于生成数组

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

const [...rest, last] = [1, 2, 3, 4, 5];
// 报错
const [first, ...rest, last] = [1, 2, 3, 4, 5];
// 报错

扩展运算符还可以将字符串转为真正的数组

[...'hello']
// [ "h", "e", "l", "l", "o" ]

比较常见的应用是可以将某些数据结构转为数组,比如:

// arguments对象
function foo() {
  const args = [...arguments];
}

控制台输出为输出内容为[object Object]

原文链接

解决方法:删掉其它字符(’+’这种符号),直接输出对象

问题原因:

  1. 加号的作用
    首先,我们的代码中有+(加号)运算符,它在这种情况下(字符串 + 其它什么东西),会调用toString()方法,将其它类型的东西转化为字符串,再和原始字符串拼接成一个字符串;
  2. toString()从哪里来,干了什么?
  • 除了null和undefined之外,其他的类型(数值、布尔、字符串、对象)都有toString()方法,它返回相应值的字符串表现(并不修改原变量)。
  • 每个对象都有一个toString()方法。
  • 当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。
  • 默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString()返回 “[object type]”,其中type是对象的类型。

而在上述项目中,res正是我们自定义的对象,所以res.toString()的结果为[object Object],所以console.log(‘res:’ + res)的结果为res: [object Object]。

常用操作

父子组件相互传递数据

原链接

  1. 创建父组件index
    import React, { useState } from "react";
    import { Input } from 'antd'
    import ChildComponent from "./ChildComponent"; 
    export default () => {
      const [inputValue1, setInputValue] = useState<string>('传递的第一个参数')
      return (
        <div>
          <div>
            <h2>父组件</h2>
            <Input style={{ width: '200px' }} placeholder='请输入内容' value={inputValue1} onChange={(e) => setInputValue(e.target.value)} />
          </div>
          <ChildComponent inputValue1={inputValue1}/>  //向子组件传递了一个inputValue1
       </div>
      );
    };
  2. 创建子组件ChildComponent
    import React, { useState } from "react";
    import { Button } from "antd";
    export default (props: any) => {
      return (
        <div>
          <h2>子组件</h2>
          <p>inputValue1:{props.inputValue1}</p>  //通过props拿到了父组件传递的inputValue1
        </div>
      );
    };
  3. 父组件向子组件传值
    父组件向子组件传值时,先将需要传递的值传递给子组件,然后在子组件中,使用props来接收父组件传递过来的值,具体的可看创建父子组件的代码。

父组件将inputValue1传递给子组件:

<ChildComponent inputValue1={inputValue1} />

子组件通过props接收inputValue1:
<p>inputValue1:{props.inputValue1}</p>

  1. 子组件向父组件传值
    子组件向父组件传值时,需要通过触发方法来传递给父组件

父组件定义一个方法:

const childClickFunc = (value: any) => {
    //通过触发方法改变父组件的值   value即为子组件传递给父组件的值
    setInputValue(value) 
  }

<ChildComponent inputValue1={inputValue1} childClick={childClickFunc} />     //定义一个childClickFunc方法

子组件触发父组件方法:

<Button onClick={() => props.childClick('子组件改变父组件的inputValue')}>点击改变父组件的值</Button> //通过props触发父组件传递的方法

  1. 父组件向子组件传递多个值的写法
    法一:
    <ChildComponent inputValue1={inputValue1} inputValue2={inputValue2} inputValue3={inputValue3} childClick={childClickFunc} />
    法二:
    <ChildComponent {...{ inputValue1, inputValue2, inputValue3 }} childClick={childClickFunc} />

数组

forEach 遍历

forEach 接收一个函数作为参数,在遍历每一项的时候,这个函数会被调用,同时将当前遍历到的项、当前项的下标(索引)、遍历的数组作为函数参数传递过来。

var arr = ['第一项', '第二项', '第三项', '第四项', '第五项'];

arr.forEach(function(item, index, arr) {
  console.log('第' + (index + 1) + '项的值是:' + item);
});

map

原文链接

let numbers = [1, 5, 10, 15];
let doubles = numbers.map((x) => {
   return x * 2;
});

// doubles is now [2, 10, 20, 30]
// numbers is still [1, 5, 10, 15]


let numbers = [1, 4, 9];
let roots = numbers.map(Math.sqrt);

// roots is now [1, 2, 3]
// numbers is still [1, 4, 9]

语法

let array = arr.map(function callback(currentValue, index, array) { 
    // Return element for new_array 
}[, thisArg])
//currentValue,数组中正在处理的当前元素。
//index,数组中正在处理的当前元素的索引。
//array,map 方法被调用的数组。
//thisArg可选的。执行 callback 函数时 使用的this 值。
//返回值:一个新数组,每个元素都是回调函数的结果。

描述
map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。

callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身。

如果 thisArg 参数有值,则每次 callback 函数被调用的时候,this 都会指向 thisArg 参数上的这个对象。如果省略了 thisArg 参数,或者赋值为 null 或 undefined,则 this 指向全局对象 。

map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组)。

使用 map 方法处理数组时,数组元素的范围是在 callback 方法第一次调用之前就已经确定了。在 map 方法执行的过程中:原数组中新增加的元素将不会被 callback 访问到;若已经存在的元素被改变或删除了,则它们的传递到 callback 的值是 map 方法遍历到它们的那一时刻的值;而被删除的元素将不会被访问到。

更多用法见原文

增减数组项

慕课网javascript

Arrays.from

先说下类数组
类数组并不是数组,而是长得像数组的对象。

var fakeArray = {
  0: '第一项',
  1: '第二项',
  3: '第三项',
  length: 3,
};

console.log(fakeArray[0]); // 输出:"第一项"
console.log(fakeArray.length); // 输出:3

但是我们不能直接使用数组的方法,需要先把类数组转化为数组,用的就是Arrays.from

功能:

  1. 类数组转化为数组
  2. 从字符串生成数组
  3. 从Set生成数组
  4. 从Map生成数组

具体使用见慕课网ES6

判断数组是否为空

var arr = [];
if(Array.isArray(arr) && arr.length === 0){
    console.log('是空数组');
}

数组是否存在某个值

详细内容见原文

  1. array.indexOf
    判断数组中是否存在某个值,如果存在返回数组元素的下标,否则返回-1

    let arr = ['something', 'anything', 'nothing', 'anything'];
    let index = arr.indexOf('nothing');
    # 结果:2
    
  2. array.includes(searchElement[, fromIndex])

    let numbers = [12, 5, 8, 130, 44];
    let result = numbers.includes(8);
    # 结果: true
    result = numbers.includes(118);
    # 结果: false
    

对象

判断对象是否为空

  1. 1.通过JSON自带的stringify()方法来判断

       function isEmptyObj(obj) {
        return JSON.stringify(obj) === '{}'
        }
    console.log('对象是否为空:', isEmptyObj({}))
  2. 使用ES6的Object.keys()方法

    function isEmptyObj(obj) {
        return Object.keys(obj).length === 0
    }
    console.log('对象是否为空:', isEmptyObj({}))
  3. for in 循环判断

    function isEmptyObj(obj) {
        for(let item in obj) {
            return true
        }
        return false
    }    
    console.log('对象是否为空:', isEmptyObj({}))

Object.create() 创建实例对象

原文见网道
构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()方法。

var person1 = {
  name: '张三',
  age: 38,
  greeting: function() {
    console.log('Hi! I\'m ' + this.name + '.');
  }
};

var person2 = Object.create(person1);

person2.name // 张三
person2.greeting() // Hi! I'm 张三.

上面代码中,对象person1是person2的模板,后者继承了前者的属性和方法。

console.log() 技巧

原文链接

  1. 打印全变量名
    变量加上大括号,要表示值和变量之间关系,可以用花括号把变量包起来:{b}
    console.log({b})
  2. 格式化
    const user = '前端小智';
    const attempts = 5;
     
    console.log('%s 登录失败了 %i 次', user, attempts);
    // 前端小智 登录失败了 5 次

异步

同步、异步

同步 行为通常指代码从上到下一行一行的顺序执行,后面的代码总是在前面的代码执行完成以后才会执行。

let a, b;
function foo() {
    a = 1;
}
foo();
b = a + 1;
console.log(b); //2

异步 行为则指代码并非按照顺序执行,后面的代码不一定总是在前面的代码执行完成以后才会执行。

let a, b;
function foo() {
    a = 1;
}
setTimeout(foo, 1000); //1 秒以后再调用 foo()
b = a + 1;
console.log(b); //NaN

  1. 这里没有显式的调用 foo(),而是将函数名 foo 传递给 setTimeout,JavaScript 运行时在 1 秒以后会自动调用 foo()。

  2. 首先代码依然顺序执行,当执行到 setTimeout(foo, 1000); 时,JavaScript 主线程发现这是一个将要异步执行的任务,就会将 foo() 放入 任务队列 然后继续执行下面的同步代码 b = a + 1;,当 b = a + 1; 执行完毕,所有同步代码都被执行完成,此时 JavaScript 主线程再去 任务队列 中取出需要执行的任务来执行,也就是 1 秒后执行 foo()。

  3. 因为 b = a + 1; 先于 foo() 执行,所以这段异步操作执行后,变量 b 最终结果为 NaN。

异步 操作示例中,setTimeout(foo, 1000); 这句代码中的 foo 就可以被称作 回调函数。所谓 回调函数,就是被主线程放入到 任务队列 中的代码,这段代码通常以函数为单位,并且等到所有 同步 代码执行完成以后才会被执行。

解决办法:
所以上面的 异步 操作示例程序如果想得到与 同步 操作一样的结果,就得改成这样:

let a, b;
function foo() {
    a = 1;
    b = a + 1;
    console.log(b); //2
}
setTimeout(foo, 1000); //1 秒以后再调用 foo()

因为计算 b 的值时依赖 a 的值,而 a = 1; 是在 回调函数 中执行的,也就是说所有 同步 代码执行完成以后才会执行 回调函数 中的 异步 代码,所以 b = a + 1; 也要移动到 回调函数 中。回调函数 中的代码也是顺序执行的,所以 b = a + 1; 语句要放在 a = 1; 语句之后。这样就能使变量 b 最终结果为 2

promise

原文链接

var isLiForget = false; //给一个布尔值判断小丽有没有忘记小花的生日
var getCloth = new Promise(function(resolve,reject){
    if(!isLiForget){ //没忘记
        var cloth = {
            color:'red',
            price:'$120'
        };
        resolve(cloth); // 得到衣服
    }else{
        var err = new Error("forgot the promise"); //忘记了
        reject(err);
    }
});
 
//之后就是调用Promise了
var testFn = function(){
    getCloth.then(function(fulfilled){
        console.log(fulfilled);
    }).catch(function(rejected){
        console.log(rejected.message);
    });
}
testFn();

对于promise的理解

  1. resolve、reject会选择一个执行,这两个是调用这个对象的时候传过来的函数
  2. 以上面代码为例。resolve(cloth)。resolve就是外面传过来的函数,其实就是``` function(fulfilled){
     console.log(fulfilled); 
     
    3. 要调用或者获取promise里面的值,需要用到then。写发有点区别,不过逻辑就是正确执行这个函数,错误执行那个函数。具体可以看链接。
    
    ### async/await取值(很重要!!)
    
    ![t2.png](https://s2.loli.net/2022/06/17/e6wAUrRa3HQzdbV.png)
    
    
    ![t3.png](https://s2.loli.net/2022/06/17/9pqnMu5TcPIDGli.png)
    
    **我要把opts这个值传给组件里面的option属性,opts是一个数组,数组的值如下图所示**
    
    
    ![t1.png](https://s2.loli.net/2022/06/17/1w6SBlZx39PAgpK.png)
    
    
    之前的做法是把值赋给一个变量,然后传给需要用的地方,但是行不通,估计是异步的原因,**state**可以解决这个问题。
    
    **解决方法**
    **用useState定义一个变量(Options),然后用更新函数接(serOptions)收新的值,再把这个变量放到需要的位置(option属性)就好。**
    
    
    ### async/await
    [原链接](https://blog.csdn.net/qq_46658751/article/details/123373909)
    #### async作用
     函数返回的是一个 Promise 对象,即便是字符串,数值,也会包装成Promise对象
    async function testAsync() {
    return “hello async”;
    }

const result = testAsync();
console.log(result);

![](https://cdn.jsdelivr.net/gh/gaofeng-lin/picture_bed/img/bd22592cd9fc45138b6cd8a70cbd50c6.png)

#### await在等待什么
await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值

#### await等到要等的结果,之后呢?
async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

### 回调函数

#### 用途

**用途:一般情况下回调函数都用在页面与服务器的异步交互,以解决页面在提交请求后无法获得到服务器反馈的数据**

为什么会出现面在提交请求后无法获得到服务器反馈的数据呢?首先来了解js的事件执行方式,如图
![](https://cdn.jsdelivr.net/gh/gaofeng-lin/picture_bed/img/2020102020575826.png)

js在执行程序时,所有代码都在执行栈中,此时都是同步在执行,但在运行代码时如果遇见事件绑定、计时器会将抛到异步任务队列里面等待js引擎执行,然后继续执行在执行栈中的代码,于此同时,被抛到异步执行队列里面的计时器会执行等待的时间,如果等待时间结束,则会将此事件抛到执行队列里面。执行栈里面的代码执行完成后,程序会去检查执行队列里面是否有事件,如果有,则拿出来执行。如果没有,则会一直循环检查,直到异步事件队列和执行队列里面都没有才结束程序


#### 回调示例:

var fs = require(“fs”);

function f(x) {
console.log(x)
}

function writeFile(callback) { //callback,表示这个参数不是一个普通变量,而是一个函数
fs.writeFile(‘input.txt’, ‘我是通过fs.writeFile 写入文件的内容’, function (err) {
if (!err) {
console.log(“文件写入完毕!”)
c = 1
callback(c) // 因为我们传进来的函数名是f(),所以此行相当于调用一次f(c)
}
});
}
var c = 0
writeFile(f) // 函数f作为一个参数传进writeFile函数

1. 此处并不一定非要写为“callback”,你可以任意写成abc, iloveyou...等等随你高兴。callback只是一种约定俗成的写法,它明确地告诉代码阅读者:此处是一个回调函数。
2. 在大多数编程语言中,函数的形参总是从外向内传递参数,但在JS中,如果形参碰到“关键字” callback 则完全相反,它表示从内向外反向调用某个外部函数。


有时候,我们会看到一些函数的形参列表里直接嵌套一个函数的情况,其本质上仍然是回调函数,因为没有了函数名,所以也称**匿名函数**。

如本例如果要写成这种风格的话就是长成这样了:

var fs = require(“fs”);

function writeFile(callback) {
fs.writeFile(‘input.txt’, ‘我是通过fs.writeFile 写入文件的内容’, function (err) {
if (!err) {
console.log(“文件写入完毕!”)
c = 1
callback(c)
}
});
}
var c = 0
writeFile(function (x) {
console.log(x)
})



### 获取异步函数返回值的方法

在项目中会遇到通过ajax、setTimeout等异步操作向后台获取数据,而js自身不会等待数据拿到后再进行下一步操作,所以会导致其他函数无法获取异步操作函数内的数据。例如:

function back(x,y){
var ret;
setTimeout(function(){
ret = x + y;
},1000)
return ret;
};

console.log(back(10,20)); //undefined



1. 回调函数

function back(x,y,callback){
setTimeout(function(){
var ret = x + y
callback(ret)
},1000)
}

back(10,20,function(a){
//在这里面就可以进行对数据的操作了

console.log(a)    //a就是回调的结果,输出30

})

```
function getSomething(cb) {
    var r = 0;
    setTimeout(function() {
        r = 2;
        cb(r)//回调函数
    },10)
}
function compute(x) {
    alert(x * 2);
}
getSomething(compute);

  1. 通过promise的方式

    function getSomething() {
        var r = 0;
        return new Promise(function(resolve) {
            setTimeout(function(){
                r = 2;
                resolve(r);
            },10)
        })
    }
    function compute(x) {
        alert(x * 2);
    }
    getSomething().then(compute);
  2. 通过async的方式

    function getSomething() {
        var r = 0;
        return new Promise(function(resolve) {
            setTimeout(function() {
                r = 2;
                resolve(r);
            },10)
        })
    }
    async function compute() {
        var x = await getSomething();
        alert(x * 2);
    }
    compute();
  3. 通过generator方式实现

    function getSomething() {
        var r = 0;
        setTimeout(function() {
            r = 2;
            it.next(r);
        },10);
    }
    function *compute(it) {
        var x = yield getSomething();
        alert(x * 2);
    }
    var it = compute();
    it.next();
    //同步的写法实现异步的逻辑
  4. 通过promise和generator相结合的方式

    function getSomething() {
        var r = 0;
        return new Promise(function(resolve) {
            setTimeout(function() {
                r = 2;
                resolve(r);
            },10)
        })
    }
    function *compute() {
        var x = yield getSomething();
        alert(x * 2);
    }
    var it = compute();
    it.next().value.then(function(value) {
        it.next(value);
    })

Ajax

AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”
实现前后端数据的交互

优点

1.Ajax最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。 (这一特点给用户的感受是在不知不觉中完成请求和响应过程) 2.Ajax不需要任何浏览器插件,但需要用户允许javaScript在浏览器上执行。
同步交互: 客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求。

异步交互: 客户端发出一个请求后,无需要等待服务器响应结束,就可以发出第二个请求。

Ajax使用JavaScript技术向服务器发送异步请求

Ajax请求无须刷新整个页面

因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以Ajax性能高!

工作原理

JSX

原链接)

JSX是什么

JSX是一种像下面这样的语法:

const element = <h1>Hello, world!</h1>;

它是一种JavaScript语法扩展,在React中可以方便地用来描述UI。
质上,JSX为我们提供了创建React元素方法(React.createElement(component, props, …children))的语法糖(syntactic sugar)。上面的代码实质上等价于:

var element = React.createElement(
  "h1",
  null,
  "Hello, world!"
);

在JSX中使用JavaScript表达式

在JSX中插入JavaScript表达式十分简单,直接在JSX中将JS表达式用大括号括起来即可。例如:

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

上面的代码中用到了函数调用表达式fromatName(user)。

在JavaScript中,表达式就是一个短语,Javascript解释器会将其计算出一个结果,常量就是最简单的一类表达式。常用的表达式有:

  • 变量名;
  • 函数定义表达式;
  • 属性访问表达式;
  • 函数调用表达式;
  • 算数表达式;
  • 关系表达式;
  • 逻辑表达式;

需要注意的是,if语句以及for循环不是JavaScript表达式,不能直接作为表达式写在{}中,但可以先将其赋值给一个变量(变量是一个JavaScript表达式):

function NumberDescriber(props) {
  let description;
  if (props.number % 2 == 0) {
    description = <strong>even</strong>;
  } else {
    description = <i>odd</i>;
  }
  return <div>{props.number} is an {description} number</div>;
}

JSX属性值

你可以使用引号将字符串字面量指定为属性值:
const element = <div tabIndex="0"></div>;
注意这里的”0”是一个字符串字面量。

或者你可以将一个JavaScript表达式嵌在一个大括号中作为属性值:
const element = <img src={user.avatarUrl}></img>;

这里用到的是JavaScript属性访问表达式,上面的代码将编译为:

const element = React.createElement("img", { src: user.avatarUrl });

JSX的Children

首先JSX可以是一个不包含Children的empty tag。如:

const element = <img src={user.avatarUrl} />;

JSX也可以像HTML标签一样包含Children:

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

后面一部分不是很重要,可看原文
React自定义组件的chilren是不会像固有的HTML标签的子元素那样自动render的,我们看下面的例子:

代码1
class Test extends React.Component {
    render() {
      return (
        <div>
          Here is a list:
          <ul>
            <li>Item 1</li>
            <li>Item 2</li>
          </ul>
        </div>
      ) 
    }
};
ReactDOM.render(
    <Test />,
    document.getElementById('test')
);

以上代码定义的组件中都是build-in组件,类似div、p、ul、li等。它们中的子元素会直接render出来,像下面这样:

但是如果你使用用户定义组件,比如:

class Test extends React.Component {
    render() {
      return (
      <Em>
        Here is a list:
        <ul>
          <li>Item 1</li>
          <li>Item 2</li>
        </ul>
      </Em>
    ) 
    }
};

class Em extends React.Component {
  render() {
    return (<div></div>);
  }
}

ReactDOM.render(
    <Test />,
    document.getElementById('test')
);

并不能得到跟上面代码1一样的结果,我们得到的只是一个空的div标签:

如果你想得到和代码1一样的结果,需要显示地指定props.children,像下面这样:

class Test extends React.Component {
    render() {
      return (
          <Em>
            Here is a list:
            <ul>
              <li>Item 1</li>
              <li>Item 2</li>
            </ul>
          </Em>
      ) 
    }
};

class Em extends React.Component {
  render() {
    return (<div>{this.props.children}</div>);
  }
}

ReactDOM.render(
    <Test />,
    document.getElementById('test')
);

JSX中的props

指定JSX中的props有以下几种方式:

  1. 使用JavaScript表达式

任何有效的JavaScript表达式都可以作为prop的值,使用的时候将该表达式放在一对大括号中即可:

<MyComponent foo={1 + 2 + 3 + 4} />

<YourComponent clickTodo={(id) => this.props.handleClick(id)} />
  1. 使用字符串字面量
    字符串字面量可以作为prop值,下面的代码是等价的:
<MyComponent message="hello world" />

<MyComponent message={'hello world'} />
  1. 使用扩展运算符
    如果你想将一个prop对象传入JSX,你可以使用扩展运算符…直接将整个prop对象传入。下面的2个组件是等价的:
function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}

扩展运算符是一个es6特性。是一种传递属性的十分便利的方式。但请注意不要滥用该运算符,注意不要将一大堆毫不相关的prop一股脑全部传入下面的组件中。

JSX中的Children

React组件中有一个特殊的prop–props.children。它指代了JSX表达式中开闭标签中包含的内容。

下面讨论的是几种指定JSX的children的方法:

(1)使用字符串字面量
(2)JSX元素作为children
(3)JavaScript表达式
(4)函数children

注意事项

(1)使用JSX时要引入React库
(2)注意引入JSX中用到的自定义组件
(3)自定义组件首字母一定要大写
(4)元素标签名不能使用表达式
(5)设置style属性

在设置标签style属性的时候,要注意,我们是将一个描述style的对象以JavaScipt表达式的形式传入。因此应该有2层大括号:

<div style={{color:'red', margin:'10px auto'}}></div>

判断条件

在react 组件return语句里面如果要使用判断语句,像下面这样。

{judgeSchema(project)? (
        <div>
          <Form1 schema={projtestParaHYPara}></Form1>
        </div>
    ):
      null
    }

Dom

Dom简介

DOM (Document Object Model,文档对象模型) 是 W3C 制定的访问、操作 HTML 和XML的标准。

DOM 节点是契合HTML标准的,可以对其细分:

整个文档是一个文档节点
HTML 元素内的文本是文本节点
每个 HTML 元素是元素节点
每个 HTML 属性是属性节点
注释为注释节点

获取与操作Dom

详细的可以看慕课网

获取 DOM 节点的方式有很多,这里例举几个常用的,所有的 DOM 元素都具有以下方法:

element.getElementById
element.getElementByName
element.getElementsByTagName
element.getElementsByClassName
element.querySelector
element.querySelectorAll

Npm

查看包信息

方案一:
直接看package.json,项目相关的都在里面

方案二:

查看全局已安装(-g 的意思是 global 全局的意思)

$ npm ls -g

查看当前项目已安装包(项目跟目录必须有 package.json 文件)

$ npm ls

查看包本版

npm view <packagename> versions --json

同样也是会把所有包的依赖显示出来。同上,加上 —depth 0 就好了。

$ npm ls --depth 0

如果只想显示生产环境依赖的包

$ npm ls --depth 0 --prod

只显示开发环境依赖的包

$ npm ls --depth 0 --dev

npm 与 npx

在npm的基础之上,npx让npm包中的命令行工具和其他可执行文件在使用上变得更加简单。它极大地简化了我们之前使用纯粹的npm时所需要的大量步骤。

区别1.一个永久存在,一个临时安装,用完后删除

npm install -g create-react-app
create-react-app test-app
npx create-react-app test-app

npm他会在本地全局性的安装create-react-app,这个包会存储在node目录下面去。以后创建react项目直接执行create-react-app命令就可以了。
npx命令他会把create-react-app安装包临时安装上,等项目初始化完成以后,他就删除掉。

npx 会帮你执行依赖包里的二进制文件

执行webpack里面的文件 npm执行

npm i -D webpack ./node_modules/.bin/webpack -v
结果是:6.9.0

或者执行
npm i -D webpack`npm bin`/webpack -v

npx执行
npm i -D webpack
npx webpack -v

npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去环境变量里面的 PATH 里找。如果依然找不到,就会帮你安装!

区别3.npx可以执行文件,但是npm不可以

npm与yarn

命令对比

原文链接

“Yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具 ,正如官方文档中写的,Yarn 是为了弥补 npm 的一些缺陷而出现的。”这句话让我想起了使用npm时的坑了:

  • npm install的时候巨慢。特别是新的项目拉下来要等半天,删除node_modules,重新install的时候依旧如此。
  • 同一个项目,安装的时候无法保持一致性。由于package.json文件中版本号的特点,下面三个版本号在安装的时候代表不同的含义。
"5.0.3",
"~5.0.3",
"^5.0.3"

“5.0.3”表示安装指定的5.0.3版本,“~5.0.3”表示安装5.0.X中最新的版本,“^5.0.3”表示安装5.X.X中最新的版本。这就麻烦了,常常会出现同一个项目,有的同事是OK的,有的同事会由于安装的版本不一致出现bug。

  • 安装的时候,包会在同一时间下载和安装,中途某个时候,一个包抛出了一个错误,但是npm会继续下载和安装包。因为npm会把所有的日志输出到终端,有关错误包的错误信息就会在一大堆npm打印的警告中丢失掉,并且你甚至永远不会注意到实际发生的错误。

Yarn的优点
速度快 。速度快主要来自以下两个方面:
并行安装:无论 npm 还是 Yarn 在执行包的安装时,都会执行一系列任务。npm 是按照队列执行每个 package,也就是说必须要等到当前 package 安装完成之后,才能继续后面的安装。而 Yarn 是同步执行所有任务,提高了性能。
离线模式:如果之前已经安装过一个软件包,用Yarn再次安装时之间从缓存中获取,就不用像npm那样再从网络下载了。
安装版本统一:为了防止拉取到不同的版本,Yarn 有一个锁定文件 (lock file) 记录了被确切安装上的模块的版本号。每次只要新增了一个模块,Yarn 就会创建(或更新)yarn.lock 这个文件。这么做就保证了,每一次拉取同一个项目依赖时,使用的都是一样的模块版本。npm 其实也有办法实现处处使用相同版本的 packages,但需要开发者执行 npm shrinkwrap 命令。这个命令将会生成一个锁定文件,在执行 npm install 的时候,该锁定文件会先被读取,和 Yarn 读取 yarn.lock 文件一个道理。npm 和 Yarn 两者的不同之处在于,Yarn 默认会生成这样的锁定文件,而 npm 要通过 shrinkwrap 命令生成 npm-shrinkwrap.json 文件,只有当这个文件存在的时候,packages 版本信息才会被记录和更新。
更简洁的输出:npm 的输出信息比较冗长。在执行 npm install 的时候,命令行里会不断地打印出所有被安装上的依赖。相比之下,Yarn 简洁太多:默认情况下,结合了 emoji直观且直接地打印出必要的信息,也提供了一些命令供开发者查询额外的安装信息。
多注册来源处理:所有的依赖包,不管他被不同的库间接关联引用多少次,安装这个包时,只会从一个注册来源去装,要么是 npm 要么是 bower, 防止出现混乱不一致。
更好的语义化: yarn改变了一些npm命令的名称,比如 yarn add/remove,感觉上比 npm 原本的 install/uninstall 要更清晰。

跨域

原文链接

什么是跨域

当一个请求url的协议,域名,端口三者之间任意一个与当前的url不同都即为跨域

 当前页面url                  被请求页面url                是否跨域            原因
http://www.test.com/    http://www.test.com/index.html    否            同源(协议、域名、端口号相同)
http://www.test.com/    https://www.test.com/index.html   跨域          协议不同(http/https)
http://www.test.com/    http://www.baidu.com/             跨域          主域名不同(test/baidu)
http://www.test.com/    http://blog.test.com/             跨域          子域名不同(www/blog)

为什么会出现跨域

出于浏览器的同源策略限制.同源策略是一种约定,它是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,则浏览器的正常的功能可能会受到影响,跨域收是Web是构建在同源策略基础上的,浏览器只是针对同源策略的一种实现,同源策略会阻止一个域的JavaScript脚本和另一个域的内容进行交互,所谓同源(即指同一个域)就是两个页面具备同样的协议(protocol),主机(host)和端口号(port)

请求是跨域的,并不一定会报错。普通的图片请求,css文件请求是不会报错的

跨域会阻止什么操作

浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom的查询

1.阻止接口请求

比如用ajax从http://192.168.100.150:8020/实验/jsonp.html页面向http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp发起请求,由于两个url端口不同,所以属于跨域,在console打印台会报No ‘Access-Control-Allow-Origin’ header is present on the requested resource

值得说的是虽然浏览器禁止用户对请求返回数据的显示和操作,但浏览器确实是去请求了,如果服务器没有做限制的话会返回数据的,在调试模式的network中可以看到返回状态为200,且可看到返回数据

2.阻止dom获取和操作

比如a页面中嵌入了iframe,src为不同源的b页面,则在a中无法操作b中的dom,也没有办法改变b中dom中的css样式。

而如果ab是同源的话是可以获取并操作的。

<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<style type="text/css">
			iframe{
				width:100%;height:800px;
			}
		</style>
	</head>
	<body>
		<!--<iframe src="http://192.168.100.150:8081/zhxZone/webmana/attachment/imageManager" frameborder="0" id="iframe"></iframe>-->
		<iframe src="http://192.168.100.150:8020/实验/jsonp.html" frameborder="0" id="iframe"></iframe>
		<script type="text/javascript">
			var i=document.getElementById("iframe");
			i.onload=function(){
				/*console.log(i.contentDocument)
				console.log(i.contentWindow.document.getElementById("text").innerHTML)*/
				var b=i.contentWindow.document.getElementsByTagName("body")[0];
				i.contentWindow.document.getElementById("text").style.background="gray";
				i.contentWindow.document.getElementById("text").innerHTML="111";
			}
		</script>
	</body>
</html>

改变了iframe中的元素

甚至是可以获取iframe中的cookie

var i=document.getElementById("iframe");
i.onload=function(){
	console.log(i.contentDocument.cookie)
}

不用说也知道这是极为危险的,所以浏览器才会阻止非同源操作dom

浏览器的这个限制虽然不能保证完全安全,但是会增加攻击的困难性

虽然安全机制挺好,可以抵御坏人入侵,但有时我们自己需要跨域请求接口数据或者操作自己的dom,也被浏览器阻止了,所以就需要跨域

跨域的前提肯定是你和服务器是一伙的,你可以控制服务器返回的数据,否则跨域是无法完成的

解决跨域的方法

前端jsp

CORS方案

就是通过服务器设置响应头来实现跨域

CORS才是解决跨域的真正解决方案。
前端需要做什么?
无需做任何事情,正常发送Ajax请求即可。
后端需要做什么?
需要加 响应头 。或者使用第三方模块 cors 。

代理服务器(一般用Nginx)


1).代理服务和前端服务之间由于协议域名端口三者统一不存在跨域的问题,可以直接发送请求

2).代理服务和后端服务之间并不通过浏览器没有同源策略的限制,可以直接发送请求

这里是一个nginx启用COSR的参考配置

#
# Wide-open CORS config for nginx
#
location / {
     if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        #
        # Custom headers and headers various browsers *should* be OK with but aren't
        #
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        #
        # Tell client that this pre-flight info is valid for 20 days
        #
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     if ($request_method = 'POST') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
     }
     if ($request_method = 'GET') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
     }
}

调试方法

快捷键:

F8:进入下一个断点
F10:单步执行,不进入子函数
F11:单步执行,遇到子函数会进入子函数
shift+F11:跳出当前函数

文章作者: Jason Lin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 reprint policy. If reproduced, please indicate source Jason Lin !
  目录