第一章:JavaScript复习
1.复习js中的三种变量声明
三种声明方式:第一种:首先使用的const 如果希望变量被改变则使用let关键字 至于var关键字最好报在代码中出现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
/*var和let对比:
* var
* -没有块级作用域
*
* let
* -有块级作用域
*
*const 和let类似 具有块级作用域 但是它只能赋值一次 使用场景:对于一些常量可以使用这个关键字
*
* 对于一些对象函数也可以使用这个关键字 这样可以避免对象和函数被意外修改
* */
var a=2;//
{
var b=1;//块作用域
}
function fn(){
var a=1;
}
console.log(a)
for (var i = 0; i < 10; i++) {
console.log(i);//在循环内部访问
}
console.log(i)//在循环外部访问
if (true){
var tx=3;
console.log("内部"+tx)
}
//在外面访问tx
console.log("外部"+tx)
//因此只能使用闭包来书写
(function () {
if (true){
var mn=12;
console.log("mn为")
console.log(mn);
}
})()
const Pi=3.14;
let t=2;
t=Pi*t*t;
console.log(t);
</script>
</head>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>解构赋值</title>
<script>
// let a,b;
// let arr=['吴邪','解雨臣'];
// a=arr[0];
// b=arr[1];
// console.log(a,b);
// 以上方法不太友好 因此使用解构赋值
// [a,b]=arr;
// console.log(a,b);//效果一样
/*
const [a,b]=arr;//声明完直接赋值
console.log(a,b)
*/
function f() {
return ['吴邪','王胖子','解雨臣','张起灵'];
}
//调用函数来赋值
const [a,b, ,...c]=f();//可以跳过元素 ...变量 会接受后面所有的元素
console.log(a,b,c)
// 对于对象的解包
const obj={
name:'吴邪',
age:19,
gender:'男'
};
let x,y,z;
// 传统的赋值方式
x=obj.name;
y=obj.age;
z=obj.gender;
console.log(x,y,z);//这种方式太麻烦
//
// 对象的解构:
let o,d,j;
({
name:o,//将name赋值给o
age:d,//将age赋值给d
gender: j,//将gender赋值给j
}=obj) ;//如果变量名和属性名一致 则可以只写一个
console.log(o,d,j)
// 利用数组的解构来交换两个变量的位置
let p=10,q=20;
// console.log(p,q)
// 交换变量
[p,q]=[q,p];
console.log(p,q);
arr01=[12,14,15,12]
[arr01[1],arr01[2]]=[arr01[2],arr01[1]];//交换数组的值
console.log(arr01)
</script>
</head>
<body>
</body>
</html>
展开:
//展开:
function fn(a,b,c) {
return a+b+c;
}
const arr=[1,2,3];
//计算数组中三个数的和
/*let result=fn(arr[0],arr[1],arr[2]);
console.log(result)*/
//可以通过 ...来展开一个数组
let result=fn(...arr)
console.log(result)
//2.相当于将arr复制
const arr2=[...arr,4,5,6];
console.log(arr2);
//3.对象的展开 相当于一个复制
const obj={
name:'解雨臣',
age:'23',
sex:'男'
};
const obj2={...obj,address:'吴山居'};
console.log(obj2);
//箭头函数:箭头函数是传统函数表达式的简写方式 他简化了函数的编写
//特点:箭头函数没有自己的this 不能作为构造函数来使用 无法通过call apply bind 指定函数的this
//1.箭头函数的定义:
//类型一:只有一个参数的箭头函数 参数=>返回值
const fn=function (a) {
return '我是函数';
}
//等价于:
const fn2=a =>'我是函数';
console.log(fn2(1));
//类型二:两个参数:如果没有参数 或多个参数 参数需要用()括起来
//()=>返回值 (a,b,c)=>返回值
const fn3 =(a,b,c)=>a+'我是另一个箭头函数';
console.log(fn3());
//例如:
const sum=(a,b)=>a+b;//箭头后面的值就是返回值 返回值必须是·一个表达式
let t=sum(1,2);
console.log(t);
//表达式:有返回值的语句
const vb=(m,n)=>({name:'吴邪',age:18,sex:'男'})
console.log(vb());
//如果需要在箭头函数中定义逻辑 则可以在后面书写代码块
const fn4=(a,b)=>{
if (a===b){
a+=5;
console.log(a)
}else if (a===10){
a+=10;
}
return a+b;
};
console.log(fn4(2,2))
/*
* 箭头函数:只有一个参数返回值 参数 =>返回值
* 如果没有参数 或多个参数 参数需要使用()括起来
* ()=>返回值
* (a,b,c)=>返回值
*
* 箭头后面的值就是函数的返回值
* 返回值必须是一个表达式
* 如果返回值是对象 必须加()
*
* 如果需要在箭头函数中定义逻辑 可以直接在箭头后跟一个代码块
*
*
* */
//特点:
function fn(...args){
console.log(args);
console.log(arguments.length);//表示当前函数的实参 用来保存函数的实参
}
fn();
const fn2=(...aggs)=>{
// console.log(arguments) 箭头函数没有这个
console.log(aggs);
};
fn2()
/*
* 1.箭头函数中没有argue
* 2.箭头函数中没有自己的this
* -它的this总是外边的this 也就是全局作用域的this
* 3.箭头函数中的this无法通过call() apply()来修改
*
* 4.箭头函数无法作为构造函数来使用
* */
const fn3=()=>{
console.log(this)
};
fn3()
const obj={
hello:()=>{
console.log(this)
}
}
obj.hello();
new fn();//fn是普通函数
js中的模块化:
/*js中的模块化:
*
*es中的模块化分为为:export (导出)和 import(导入)
*
* */
export const a=10;//导出a
const b=20;
const c=90;
const obj={
name:'吴邪',
age:'18'
}
const fn=()=>{
console.log('我是fn');
}
fn()
//作为一个模块,我们不希望模块中的内容都暴露给外部
//作为导入方:我们也不希望导入无用的变量
// export导出:导出用来决定模块中那些内容可以被外部查看
//导出分为两种:
//1.默认导出
//2.命名导出
//1.默认导出:语法:export default 要导出的值
export default a;//将变量a作为默认导出
/*2.导入:import 导入用来将外部模块中的内容导入当前模块中
* -import obj from '../react教程/react03' 导入模块时 变量名可以自主指定 无需和模块中的变量名对应
* */
//导入模块中的默认模块
import a from './react03';//这样写不会成功
/*
* 默认情况下script标签不能使用import语句:如果想让其支持模块化 必须设置script属性为module
*
* */
import a from '../react教程/react03'
console.log(a)
/*
* 在网页中导入模块时 模块的路径必须写完整 (./或../开头 扩展名也必须写上)
* */
//导入指定的内容:import {b,c} from './react02.js'
//也可以更改变量名称:import {b as x,c} from './react02.js'
export {obj,fn};//命名导出
类(Class):类是对象的模块 类中定义了对象中包含了那些属性和方法 也可以跳过function来定义类 但是两种方式并不是完全通用
/*
* 1.类的定义:
* 类是对象的模板 类决定了一个对象有哪些属性和方法
* 使用class关键字来定义一个类
*
*
* */
class person{
//在类中可以定义属性
// 属性
name='吴邪';
//属性
age=19;
// 在类中定义方法
// 定义了一个实例化方法
run(){
console.log("我是一个实例化方法")
}
// 构造函数:当我们通过new创建对象时 实际上就是在调用类的构造函数
constructor(name,age) {
//在构造函数中我们可以跳过this来应用当前的对象
//将参数赋值给对象中的属性
this.name=name;
this.age=age;
console.log("我是构造函数")
}
}
const per=new person('吴邪','12');
console.log(per);
//类中的MyClass
class MyClass{
fn(){
console.log(this)
}
fn3=()=>{
console.log(this)
}
}
const mc=new MyClass();
console.log(mc)
mc.fn();//调用方法 和Java中的一样
const test=mc.fn;
test()//以函数的形式调用 就是未定义
//类中的所有代码斗湖在严格模式下执行 严格模式的特点就是函数的this就不是window
"use strict";
//注意:在类中方法的this不是固定的 以方法形式调用时 this就是当前的实例 以函数形式调用 this是undefined
//在开发的时候 我们希望方法中的this是固定的 不会因为调用方式不同而改变
//如果有这种需求 我们可以使用箭头函数来定义
/*如果类中的方法是以箭头函数来定义的 则方法中的this恒为当前示例 不会改变
*
* */
/*
* 类中的继承:
*
* */
class Dog{
constructor(name,age) {
this.name=name;
this.age=age;
}
sayHello=()=>{
console.log("你好")
}
}
//从以上代码可以看出:上面哟荤多重复的代码 一次我们可以吧许多共有的代码提取出来 然后使用继承
//将多个重复代码提取出来
class Animal{
constructor(name,age) {
this.name=name;
this.age=age;
}
sayHello=()=>{
console.log("吴邪")
}
}
//通过使用继承 我们可以使得其他类中的属性和方法
/*
class Snake{
constructor(name,age) {
this.name=name;
this.age=age;
}
SayHello=()=>{
console.log("你好!")
}
}*/
//使用extends来继承一个类 继承后就相当于将该类的代码赋值到了当前类
class Snake extends Animal{
//当我们在子类中重写父类构造函数时 必须在子类构造函数中第一时间调用父类构造函数 否则会报错
constructor(lenngth,anme,age) {
super(name,age);//这个就是第一时间调用
// 然后再重新定义一个length
this.len=lenngth;
}
// 很明显代码块内没有代码但是仍然可以控制台输出
}
const dog=new Snake('吴邪','12');
dog.sayHello()
console.log(dog);
//当我们使用继承后 被继承的类就称为父类 继承父类的类 称为子类
//子类继承父类后 将获得父类指定所有属性和方法
//也可以创建同名属性或方法来对父类进行重写
/*静态属性和方法:
*
* 直接通过类调用的属性和方法被称为静态属性和静态方法
*
*
*
* */
class MyClass{
name='吴邪';//属性
fn=()=>{//方法
console.log("铁三角")
}
}
//开始面向对象
const mc=new MyClass();
mc.fn()
console.log(mc.name);
//使用static关键字来定义
class MyClass1{
static name='吴邪';//属性
static fn=()=>{//方法
console.log("铁三角")
}
}
//开始面向对象
console.log(MyClass1.name);
console.log(MyClass1.fn());
/*数组的方法:
* 1.map()-根据原有数组返回一个新数组
*
* */
const arr=[1,2,3,45,6];
let map = arr.map(()=>10);//这个直接是把数组中的所有元素都变为10
console.log(map);
//需要一个回调函数作为参数 回调函数的返回值会最为数组中的元素
/*let map1 = arr.map((item)=>item+2);//这个代表把数组中的每个元素都加2
console.log(map1);*/
let map1 = arr.map((item,index)=>item+2);//这个代表把数组中的每个元素都加2
console.log(map1);
//回调函数中有三个参数:
/*
* 第一个参数:当前元素
* 第二个参数:当前元素的索引
* 第三个参数:当前数组
*
* */
const arr2=['吴邪','解雨臣','张起灵','王胖子']
let strings = arr2.map(item=>"<li>"+item+"<li>");
console.log(strings);
/*
*
* filter()过滤:可以从一个数组中获得符合条件的元素*/
const arr3=[1,2,3,4,5];
let numbers = arr3.filter(item=>item%2===0);
console.log(numbers);
//会根据回调函数的结果来决定是否保留元素 true保留 FALSE 不保留
//find()会从数组中找到符合条件的第一个元素
let number = arr3.find(items=>items%2===0);
console.log(number);
//reduce()用来整合数组 也就是对数组中的元素进行运算
console.log(arr.reduce((m, n) => {
console.log(m, n);
return m + n;
}),1);//指定初始值
//需要两个参数:回调函数:指定运算规则 四个参数:m上一次运算结构 n当前值 index 当前索引 arr 当前值
//初始值:用来指定第一次运算的m 如果不指定则直接从第二个元素开始计算
第二章:react入门
1.网页中是BS架构
react是一个用于构建用户界面的JavaScript库 用来为携带的网络构建用户界面 react起源于Facebook
react特点:虚拟dom 声明式 基于组件 支持服务器端渲染 快速简单 易学
2.书写第一个程序:hello world
解决了兼容性的问题
基于传统编程的命令式 react采用声明式
helloworld的书写:在入门阶段我们使用js脚本来书写:
react.js
react-dom.js
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello world</title>
<!--1.引入react的库-->
<script src="script/react.development.js"></script>
<!--2.引入react的dom库-->
<script src="script/react-dom.development.js"></script>
</head>
<body>
<div id="root"></div>
<script >
/*
* react就是用来代替dom的
* 通过dom向页面中添加一个div
* */
/*原生js操作*/
/*// 1.创建一个div
const div =document.createElement('div')
// 2.向div中设置问题
div.innerText='我是一个div'
// 3.获取root
const root=document.getElementById('root')
// 4.将div添加到页面中
root.appendChild(div)*/
// 二.使用react库来操作dom
/*React.createElement()--用来创建一个react元素 参数:1.元素名(组件名) 2.元素的属性 3.元素的子元素(内容)
*
* */
// 1.通过react向页面中添加一个div
const div= React.createElement('div',{},'我是创建的div');//创建一个react元素
//2.获取根元素对应的react元素
const root= ReactDOM.createRoot(document.getElementById('root'))//这个方法用来创建根元素 需要一个DOm元素作为参数
//3.将div渲染到根元素中
root.render(div);
// console.log(div);
</script>
</body>
</html>
三个API之一:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>三个API</title>
<script src="script/react.development.js"></script>
<script src="script/react-dom.development.js"></script>
</head>
<body>
<button id="btn">我是按钮</button>
<div id="root"></div>
<script>
/*
* React.createElement()
* - 用来创建一个React元素
* - 参数:
* 1.元素的名称(html标签必须小写)
* 2.标签中的属性
* - class属性需要使用className来设置
* - 在设置事件时,属性名需要修改为驼峰命名法
* 3.元素的内容(子元素)
* - 注意点:
* React元素最终会通过虚拟DOM转换为真实的DOM元素
* React元素一旦创建就无法修改,只能通过新创建的元素进行替换
*
*
* */
// 创建一个React元素
const button = React.createElement('button', {
type: 'button',
className: 'hello',
onClick: () => {
alert('你点我干嘛')
}
}, '点我一下');
// 创建第一个div
const div = React.createElement('div', {}, '我是一个div', button);
// 获取根元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 将元素在根元素中显示
root.render(div);
// 获取按钮对象
const btn = document.getElementById('btn');
btn.addEventListener('click', ()=>{
// 点击按钮后,修改div中button的文字为click me
const button = React.createElement('button', {
type: 'button',
className: 'hello',
onClick: () => {
alert('你点我干嘛')
}
}, 'click me');
// 创建一个div
const div = React.createElement('div', {}, '我是一个div', button);
// 修改React元素后,必须重新对根元素进行渲染
// 当调用render渲染页面时,React会自动比较两次渲染的元素,只在真实DOM中更新发生变化的部分
// 没发生变化的保持不变
root.render(div);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="script/react.development.js"></script>
<script src="script/react-dom.development.js"></script>
<script >
<!-- 1.创建一个react元素-->
const button=React.createElement('button',{
type:'button',
className:'hello',
onClick:()=>{
alert("点我干嘛")
}
},'点我一下')
//1.创建一个div
const div=React.createElement('div',{},'我是一个模块',button)
//2.获取根元素: 就是React元素要插入的位置
const root=ReactDOM.createRoot(document.getElementById('root'))//老版本中react中使用的方法
//3/将元素在根元素中显示
root.render(div)// root.render()用来将react元素渲染到根元素中 根元素中所有的内容都会被删除 被react所替换 当调用render的
// 时候 react会将两次的渲染结果进行比较 他会确保只修改那些发生变化的元素 对dom做最少的修改
</script>
</head>
<body>
<div id="root">
</div>
</body>
</html>
第三章:JSX介绍
1.jsx说明
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>JSX</title>
<script src="script/react.development.js"></script>
<script src="script/react-dom.development.js"></script>
<!-- 引入babel -->
<script src="script/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<!--设置js代码被babel处理-->
<script type="text/babel">
// 创建一个React元素 <button>我是按钮</button>
// 命令式编程
// const button = React.createElement('button', {}, '我是按钮');
// 声明式编程,结果导向的编程
// 在React中可以通过JSX(JS扩展)来创建React元素,JSX需要被翻译为JS代码,才能被React执行
// 要在React中使用JSX,必须引入babel来完成“翻译”工作
// const button = <button>我是按钮</button>; // React.createElement('button', {}, '我是按钮');
/*
* JSX就是React.createElement()的语法糖
* JSX在执行之前都会被babel转换为js代码
* */
const div = <div>
我是一个div
<button>我是按钮</button>
</div>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(div);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>JSX的注意</title>
<script src="script/react.development.js"></script>
<script src="script/react-dom.development.js"></script>
<!-- 引入babel -->
<script src="script/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
/*
* JSX的注意事项
* 1. JSX不是字符串,不要加引号
* 2. JSX中html标签应该小写,React组件应该大写开头
* 3. JSX中有且只有一个根标签
* 4. JSX的标签必须正确结束(自结束标签必须写/)
* 5. 在JSX中可以使用{}嵌入表达式
* - 有值的语句的就是表达式
* 6. 如果表达式是空值、布尔值、undefined,将不会显示
* 7. 在JSX中,属性可以直接在标签中设置
* 注意:
* class需要使用className代替
* style中必须使用对象设置
* style={{background:'red'}}
*
* */
function fn() {
return 'hello';
}
const name = '孙悟空';
const div = <div
id="box"
onClick={() => {
alert('哈哈')
}} className="box1"
// style中必须使用对象设置 style={{background:'red'}}
style={{backgroundColor: "yellowgreen", border: '10px red solid'}}
>
我是一个div
<ul>
<li>列表项</li>
</ul>
<input type="text"/>
<div>
{name} <br/>
{1 + 1} <br/>
{fn()} <br/>
{NaN} <br/>
</div>
</div>;
// alert(div);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(div);
</script>
</body>
</html>
2.列表渲染
注意:如果在页面中书写的代码没有在对应的网页中显示可以更改一下默认值:
<html lang="en"> 更改为<html lang="zh">
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>渲染列表</title>
<script src="script/react.development.js"></script>
<script src="script/react-dom.development.js"></script>
<!-- 引入babel -->
<script src="script/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const name = '吴邪';
const lang = 'cn';
/*
* {} 只能用来放js表达式,而不能放语句(if for)
* 在语句中是可以去操作JSX
* */
// const div = <div>Hello {name}</div>;
let div;
if(lang === 'en'){
div = <div>hello {name}</div>;
}else if(lang === 'cn'){
div = <div>你好 {name}</div>;
}
const data = ['吴邪', '解雨臣', '张起灵'];
/*
<ul>
<li>孙悟空</li>
<li>猪八戒</li>
...
</ul>
[<li>孙悟空</li>, <li>猪八戒</li>, <li>沙和尚</li>]
* */
// const arr = [];
// 遍历data 使用for循环
// for(let i=0; i<data.length; i++){
// arr.push(<li>{data[i]}</li>);
// }
// const arr = data.map(item => <li>{item}</li>);
// 将arr渲染为一个列表在网页中显示
// jsx中会自动将数组中的元素在页面中显示
// const list = <ul>{arr}</ul>;
const list = <ul>{data.map(item => <li>{item}</li>)}</ul>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(list);
</script>
</body>
</html>
3.虚拟DOM
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>虚拟DOM</title>
<script src="script/react.development.js"></script>
<script src="script/react-dom.development.js"></script>
<script src="script/babel.min.js"></script>
</head>
<body>
<button id="btn">点我一下</button>
<hr>
<div id="root"></div>
<script type="text/babel">
//创建一个数据
const data = ['孙悟空', '猪八戒', '沙和尚'];
// 创建列表
const list = <ul>
{/*data.map(item => <li key={item}>{item}</li>)*/}
{data.map((item, index) => <li key={index}>{item}</li>)}
</ul>;
// 获取根元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 渲染元素
root.render(list);
/*
* 在React我们操作的元素被称为React元素,并不是真正的原生DOM元素,
* React通过虚拟DOM 将React元素 和 原生DOM,进行映射,虽然操作的React元素,但是这些操作最终都会在真实DOM中体现出来
* 虚拟DOM的好处:
* 1.降低API复杂度
* 2.解决兼容问题
* 3.提升性能(减少DOM的不必要操作)
*
* 每当我们调用root.render()时,页面就会发生重新渲染
* React会通过diffing算法,将新的元素和旧的元素进行比较
* 通过比较找到发生变化的元素,并且只对变化的元素进行修改,没有发生的变化不予处理
* */
document.getElementById('btn').onclick = function (){
// 重新渲染页面
//创建一个数据
const data = ['唐僧', '孙悟空', '猪八戒', '沙和尚'];
// 创建列表
const list = <ul>
{/*data.map(item => <li key={item}>{item}</li>)*/}
{data.map((item, index) => <li key={index}>{item}</li>)}
</ul>;
// 渲染元素
root.render(list);
/*
* 旧数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 新数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 比较两次数据时,React会先比较父元素,父元素如果不同,直接所有元素全部替换
* 父元素一致,在去逐个比较子元素,直到找到所有发生变化的元素为止
* 上例中,新旧两组数据完全一致,所以没有任何DOM对象被修改
*
*
* 旧数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 新数据
* ul
* li>tom
* li>猪八戒
* li>沙和尚
*
* 上例中,只有第一个li发生变化,所以只有第一个li被修改,其他元素不变
*
* 当我们在JSX中显示数组中,数组中每一个元素都需要设置一个唯一key,否则控制台会显示红色警告
* 重新渲染页面时,React会按照顺序依次比较对应的元素,当渲染一个列表时如果不指定key,同样也会按照顺序进行比较,
* 如果列表的顺序永远不会发生变化,这么做当然没有问题,但是如果列表的顺序会发生变化,这可能会导致性能问题出现
*
*
* 旧数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 新数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* li>唐僧
*
* 上例中,在列表的最后添加了一个新元素,并没有改变其他的元素的顺序,所以这种操作不会带来性能问题
*
*
*
* 旧数据
* ul
* li>孙悟空
* li>猪八戒
* li>沙和尚
* 新数据
* ul
* li>唐僧
* li>孙悟空
* li>猪八戒
* li>沙和尚
*
* 上例中,在列表的最前边插入了一个新元素,其他元素内容并没有发生变化,
* 但是由于新元素插入到了开始位置,其余元素的位置全都发生变化,而React默认是根据位置比较元素
* 所以 此时,所有元素都会被修改
*
* 为了解决这个问题,React为列表设计了一个key属性,
* key的作用相当于ID,只是无法在页面中查看,当设置key以后,再比较元素时,
* 就会比较相同key的元素,而不是按照顺序进行比较
* 在渲染一个列表时,通常会给列表项设置一个唯一的key来避免上述问题
* (这个key在当前列表中唯一即可)
* 注意:
* 1.开发中一般会采用数据的id作为key
* 2.尽量不要使用元素的index作为key
* 索引会跟着元素顺序的改变而改变,所以使用索引做key跟没有key是一样的
* 唯一的不同就是,控制台的警告没了
* 当元素的顺序不会发生变化时,用索引做key也没有什么问题
* 旧数据
* ul
* li(key=孙悟空)>孙悟空
* li(key=猪八戒)>猪八戒
* li(key=沙和尚)>沙和尚
* 新数据
* ul
* li(key=唐僧)>唐僧
* li(key=孙悟空)>孙悟空
* li(key=猪八戒)>猪八戒
* li(key=沙和尚)>沙和尚
* */
};
</script>
</body>
</html>