react学习笔记1-JavaScript基础知识

什么是React

React 用于构建 Web 和原生交互界面的库。

  • 是一个用于构建用户界面的 JAVASCRIPT 库。
  • 主要用于构建 UI,很多人认为 React 是 MVC 中的 V(视图)。
  • React 起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。
  • React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。

特点

  1. 声明式设计 −React采用声明范式,可以轻松描述应用。
  2. 高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
  3. 灵活 −React可以与已知的库或框架很好地配合。
  4. JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
  5. 组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
  6. 单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。

常见的react文件后缀名

  • .js: 这是 JavaScript 文件。这是前端开发的基础,它包含了逻辑和函数等代码。
  • .jsx: 这是 JavaScript XML 的缩写,它允许你在 JavaScript 文件中书写 XML 或 HTML 风格的语法。这在 React 中广泛地应用,因为 React 组件通常就是用 JSX 编写的。
  • .ts: 这是 TypeScript 文件。TypeScript 是 Microsoft 开发的一种静态类型检查的 JavaScript 扩展语言,它可以帮助开发者编写更健壮的代码,并提供了更好的编辑器支持。
  • .tsx: 这是 TypeScript 的 JSX 版本。如果你在 TypeScript 文件中需要书写 JSX,则需要将文件后缀名设置为 .tsx。
  • .css: 这是层叠样式表(Cascading Style Sheets)文件,用于描述 HTML 文档的外观和格式。
  • .less: 这是 LESS 文件,LESS 是一种 CSS 预处理语言,它添加了变量、混合、函数等特性,使得 CSS 更易于维护和扩展。Ant Design 的样式就是用 LESS 编写的。
  • .json: 这是 JSON (JavaScript Object Notation) 文件,它是一种轻量级的数据交换格式,非常适合于数据的存储和交换。
  • .md / .markdown: 这是 Markdown 文件,它是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。


ES6

ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。

ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。


箭头函数

作用

更简洁的函数表达式,并且可以解决 this 关键字在不同环境中可能变化的问题。

JavaScript 中的 this 关键字是特别的,它的值取决于函数如何被调用:

  • 在传统的函数中,this 的值可以变化。
  • 而在箭头函数中,this 是在函数创建时就被绑定的,它的值与离它最近的包围函数(即它的词法环境)的 this 值相同。

举例说明箭头函数是怎么解决this关键字可能变化的问题。

const myObject = {
  property: 'Hello',
  method: function() {
    console.log(this.property);

    setTimeout(function() {
      console.log(this.property);
    }, 1000);
  }
}

myObject.method();

上述代码中的 setTimeout 内部的函数是一个匿名函数,当它被调用时,它的 this 实际上不指向 myObject,而是指向全局对象(在浏览器中为 window)。所以这个代码会先打印出 “Hello”,然后是 undefined

而改用箭头函数呢?

const myObject = {
  property: 'Hello',
  method: function() {
    console.log(this.property);

    setTimeout(() => {
      console.log(this.property);
    }, 1000);
  }
}

myObject.method();

由于箭头函数没有自己的 this,它会从包围它的 method 函数那里获取 this,即 myObject 对象。因此,当 setTimeout 中的箭头函数被调用时,this.property 正确地引用了 myObjectproperty 属性。所以这次会连续打印出两个 “Hello”。

因此,箭头函数通过继承其包围函数的 this 上下文,解决了 this 在不同执行环境中可能变化的问题。

省略规则

一个基本的箭头函数的语法如下:

const functionName = (parameters) => {
  // function body
}

其中 (parameters) 是你要传递给函数的参数列表,=> 是箭头符号,表示这是一个箭头函数,而 {} 中间的部分就是函数体,也就是当调用这个函数时会执行的代码。

箭头函数有一些编写更简洁的省略规则:

  1. 参数省略规则:如果箭头函数只有一个参数,那么可以省略参数两边的括号。例如:
const square = x => {
  return x * x;
}
  1. 函数体省略规则:如果函数体只有一条语句,并且该语句就是返回值,那么可以省略 {} 和 return 关键字。例如:
const square = x => x * x;

其他更多的省略形式:

var elements = ["Hydrogen", "Helium", "Lithium", "Beryllium"];

elements.map(function (element) {
  return element.length;
}); // 返回数组:[8, 6, 7, 9]

// 上面的普通函数可以改写成如下的箭头函数
elements.map((element) => {
  return element.length;
}); // [8, 6, 7, 9]

// 当箭头函数只有一个参数时,可以省略参数的圆括号
elements.map(element => {
  return element.length;
}); // [8, 6, 7, 9]

// 当箭头函数的函数体只有一个 `return` 语句时,可以省略 `return` 关键字和方法体的花括号
elements.map((element) => element.length); // [8, 6, 7, 9]

// 在这个例子中,因为我们只需要 `length` 属性,所以可以使用参数解构
// 需要注意的是字符串 `"length"` 是我们想要获得的属性的名称,而 `lengthFooBArX` 则只是个变量名,
// 可以替换成任意合法的变量名
elements.map(({ length: lengthFooBArX }) => lengthFooBArX); // [8, 6, 7, 9]


模块

ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。

ES6 的模块化分为导出(export)导入(import)两个模块。

基本用法

模块导入导出各种类型的变量,如字符串,数值,函数,类。

  • 导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。
  • 不仅能导出声明还能导出引用(例如函数)。
  • export 命令可以出现在模块的任何位置,但必需处于模块顶层。
  • import 命令会提升到整个模块的头部,首先执行。
// 导出例子
/*-----export [test.js]-----*/
let myName = "Tom";
let myAge = 20;
let myfn = function(){
    return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass =  class myClass {
    static a = "yeah!";
}
export { myName, myAge, myfn, myClass }

注意:

  • 建议使用大括号指定所要输出的一组变量写在文档尾部,明确导出的接口。
  • 函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。
/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!

可以使用as重新定义变量名,解决不同模块导出接口名称命名重复的问题

import

  1. 只读属性。可以改写 import 变量类型为对象的属性值,不能改写 import 变量类型为基本类型的值。
  2. 单例模式。多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import 。

举例说明:

  1. 只读属性,不能更改导入为基本类型变量
// 在 module.js 文件中
export const a = 1;

// 在另一个文件中
import { a } from './module.js';
a = 2; // 错误!`a` 是只读的
  1. 只读属性,可以修改导入为引用类型的属性
// 在 module.js 文件中
export const obj = { prop: 'hello' };

// 在另一个文件中
import { obj } from './module.js';

obj.prop = 'world'; // 正确。改写 obj 对象的属性值是允许的
obj = {}; // 错误!`obj` 是只读的,不能改写整个对象
  1. 只读属性。如果导入的变量类型是对象(包括数组和函数),你可以修改其属性值,但你不能重新分配整个对象
// 在 module.js 文件中
export const arr = [1, 2, 3];

// 在另一个文件中
import { arr } from './module.js';

arr[0] = 4; // 正确。改写 arr 数组的元素值是允许的
arr = []; // 错误!`arr` 是只读的,不能改写整个数组
  1. 单例模式。
import { a } "./xxx.js";
import { a } "./xxx.js";
// 相当于 import { a } "./xxx.js";
 
import { a } from "./xxx.js";
import { b } from "./xxx.js";
// 相当于 import { a, b } from "./xxx.js";

export default

在一个文件或模块中,可以使用 export 关键字导出多个变量或函数,但 export default 只能用一次。这是因为 export default 导出的成员将作为模块的默认导出,当其他模块只使用 import 语句而不加花括号 {} 导入时,就会得到这个默认导出的值。

  • export default 中的 default 是对应的导出接口变量
  • 通过 export 方式导出,在导入时要加{ },export default 则不需要。
  • export default 向外暴露的成员,可以使用任意变量来接收。

假设我们有一个名为 module.js 的模块:

// module.js
export const a = 1;
export const b = 2;
export default function() {
  console.log('default function');
}

现在我们来导入这个模块:

// 在另一个文件中
import myFunc, { a, b } from './module.js';

console.log(a); // 输出:1
console.log(b); // 输出:2
myFunc(); // 输出:'default function'

在以上示例中,a 和 b 都通过 export 语句导出,所以在导入它们时需要包裹在 {} 中。而默认导出的函数没有具体的名字,我们在导入时可以自由地给它命名(在上面的例子中,我们把它命名为 myFunc),并且不需要放在 {} 中。


Promise

promise是 JavaScript ES6 引入的概念,是异步编程的一种解决方案。

从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

Promise 状态

Promise 异步操作有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

除了异步操作的结果,任何其他操作都无法改变这个状态。

Promise 对象只有:

  • 从 pending 变为 fulfilled
  • 从 pending 变为 rejected

只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。

状态的缺点

  • 无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
  • 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

Promise构造方法

new Promise(executor)

其中executor在构造函数中执行的 function。它接收两个函数作为参数:resolveFuncrejectFuncexecutor 中抛出的任何错误都会导致 Promise 被拒绝,并且返回值将被忽略。

function executor(resolveFunc, rejectFunc) {
  // 通常,`executor` 函数用于封装某些接受回调函数作为参数的异步操作,比如上面的 `readFile` 函数
}
  1. 在构造函数生成新的 Promise 对象时,它还会生成一对相应的 resolveFuncrejectFunc 函数;它们与 Promise 对象“绑定”在一起。
  2. executor 通常会封装某些提供基于回调的 API 的异步操作。回调函数(传给原始回调 API 的函数)在 executor 代码中定义,因此它可以访问 resolveFuncrejectFunc
  3. executor 是同步调用的(在构造 Promise 时立即调用),并将 resolveFuncrejectFunc 函数作为传入参数。
  4. executor 中的代码有机会执行某些操作。异步任务的最终完成通过 resolveFuncrejectFunc 引起的副作用与 Promise 实例进行通信。这个副作用让 Promise 对象变为“已解决”状态。
    • 如果先调用 resolveFunc,则传入的值将解决
    • 如果先调用 rejectFunc,则 Promise 立即变为已拒绝状态
    • 一旦 resolveFuncrejectFunc 中的一个被调用,Promise 将保持解决状态。只有第一次调用 resolveFuncrejectFunc 会影响 Promise 的最终状态,随后对任一函数的调用都不能更改兑现值或拒绝原因,也不能将其最终状态从“已兑现”转换为“已拒绝”或相反。
    • 如果 executor 抛出错误,则 Promise 被拒绝。但是,如果 resolveFuncrejectFunc 中的一个已经被调用(因此 Promise 已经被解决),则忽略该错误。
    • 解决 Promise 不一定会导致 Promise 变为已兑现或已拒绝(即已敲定)。Promise 可能仍处于待定状态,因为它可能是用另一个 thenable 对象解决的,但它的最终状态将与已解决的 thenable 对象一致。
  5. 一旦 Promise 敲定,它会(异步地)调用任何通过 then()、catch() 或 finally() 关联的进一步处理程序。最终的兑现值或拒绝原因,在调用时作为输入参数传给兑现和拒绝处理程序。

一个比较全面的例子

// 为了尝试错误处理,使用“阈值”值会随机地引发错误。
const THRESHOLD_A = 8; // 可以使用 0 使错误必现

function tetheredGetNumber(resolve, reject) {
  setTimeout(() => {
    const randomInt = Date.now();
    const value = randomInt % 10;
    if (value < THRESHOLD_A) {
      resolve(value);
    } else {
      reject(`太大了:${value}`);
    }
  }, 500);
}

function determineParity(value) {
  const isOdd = value % 2 === 1;
  return { value, isOdd };
}

function troubleWithGetNumber(reason) {
  const err = new Error("获取数据时遇到问题", { cause: reason });
  console.error(err);
  throw err;
}

function promiseGetWord(parityInfo) {
  return new Promise((resolve, reject) => {
    const { value, isOdd } = parityInfo;
    if (value >= THRESHOLD_A - 1) {
      reject(`还是太大了:${value}`);
    } else {
      parityInfo.wordEvenOdd = isOdd ? "奇数" : "偶数";
      resolve(parityInfo);
    }
  });
}

new Promise(tetheredGetNumber)
  .then(determineParity, troubleWithGetNumber)
  .then(promiseGetWord)
  .then((info) => {
    console.log(`得到了:${info.value}, ${info.wordEvenOdd}`);
    return info;
  })
  .catch((reason) => {
    if (reason.cause) {
      console.error("已经在前面处理过错误了");
    } else {
      console.error(`运行 promiseGetWord() 时遇到问题:${reason}`);
    }
  })
  .finally((info) => console.log("所有回调都完成了"));

then 方法

then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。

在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。

通过 .then 形式添加的回调函数,不论什么时候,都会被调用。

链式调用

then() 函数会返回一个和原来不同的新的 Promise:

const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

promise2 不仅表示 doSomething() 函数的完成,也代表了你传入的 successCallback 或者 failureCallback 的完成。

每一个 Promise 都代表了链中另一个异步过程的完成。

此外,then 的参数是可选的,catch(failureCallback) 等同于 then(null, failureCallback)——所以如果你的错误处理代码对所有步骤都是一样的,你可以把它附加到链的末尾:

doSomething()
  .then(function (result) {
    return doSomethingElse(result);
  })
  .then(function (newResult) {
    return doThirdThing(newResult);
  })
  .then(function (finalResult) {
    console.log(`得到最终结果:${finalResult}`);
  })
  .catch(failureCallback);

可以用箭头函数改写:

doSomething()
	.then(result => doSomethingElse(result))
	.then(newResult => doThirdThing(newResult))
	.then(finalResult => {
		console.log(`得到最终结果:${finalResult}`);
	})	
	.catch(failureCallback);

注意:用箭头函数改写一定要有返回值,否则,回调将无法获取上一个 Promise 的结果。


generator 函数

Generator 有两个区分于普通函数的部分:

  • 在 function 后面,函数名之前有个 * ;
  • 函数内部有 yield 表达式。

生成器函数在执行时能暂停,后面又能从暂停处继续执行

function*

function*这种声明方式 (function关键字后跟一个星号)会定义一个生成器函数 (generator function),它返回一个 Generator 对象。

语法

function* name([param[, param[, ... param]]]) { statements }
  • name 函数名
  • param 要传递给函数的一个参数的名称,一个函数最多可以有 255 个参数
  • statements 普通 JS 语句

调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 ( iterator )对象。当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。

如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function* generator(i) {
  yield i;
  yield* anotherGenerator(i); // 移交执行权
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20

async 函数

async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。

async 函数是使用async关键字声明的函数。async 函数AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。asyncawait 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise

async 函数可能包含 0 个或者多个 await 表达式。await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值。使用 async/await 关键字就可以在异步代码中使用普通的 try/catch 代码块。

语法

async function name([param[, param[, ... param]]]) { statements }
  • name: 函数名称。
  • param: 要传递给函数的参数的名称。
  • statements: 函数体语句。

async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。

下面两个是等价的

async function foo1() {
  return 1;
}

// foo1等价于foo2

function foo2() {
  return Promise.resolve(1);
}

async 函数的函数体可以被看作是由 0 个或者多个 await 表达式分割开来的。

  • 从第一行代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。
  • 一个不含 await 表达式的 async 函数是会同步运行的。
  • 如果函数体内有一个 await 表达式,async 函数就一定会异步执行。
async function foo1() {
  await 1;
}

// foo1等价于foo2

function foo2() {
  return Promise.resolve(1).then(() => undefined);
}

async/await 和 Promise/then 对比

大多数 async 函数也可以使用 Promises 编写。但是,在错误处理方面,async 函数更容易捕获异常错误。

  • 使用async/await可以让我们编写看起来更像同步代码的异步代码,它们通常更易于阅读和理解。
  • 使用.then需要更多的回调函数,可能导致“回调地狱”,但在某些情况下(例如链式调用或者处理各种可能的解决/拒绝情况)可能会更有用。
  • async/await基于Promise实现,它仍然需要Promise本身提供的功能,比如Promise.all用于并行等待多个操作完成。

await 和并行

请使用 Promise.all 或者 Promise.allSettled在async中执行多个promise

function resolveAfter2Seconds() {
  console.log("starting slow promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("slow");
      console.log("slow promise is done");
    }, 2000);
  });
}

function resolveAfter1Second() {
  console.log("starting fast promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("fast");
      console.log("fast promise is done");
    }, 1000);
  });
}

async function sequentialStart() {
  console.log("==SEQUENTIAL START==");

  // 1. Execution gets here almost instantly
  const slow = await resolveAfter2Seconds();
  console.log(slow); // 2. this runs 2 seconds after 1.

  const fast = await resolveAfter1Second();
  console.log(fast); // 3. this runs 3 seconds after 1.
}

async function concurrentStart() {
  console.log("==CONCURRENT START with await==");
  const slow = resolveAfter2Seconds(); // starts timer immediately
  const fast = resolveAfter1Second(); // starts timer immediately

  // 1. Execution gets here almost instantly
  console.log(await slow); // 2. this runs 2 seconds after 1.
  console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
}

function concurrentPromise() {
  console.log("==CONCURRENT START with Promise.all==");
  return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then(
    (messages) => {
      console.log(messages[0]); // slow
      console.log(messages[1]); // fast
    },
  );
}

async function parallel() {
  console.log("==PARALLEL with await Promise.all==");

  // Start 2 "jobs" in parallel and wait for both of them to complete
  await Promise.all([
    (async () => console.log(await resolveAfter2Seconds()))(),
    (async () => console.log(await resolveAfter1Second()))(),
  ]);
}

sequentialStart(); // after 2 seconds, logs "slow", then after 1 more second, "fast"

// wait above to finish
setTimeout(concurrentStart, 4000); // after 2 seconds, logs "slow" and then "fast"

// wait again
setTimeout(concurrentPromise, 7000); // same as concurrentStart

// wait again
setTimeout(parallel, 10000); // truly parallel: after 1 second, logs "fast", then after 1 more second, "slow"

首先看resolveAfter2SecondsresolveAfter1Second函数,它们返回一个新的Promise,这个Promise在一段延迟后解决,并打印出消息。

详细解释每种处理异步操作的方法:

  1. 顺序(Sequential):异步函数sequentialStart里面,首先等待resolveAfter2Seconds解决,然后再等待resolveAfter1Second解决。因此,这两个promise是一个接一个地解决的
  2. 并发(Concurrent):在concurrentStart函数中,两个promise都立即开始,但是我们通过await关键字按顺序等待它们解决。所以,在第一个promise解决之后,第二个promise可能已经解决了,因此你可以几乎立刻得到结果。
  3. Promise.all并发concurrentPromise函数与concurrentStart函数的行为相似,但是它使用Promise.all等待所有的promise都解决。当所有的promise都解决时,.then会被调用,并且会收到一个数组,这个数组包含了每个promise解决的值。
  4. 并行(Parallel):在parallel函数中,我们也使用Promise.all,但是我们同时开始(并打印)两个promise的结果。这是真正的并行执行,因为两个操作是同时进行的,并且各自的结果会在它们就绪时立即打印出来。

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值