前端技术栈+Vue笔记
ES6新特性
1.let
1)let声明有严格的局部作用域
此时"console.log("job= "+job)"将报错
{
var name = "zy学习";
let job = "java工程师";
console.log("name= "+name)
console.log("job= "+job)
}
console.log("name2= "+name)
console.log("job= "+job)
2)let只能声明一次
此时业因为let的重复声明
会报错SyntaxError (语法错误):redeclaration of let num2
//let 只能声明1次
var num1 = 100;
var num1 = 200;
console.log(num1);
let num2 = 100;
let num2 = 200;
console.log(num2);
3)当var变量 定义在后面,不会报错未定义,而是提升变量为undefind。
let变量则不会,会毫不留情的报错
//let不存在变量提升 var存在变量提升
console.log("x=",x);
var x = "tom";
console.log("z=",z);
let z = "zzz";
2.count
1)常量在定义时,需要赋值
//常量在定义时,需要赋值
const PI=3.14;//SyntaxError: missing = in const declaration
console.log("PI= ",PI)
2)常量赋值后不能修改
会报错 invalid assignment to const 'PI'
const PI=3.14;
PI = 3.14159256;
3.解构赋值
3.1 数组解构
1)传统方法
let arr = [1, 2, 3];
//解构(取出数据):
let x = arr[0], y = arr[1], z = arr[2];
console.log("x= " + x + " ,y= " + y + " ,z= " + z);
2)ES6
let [a,b,c] = arr;
console.log("a= " + a + " ,b= " + b + " ,c= " + c);
let [n1,n2,n3] = [100,200,300];
console.log("n1= " + n1 + " ,n2= " + n2 + " ,n3= " + n3);
3.2 对象解构
注意事项:
1.{name,age}中的name,age 名字要和monster中的对象属性名保持一致
2.使用{ } 不要使用[ ]
3.{name,age}顺序无所谓
1)传统方法
//传统 对象名.属性名
let monster = {name: '牛魔王', age: 880}
console.log(monster.name," ",monster.age)
2)ES6
let {name,age} = monster;
console.log("name= ",name," ",",age= ",age)
console.log("age= ",age," ",",name= ",name)
let {name,age} = {name: '牛魔王', age: 880}
方法的形参对象也可以解构
function f1(name,age){
console.log("f1-name= ",name," ",",f1-age= ",age)
}
f1(monster)
3.3 方法解构
4.模板字符串
注意事项:
1.反引号 ``包裹
2.可作为普通字符串使用
3.可以原生输出代码 (意思是不需要加很多 \n 拼接字符串)
let str1 = `for(int i = 0 ;i <10 ; i++){
System.out.println("i= "+i);
}`;
console.log("str1=", str1)
4.字符串插入变量和表达式,使用${ }
*类似于el表达式
let name = "zy88";
let str2 = `学习=${name}`;
let str3 = `1+2=${1 + 2}`;
let n1 = 80;
let n2 = 20;
let str4 = `n1+n2=${n1 + n2}`;
console.log("str2=", str2)
console.log("str3=", str3)
console.log("str4=", str4)
5.字符串中调用函数
function sayHi(name) {
return "hi "+name;
}
let name2 = 'tom'
let str5 = `sayHi()返回结果= ${sayHi(name2)}`
console.log("str5=", str5)
5.对象相关新特性
注意事项:
1.自动从上面找到相应变量
2.对象方法简写
1)传统对方法的定义
let monster = {
name:"红孩儿",
age:100,
sayHi:function () {
console.log("信息:name=",this.name,"age=",this.age)
}
}
monster.sayHi();
2)ES6对方法的定义
let monster2 = {
name:"红孩儿~",
age:900,
sayHi() {
console.log("信息:name=",this.name,"age=",this.age)
}
}
3.对象扩展运算符--拷贝
1)传统拷贝
let cat2 = cat;
cat2.name = "大花猫"
console.log("cat=>",cat)
console.log("cat2=>",cat2)
2)ES6深拷贝
let cat2 = {...cat};//深拷贝
cat2.name = "中花猫"
console.log("cat=>",cat)
console.log("cat2=>",cat2)
3)ES6并对象
let monster = {name:"白骨精",age:100}
let car = {brand:"xiaomi",price:149000}
let monster_car = {...monster,...car}
6.箭头函数
1)基本语法:
(参数列表)=> {
函数体...
}
2)注意事项:
1.没有参数或多个参数用括号( )括起来,一个参数可以省略括号
2.函数体只有一行语句,并需要返回结果时,可以省略花括号{},结果自动返回
3)对比传统
a)传统
var f1 = function (n) {
return n * 2;
}
console.log(f1(2))
a)ES6
let f2 = (n) => {
return n * 2;
}
// console.log(f2)
console.log(f2(3))
//简化
let f3 = n => n * 3;
console.log(f3(10))
b)传统
var f1 = function (n,m) {
var res = 0;
for (var i = n; i <=m; i++) {
res += i
}
return res
}
console.log(f1(1,10))
b)ES6
let f2 = (n,m) => {
var res = 0;
for (var i = n; i <=m; i++) {
res += i
}
return res
}
// console.log(f2)
console.log(f2(1,10))
4)箭头函数+解构
- f2传入对象monster
- f2形参时skill,所以根据es6对象解构特性,会把monster对象的skill赋给skill
- 对象解构的前提是形参skill和monster对象属性 skill的属性名保持一致
const monster = {
name:"红孩儿",
age:10000,
skill:['红缨枪','三味真火']
}
//传统解构
function f1(monster) {
console.log("skill=",monster.skill)
}
f1(monster)
//ES6
let f2 = ({skill}) =>{
console.log("skill=",skill);
}
f2(monster)
7.promise
promise的引出
promise是为了解决传统ajax异步调用需要多个操作时,会导致回调函数嵌套引起代码冗余的问题
原生的jquery 对于需要多次异步请求时表现:
<script type="text/javascript">
$.ajax({
url: "data/monster.json",
success(resultData) {
console.log("第一次ajax请求 monster基本信息= ", resultData);
$.ajax({
url: `data/monster_detail_${resultData.id}.json`,
success(resultData) {
console.log("第二次ajax请求 monster详细信息= ", resultData);
},
error(err) {
console.log("出现异常= ", err)
}
})
},
error(err) {
console.log("出现异常= ", err)
}
})
</script>
可以看出代码有非常多的嵌套,很不美观。
使用promise来完成多次ajax请求时表现:
先请求到monster.json
1. 创建Promise对象
2. 构造函数传入一个箭头函数
3. (resolve, reject) 参数列表resolve: 如果请求成功, 调用resolve函数
4. 如果请求失败, 调用reject函数
5. 箭头函数体, 仍然是通过jquery发出ajax
<script type="text/javascript">
let p = new Promise((resolve, reject) => {
//发出ajax
$.ajax({
url: "data/monster.json",
success(resultData) {//成功的回调函数
console.log("promise发出的第1次ajax monster基本信息=", resultData);
resolve(resultData);
},
error(err) {
//console.log("promise 1发出的异步请求异常=", err);
reject(err);
}
})
})
//这里我们可以继续编写请求成功后的业务
p.then((resultData) => {
//这里我们可以继续发出请求
//console.log("p.then 得到 resultData", resultData);
return new Promise((resolve, reject) => {
$.ajax({
url:`data/monster_detail_${resultData.id}.json`,
success(resultData) { //第2次ajax请求成功,回调函数
console.log("第2次ajax请求 monster的详细信息=", resultData);
//继续进行下一次的请求
resolve(resultData);
},
error(err) { //第2次ajax请求失败,回调函数
//console.log("promise2 发出的异步请求异常=", err);
reject(err);
}
})
})
}).then((resultData) => {
console.log("p.then().then(), resultData", resultData)
//即可以在这里发出第3次ajax请求=》 获取该妖怪的女友
return new Promise((resolve, reject) => {
$.ajax({
url: `data/monster_gf_${resultData.gfid}.json`,
success(resultData) { //第3次ajax请求成功,回调函数
console.log("第3次ajax请求 monster女友的详细信息=", resultData);
//继续进行下一次的请求
//resolve(resultData);
},
error(err) { //第2次ajax请求失败,回调函数
//console.log("promise2 发出的异步请求异常=", err);
//reject(err);
}
})
})
}).catch((err) => { //这里可以对多次ajax请求的异常进行处理
console.log("promise异步请求异常=", err);
})
</script>
使用了promise后 将传统的嵌套模式,改成了链式调用。
但代码依旧是否冗余,因此我们可以抽取相同的部分。
promise 代码优化重排
首先我们可以抽取一个工具方法
function get(url, data) {
return new Promise((resolve, reject) => {
$.ajax({
url: url,
data: data,
success(resultData) {
resolve(resultData);
},
error(err) {
reject(err);
}
}
)
})
}
之后调用此方法完成业务即可
get("data/monster.json").then((resultData) => {
//第1次ajax请求成功后的处理代码
console.log("第1次ajax请求返回数据=", resultData);
return get(`data/monster_detail_${resultData.id}.json`);
}).then((resultData) => {
//第2次ajax请求成功后的处理代码
console.log("第2次ajax请求返回数据=", resultData);
//return get(`data/monster_detail_${resultData.id}.json`);
return get(`data/monster_gf_${resultData.gfid}.json`);
}).then((resultData) => {
//第3次ajax请求成功后的处理代码
console.log("第3次ajax请求返回数据=", resultData);
//继续..
}).catch((err) => {
console.log("promise请求异常=", err);
})
8.模块化编程
- 传统非模块化开发有如下的缺点:(1)命名冲突(2)文件依赖[代码演示]
- Javascript 代码越来越庞大,Javascript 引入模块化编程,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块
- Javascript 使用"模块"(module)的概念来实现模块化编程, 解决非模块化编程问题
模块化编程分类
1.CommonJS 模块化规范/ES5 的写法
2.ES6 模块化规范
CommonJS 模块化规范/ES5 的写法
规则
-
每个js 文件就是一个模块,有自己的作用域。在文件中定义的变量、函数、类/对象,
都是私有的,对其他js 文件不可见 -
CommonJS 使用module.exports={} / exports={} 导出模块, 使用let/const 名称=
require("xx.js") 导入模块
图解
代码
这是 "function.js":
//这是function.js:
//定义对象,变量,常量,函数
const sum = function (a, b) {
return parseInt(a) + parseInt(b);
}
const sub = function (a, b) {
return parseInt(a) - parseInt(b);
}
let name = "zy学习";
const PI = 3.14;
const monster = {
name:"牛魔王",
age:500,
hi(){
console.log("Hi,i am 牛魔王~")
}
}
//导出
module.exports = {
sum:sum,
sub,sub,
myName,name
}
这是 "use.js":
//导入
const m = require("./function")
//使用
console.log(m.sum(100,200))
console.log(m.sub(120,60))
console.log(m.myName)
ES6 模块化规范 写法
规则
1、ES6 使用(1)export {名称/对象/函数/变量/常量} (2) export 定义= (3) export default {}
导出模块
2、使用import {} from "xx.js" / import 名称form "xx.js" 导入模块
图解
代码
导出:
const cat = {
name:"小白",
age:3,
cry(){
console.log("小猫喵喵叫~")
}
}
const dog = {
name:"大黄",
age:5,
hi(){
console.log("大黄 对你说 hi~")
}
}
//1.使用批量导出
export {
cat,
dog
}
//2.创建时直接导出
export const cat = {
name:"小白",
age:3,
cay(){
console.log("小猫喵喵叫~")
}
}
const dog = {
name:"大黄",
age:5,
hi(){
console.log("大黄 对你说 hi~")
}
}
//3.默认方式导出
export default {
cat: {
name: "小白",
age: 3,
cay() {
console.log("小猫喵喵叫~")
}
},
dog: {
name: "大黄",
age: 5,
hi() {
console.log("大黄 对你说 hi~")
}
}
}
导入
//1
import {cat,dog} from "./hspcommon";
cat.cry();
dog.hi()
//2.
import {cat} from "/./hspcommon2"
cat.name
//3.
import m from "./hspcommon3"
m.cat.cay()
Vue
基本介绍
- Vue 是一个前端框架, 易于构建用户界面
- Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或项目整合
- 支持和其它类库结合使用
- 开发复杂的单页应用非常方便
- Vue 是Vue.js 的简称
MVVM思想
- M∶即Model,模型,包括数据和一些基本操作
- V∶即View,视图,页面渲染结果
- VM∶即View-Model,模型与视图间的双向操作(无需开发人员干涉)
- 在MVVM之前,开发人员从后端获取需要的数据模型,然后要通过DOM 操作Model
渲染到View 中。而后当用户操作视图,我们还需要通过DOM获取View 中的数据,
然后同步到Model 中。 - 而MVVM中的VM 要做的事情就是把DOM 操作完全封装起来,开发人员不用再关心
Model 和View 之间是如何互相影响的 - 只要我们Model 发生了改变,View上自然就会表现出来
- 当用户修改了View,Model 中的数据也会跟着改变
- 结果:把开发人员从繁琐的DOM操作中解放出来,把关注点放在如何操作Model上, 大
大提高开发效率
Vue语法
插值表达式
- {{message}} : 插值表达式
- message 就是从model 的data 数据池来设置
- 当我们的代码执行时,会到data{} 数据池中去匹配数据, 如果匹配上, 就进行替换
, 如果没有匹配上, 就是输出空
//
欢迎你{{message}}-{{name}}
let vm = new Vue({
el: "#app", //创建的vue 实例挂载到id=app 的div
data: { //data{} 表示数据池(model 的有了数据), 有很多数据,以k-v 形式设置(根据业务
需要来设置)
message: "Hello-Vue!",
name:"zy"
}
})
数据单向渲染
- v-bind 指令可以完成基本数据渲染/绑定
- v-bind 简写形式就是一个冒号(:)
1. 使用插值表达式引用 data数据池数据是在标签体内
2. 如果是在标签/元素 的属性上去引用data数据池数据时,不能使用插值表达式
3. 需要使用v-bind, 因为v-bind是vue来解析, 默认报红,但是不影响解析
//<img v-bind:src="img_src" v-bind:width="img_width">
//<img :src="img_src" :width="img_width">
数据双向渲染
-
v-bind是数据单向渲染: data数据池绑定的数据变化,会影响view
-
v-model="hobby.val" 是数据的双向渲染,
- data数据池绑定的数据变化,会影响view 【底层的机制是 Data Bindings】
- view 关联的的元素值变化, 会影响到data数据池的数据【底层机制是Dom Listeners】
//<input type="text" v-model="hobby.val"><br/><br/>
事件绑定
- v-on:click 表示我们要给button元素绑定一个click的事件
- 底层仍然是dom处理
- 如果方法不需要传递参数,可以省略()
- v-on:click可以简写@, 但是需要浏览器支持
<button v-on:click="sayHi()">点击输出</button>
<button @click="sayOk">点击输出</button>
let vm = new Vue({
el: "#app", //创建的vue实例挂载到 id=app的div, el 就是element的简写
data: { //data{} 表示数据池(model中的数据), 有很多数据 ,以k-v形式设置(根据业务需要来设置)
message: "Vue事件处理的案例",
name: "zzz"
},
// 1. 是一个methods属性, 对应的值是对象{}
// 2. 在{} 中, 可以写很多的方法, 你可以这里理解是一个方法池
// 3. 这里需要小伙伴有js的基础=>java web第4章
methods: {
sayHi() {
console.log("hi, 银角大王~");
},
sayOk() {
console.log("ok, 金角大王~");
}
}
})
修饰符
- 修饰符(Modifiers) 是以(.)指明的后缀,指出某个指令以特殊方式绑定
- 例如,.prevent 修饰符告诉v-on 指令对于触发的事件调用event.preventDefault()即阻
止事件原本的默认行为 - 事件修饰符
.stop 阻止事件继续传播
.prevent 阻止标签默认行为
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进
行处理
.self 只当在event.target 是当前元素自身时触发处理函数
.once 事件将只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为 - 键盘事件的修饰符
比如: 项目经常需要监听一些键盘事件来触发程序的执行,而Vue 中允许在监听的时候添
加关键修饰符
- v-model 的修饰符
比如: 自动过滤用户输入的首尾空格
<button v-on:click.once="onMySubmit">点击一次</button><br/>
<input type="text" v-on:keyup.enter="onMySubmit">
<input type="text" v-on:keyup.down="onMySubmit">
<input type="text" v-model.trim="count">
条件渲染
v-if
<input type="checkbox" v-model="sel">是否同意条款[v-if 实现]
<h1 v-if="sel">你同意条款</h1>
<h1 v-else>你不同意条款</h1>
v-show
<input type="checkbox" v-model="sel">是否同意条款[v-show 实现]
<h1 v-show="sel">你同意条款</h1>
<h1 v-show="!sel">你不同意条款</h1>
v-if VS v-show
- v-if 会确保在切换过程中,条件块内的事件监听器和子组件销毁和重建
- v-show 机制相对简单, 不管初始条件是什么,元素总是会被渲染,并且只是对CSS 进行切换
- 使用建议:如果要频繁地切换,建议使用v-show ;如果运行时条件很少改变,使用v-if 较好
列表渲染
Vue 提供了v-for 列表循环指令
1、对数组进行遍历
2、用v-for 来遍历一个对象的property
<h1>简单的列表渲染-带索引</h1>
<ul>
<li v-for="(i,index) in 3">{{i}}-{{index}}</li>
</ul>
<table width="400px" border="1px">
<tr v-for="(monster,index) in monsters">
<td>{{index}}</td>
<td>{{monster.id}}</td>
<td>{{monster.name}}</td>
<td>{{monster.age}}</td>
</tr>
</table>
组件化编程
- 组件(Component) 是Vue.js 最强大的功能之一(可以提高复用性[1.界面2.业务处理])
- 组件也是一个Vue实例,也包括∶ data、methods、生命周期函数等
- 组件渲染需要html模板,所以增加了template 属性,值就是HTML 模板
- 对于全局组件,任何vue 实例都可以直接在HTML 中通过组件名称来使用组件
- data 是一个函数,不再是一个对象, 这样每次引用组件都是独立的对象/数据
全局组件
1、定义一个全局组件, 名称为counter
- {} 表示就是我们的组件相关的内容
- template 指定该组件的界面, 因为会引用到数据池的数据,所以需要是模板字符串
- 要把组件视为一个Vue 实例,也有自己的数据池和methods
- 对于组件,我们的数据池的数据,是使用函数/方法返回[目的是为了保证每个组件的数据是独立], 不能使用原来的方式
- 这时我们达到目前,界面通过template 实现共享,业务处理也复用
- 全局组件是属于所有vue 实例,因此,可以在所有的vue 实例使用
Vue.component("counter", {
template: `<button v-on:click="click()">点击次数= {{count}} 次【全局组件化】</button>`,
data() {//这里需要注意,和原来的方式不一样!!!!
return {
count: 10
}
},
methods: {
click() {
this.count++;
}
}
})
局部组件
- 可以把常用的组件,定义在某个commons.js中 export
- 如果某个页面需要使用, 直接import
const buttonCounter = {//定义一个组件, 组件的名称为 buttonCounter
template: `<button v-on:click="click()">点击次数= {{count}} 次【局部组件化】</button>`,
data() {//这里需要注意,和原来的方式不一样!!!!
return {
count: 10
}
},
methods: {
click() {
this.count++;
}
}
}
//创建Vue实例,必须有
let vm = new Vue({
el: "#app",//Vue实例的挂载点
components: { //引入/注册某个组件, 此时my_counter就是一个组件, 是一个局部组件,他的使用范围在当前vue
'my_counter': buttonCounter
}
})
vue生命周期&钩子函数
- Vue 实例有一个完整的生命周期,也就是说从开始创建、初始化数据、编译模板、挂载
DOM、渲染-更新-渲染、卸载等一系列过程,我们称为Vue 实例的生命周期 - 钩子函数(监听函数): Vue 实例在完整的生命周期过程中(比如设置数据监听、编译模
板、将实例挂载到DOM 、在数据变化时更新DOM 等), 也会运行叫做生命周期钩子的函
数 - 钩子函数的作用就是在某个阶段, 给程序员一个做某些处理的机会
- new Vue()
new 了一个Vue 的实例对象,此时就会进入组件的创建过程。 - Init Events & Lifecycle
初始化组件的事件和生命周期函数 - beforeCreate
组件创建之后遇到的第一个生命周期函数,这个阶段data 和methods 以及dom 结构都未
被初始化,也就是获取不到data 的值,不能调用methods 中的函数 - Init injections & reactivity
这个阶段中, 正在初始化data 和methods 中的方法 - created
- 这个阶段组件的data 和methods 中的方法已初始化结束,可以访问,但是dom 结构未
初始化,页面未渲染 - 在这个阶段,经常会发起Ajax 请求
- 编译模板结构(在内存)
- beforeMount
当模板在内存中编译完成,此时内存中的模板结构还未渲染至页面上,看不到真实的数据 - Create vm.$el and replace ‘el’ with it
这一步,再在把内存中渲染好的模板结构替换至真实的dom 结构也就是页面上 - mounted
此时,页面渲染好,用户看到的是真实的页面数据, 生命周期创建阶段完毕,进入到了运
行中的阶段 - 生命周期运行中
10.1 beforeUpdate
当执行此函数,数据池的数据新的,但是页面是旧的
10.2 Virtual DOM re-render and patch
根据最新的data 数据,重新渲染内存中的模板结构,并把渲染好的模板结构,替换至页面
上
10.3 updated
页面已经完成了更新,此时,data 数据和页面的数据都是新的 - beforeDestroy
当执行此函数时,组件即将被销毁,但是还没有真正开始销毁,此时组件的data、methods
数据或方法还可被调用 - Teardown……
注销组件和事件监听 - destroyed
组件已经完成了销毁
Vue Cli脚手架
项目文件结构
Vue 请求页面执行流程
- 整个页面渲染过程中,main.js 是中心,也是连接各个组件,路由器的关键
通过修改成完整的写法, 清晰思路
D:\vue_project\vue_project_quickstart\src\main.js
import Vue from 'vue'
import App from './App' //完整写法是import App from './App.vue'
import router from './router'//完整写法是import router from './router/index.js'
Vue.config.productionTip = false
new Vue({
el: '#app', //这里的#app 是挂到index.html 的<div id="app"></div>
router, //完整写法是router: router, 第二个router 是import router[这里] from './router'
components: {App }, //完整写法是components: { 'App':App } 因为名字相同可以省略'App'
template: '<App/>' //这里的'<App/>' 的App 就是上面components 引入的组件的名字
ElementUI
ElementUI 官网: https://element.eleme.cn/#/zh-CN
ElementUI 是组件库,网站快速成型工具
Axios
- axios 是独立于vue 的一个项目,不是Vue 的一部分
- axios 通常和Vue 一起使用,实现Ajax 操作
- Axios 是一个基于promise 的HTTP 库
<script>
new Vue({
el: "#app",
data: {
msg: "妖怪信息列表",
monsterList: [] //表示妖怪的信息数组
},
methods: {//自定义方法
list() {//发送ajax请求,获取数据 axios
/*
1. axios.get() 表示发出ajax请求
2. http://localhost:63342/axios/data/response.data.json 表示请求的url
要根据实际情况来填写
3. axios发出ajax请求的基本语法
axios.get(url).then(箭头函数).then(箭头函数)...catch(箭头函数)
(1) 如果get请求成功就进入到第一个then()
(2) 可以再 第一个then()中继续发出axios的ajax请求
(3) 如果有异常, 会进入到 catch(箭头函数)
4. list在生命周期函数created() 中调用-自己去回顾vue的生命周期函数
*/
axios.get("http://localhost:63342/axios/data/response.data.json")
.then(responseData => {
console.log("responseData= ", responseData)
//使用JSON.stringify(json) 把json对象转成一个字符串,方便观察
console.log("responseData= ", JSON.stringify(responseData));
console.log("responseData.data.data.item= ", responseData.data.data.items)
//将妖怪列表数组信息, 绑定到 data数据池的 monsterList
//要学会看返回的数据格式!!!
this.monsterList = responseData.data.data.items;
//可以再次发出ajax请求
// return axios.get("http://localhost:63342/axios/data/response.data.json")
})
// .then(responseData => {
// console.log("第二次axios发出 ajax请求responseData= ", responseData)
// })
.catch(err => {
console.log("异常=", err)
})
}
},
created() {
this.list();
}
})
</script>
将JSON 对象转成JSON.stringify(response)
-
将JSON 对象转成JSON.stringify(response)
-
格式化输出JSON 字符串,方便观察分析
本文学习内容来自韩顺平老师的课程
仅供个人参考学习
标签:总结,Vue,console,log,前端,monster,let,data,name From: https://www.cnblogs.com/zydevelop/p/18107147/zy_frontEnd