我们了解了Angular的特性,再来着手学习可能会对这些特性有更深刻的体会
1.现实生活中的MVC
现实生活中的分工合作案例 – 大饭店
在一些街边小饭馆其实这些事情完全是交给一个人去做 –> 老板
对比一下这两种经营方式的区别
同样的事情两种实现方式
代码中的MVC
实际上,我们写网站的过程,和开饭店的原理和流程极其相似,我们写网站是将数据展示到页面上.
目前,我们写代码都是一股脑的将所有的代码写在一起,这样去写固然没有什么问题,因为你终究还是将效果实现了.
但是这样做的后果是可怕的,随着项目越来越大,你的代码将变得非常难以维护.
为了让我们的代码更加容易的维护,也为了使得我们的代码层次更加分明
我们将我们的代码分为三部分
MVC是由后端而来,由于受到前端技术的限制便有了一些细节的调整,
使用AngularJS构建应用(App)时是以模块化(Module)的方式组织的,即将整个应用划分成若干模块,每个模块都有各自的职责,最终组合成一个整体。
采用模块化的组织方式,可以最大程度的实现代码的复用,可以像搭积木一样进行开发。
通过为任一HTML标签添加ng-app属性,可以指定一个应用,表示此标签所包裹的内容都属于应用(App)的一部分。通俗的理解就是: ng-app属性标签被Angular管理,包括其中的子标签.Angular会认为这是一个应用,将其作为一个应用来看待。
AngularJS提供了一个全局对象angular,调用该全局对象的module()方法可以创建一个模块.该方法返回一个模块对象.这个模块对象就是AngularJS用来管理应用的对象.
<html lang="en" ng-app="myApp">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.js">script>
head>
<body>
<script>
//调用全局对象angular的module方法创建一个模块.
//第一个参数: 模块要管理的标签范围,写上ng-app属性的值.
//第二个参数: 模块依赖,数组,如果不需要依赖,就直接写一个空数组就可以.
//返回值:返回的app对象,我们可以认为这个对象就是ng用来管理这个模块的.
var app = angular.module('myApp',[])
script>
body>
html>
### 3. 定义控制器 接下来我们可以通过app模块对象来创建控制器. 调用app模块对象的controller方法,就可以创建控制器.
var app = angular.module('myApp',[])
//调用app的controller方法创建控制器.
//第一个参数: 要将创建的控制器关联到指定的View上,字符串.
//第二个参数: 这是一个数组,数组的其中一个元素是'$scope',
// 数组的最后一个元素必须是1个function 参数是$scope
// 先把$scope写死,后面学了服务就知道$scope
app.controller('demoCtrl',['$scope',function ($scope) {
}]);
"demoCtrl">
<dl>
<dt>dt>
<dd>dd>
dl>
div>
* 这个时候,创建的控制器,就与这个视图关联起来了 * 在控制器中就可以处理制造数据.视图中就可以显示模型数据了.
数据模型
控制器负责处理制造模型数据.
视图负责显示控制器制造的模型数据.
那么控制器如何制造数据呢?
app.controller('demoCtrl',['$scope',function ($scope) {
//我们可以认为,$scope就是一个数据模型.
//这是一个对象,
//这个对象可以在控制器中访问,也可以在与控制器关联的视图中直接访问.
//那么这个时候,控制器中可以将制造的数据放在这个$scope对象中.
$scope.name = '小明';
$scope.info = '我的名字叫小明,今年18岁';
}]);
这个时候,控制器已经负责将数据模型构建完毕,那么视图中如何展示这些数据呢?
最后,视图中就显示出来数据了
Angular的基本结构
Angular的基本结构和工作流程
- 指定Angular管理的范围 ng-app
- 创建模块 angular.module()
创建角色
- 3.1 创建控制器 app.controller()
- 3.2 指定与控制器关联的视图 ng-controller
控制器制造数据模型并附到 scope scope.xx = yy;
- 在视图中显示模型数据 {{}}
下面举个例子来对Angular的结构有更清晰的认知
<html lang="en" ng-app="ngApp">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.js">script>
head>
<body>
<table ng-controller="stuList">
<tr>
<td>编号td>
<td>姓名td>
<td>性别td>
<td>年龄td>
tr>
<tr ng-repeat="stu in students">
<td>{{stu.id}}td>
<td>{{stu.name}}td>
<td>{{stu.gender}}td>
<td>{{stu.age}}td>
tr>
table>
<script>
//2.创建模块
var app = angular.module('ngApp',[]);
//3.创建MVC角色. 控制器,视图
app.controller('stuList',['$scope',function ($scope) {
//制造数据模型,并绑定到$scope中.
$scope.students = [
{id:1,name:'小明',gender:'男',age:18},
{id:2,name:'小花',gender:'女',age:16},
{id:3,name:'小强',gender:'男',age:14},
{id:4,name:'小东',gender:'男',age:19},
{id:5,name:'小常',gender:'女',age:20}
];
}]);
script>
body>
html>
Angular指令
- HTML在构建应用(App)时存在诸多不足之处
- AngularJS通过扩展一系列的HTML属性或标签来弥补这些缺陷
- 所谓指令就是AngularJS自定义的HTML属性或标签
- 这些指令都是以ng-做为前缀的,例如ng-app、ng-controller、ng-repeat等
1. 内置指令
- ng-app 指定应用根元素 也就是ng管理的范围.可以使用全局对象angular的module方法根据该属性的值创建模块对象.
- ng-controller 指定与控制器关联的视图.
- ng-show 控制元素及其子元素是否显示 true->显示 false->不显示
- ng-hide 控制元素及其子元素是否隐藏 true->隐藏 false->不隐藏
- ng-if 控制元素及其子元素是否创建 true->创建 false->删除.
- ng-src 增强图片路径. src=”“虽然能显示,但是会报错.报错的原因.
- ng-href 增强href路径
- ng-class 值为对象 {red:true} 表示会添加1个red类名到元素身上. {red:false}不会将red类名添加到元素身上.
- ng-include 将外部文件包含进来.一般用在页面的公共部分.被请求的页面是以ajax的方式请求的. ng-inlcude=“‘path’”需要注意的是,给该属性赋值的时候,属性本身的值应该使用双引号引起来,但是其中的路径还要使用单引号引起来.
- ng-disable 是否禁用表单元素
- ng-readonly 是否只读
- ng-checked 原生的checked属性带有歧义,ng补充1个新的指令ng-checked 其值bool类型 true选中 false不选中.
- ng-selected 是否选中
后续还会介绍其他内置指令
2. 自定义指令
- AngularJS不仅提供了功能强大的内置指令.还提供了一套允许我们自定义指令的机制.
- 如果我们觉得ng提供的指令不够强大和好用,我们完全可以定义我们自己的指令.
模块对象app,提供了一个directive方法,这个方法就可以让我们自定义指令.
该方法需要两个参数
第1个参数 指令的名称 - 指令名称不能包含符号,如果指令名称是tag,那么直接使用tag就行. 如果指令是hmTag,那么要使用hm-tag
第2个参数 回调函数
在ng解析这个指令的时候,就会自动去执行这个回调函数.
这个回调函数必须要按照要求返回1个对象.
restrict: ECMA E:Element C:Class M:Mark A:Attribute 自定义指令
replace: bool值,是否替换原有标签.
template: 模板,自定义指令将被替换成什么.
templateUrl:加载外部文件 给一个需要加载的文件的路径.
<html lang="en" ng-app="hmApp">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.js">script>
head>
<body>
<div hm-tag>div>
<script>
var app = angular.module('hmApp',[]);
app.directive('hmTag',function () {
return {
restrict:'EA',
replace:false,
template:'我是中国人,我爱自己的祖国
'
};
});
script>
body>
html>
数据单向绑定
- 所谓的数据的单向绑定,
- 指的就是在控制器中制造数据模型,
- 并将数据模型显示在视图上的过程.
- 数据单向绑定的步骤
- 先在控制器中制造数据
- 在视图中使用绑定符号{{}}将控制器制造的数据模型显示
<html lang="en" ng-app="hmApp">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.js">script>
head>
<body>
<div ng-controller="demoCtrl">
<h1>{{data.name}}h1>
<ul>
<li ng-repeat="item in data.courses">{{item}}li>
ul>
div>
<script>
var app = angular.module('hmApp',[]);
app.controller('demoCtrl',['$scope',function ($scope) {
$scope.data = {
name:"小明",
courses:['html','css','js']
};
}]);
script>
body>
html>
```
需要特别说明的是ng-bind指令和{{}}符号的效果是一样的.
{{}}符号是ng-bind指令的简写形式.
所以,上面的代码也可以这样写.
```js
<div ng-controller="demoCtrl">
<h1 ng-bind="data.name">h1>
<ul>
<li ng-repeat="item in data.courses" ng-bind="item">li>
ul>
div>
虽然ng-bind与{{}}使用效果是一样的,
但是在更多的地方我们仍然使用{{}}的场景更多一些
比如 : 这样的情况使用{{}}就方便的很多了.
“`js
大家好,我叫{{name}},我今年岁{{age}}了
“`
### 解决闪烁的问题
- 虽然{{}}符号和ng-bind指令作用是一样的.但是他们在效果上有一些些区别
- 使用{{}}符号有可能会出现闪烁现行(当吧ng文件的引入放在页面后面的时候)
- 出现闪烁的原因
- 浏览器先渲染html页面.遇到ng指令不认识 原因输出.
- 而后,ng开始工作,渲染绑定数据.
- 所以,中间有这么一个闪烁的过程.
解决闪烁问题
- 第一种方式: 将ng文件的引入放在head标签中.
- 第二种方式: 为标签添加1个ng-cloak指令.就可以解决.
ng-cloak的原理.
- ng会自动的生成1段css代码,将这个标签隐藏.
- 当数据绑定工作完成之后,再显示这个标签.
数据双向绑定
- ng-model指令可以作用于表单元素.
该指令的作用
- 声明1个变量.该属性的值就是声明的变量.
- 这个变量的值和表单元素的值是相互影响的.
- 表单元素的值发生变化,这个变量的值也会跟着发生变化.
- 这个变量的值发生变化,表单元素的值也会跟着发生变化.
- 这样的数据绑定,就叫做双向数据绑定.
<div ng-controller="demoCtrl">
<input type="text" ng-model="val">
<h1>{{val}}h1>
<button ng-click="show()">按钮button>
div>
<script>
var app = angular.module('hmApp',[]);
app.controller('demoCtrl',['$scope',function ($scope) {
$scope.show = function () {
$scope.val = 199;
}
}]);
script>
body>
ng-init指令
数据模型的初始化,我们一般是放在控制器中.
为 scope对象附加属性的方式来追加.然后在视图中就可以直接绑定数据.实际上,Angular还提供了一种更为简便的方式来初始化模型数据.就是使用ng−init属性.使用ng−init初始化的变量在控制器中通过 scope仍然可以访问
“`js
{{name}} {{age}}
var app = angular.module('hmApp',[]);
“`
对于一些复杂的数据模型,比如数组、对象明显在controller中初始化就方便的很多.
Angular事件处理
1 ng-click
原生js中如果需要绑定点击事件,在html元素中使用onclick属性,
指定该属性的值为一个函数调用.
那么这个函数就是全局的函数,会造成全局污染.
“`js
function login(){
.......
}
登录
“`
Angular为了解决这个问题,使用了1个ng指令ng-click
这是Angular中的点击指令,为其赋值一个函数,不同的是,这个函数需要在控制器中绑定到$scope对象中.
那么这个函数就不再是一个全局的函数,就不存在全局污染的问题了.
"demoCtrl">
<button ng-click="login()">按钮button>
div>
<script>
var app = angular.module('hmApp',[]);
app.controller('demoCtrl',['$scope',function ($scope) {
//在控制器中为$scope绑定login函数.这样在视图中就可以调用
$scope.login = function () {
alert("正在登录,请稍后.....");
}
}]);
script>
2 ng-moueover
其实原理是一样的,这是ng的指令 用来绑定鼠标移入事件.
"demoCtrl">
<button ng-click="login()">按钮button>
<input type="text" ng-mouseover="mover()">
div>
<script>
var app = angular.module('hmApp',[]);
app.controller('demoCtrl',['$scope',function ($scope) {
$scope.login = function () {
alert("正在登录,请稍后.....");
};
$scope.mover = function () {
console.log("鼠标移入了.....");
}
}]);
script>
其他事件指令
其实到这里我们可以总结出一些规律了
Angular的事件指令是在原生指令属性的基础之上把on去掉替换为ng-
onclick –> ng-click
onmouseover –> ng-mouseover
onkeyup –> ng-keyup
……
数据处理
1 ng-repeat
用于循环生成标签,并绑定数据.
可以遍历数组.
<html lang="en" ng-app="hmApp">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.min.js">script>
head>
<body>
<div ng-controller="processDataView">
<ul>
<li ng-repeat="item in lessons">{{item}}li>
ul>
div>
<script>
var app = angular.module('hmApp',[]);
app.controller('processDataView',['$scope',function ($scope) {
$scope.lessons = ['html','css','js','jQuery'];
}])
script>
body>
html>
可以遍历对象
<html lang="en" ng-app="hmApp">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.min.js">script>
head>
<body>
<div ng-controller="processDataView">
<ul>
<li ng-repeat="item in lessons">li>
ul>
<table>
<tr>
<td ng-repeat="item in student">{{item}}td>
<td ng-repeat="(key,item) in student">{{key}}:{{item}}td>
tr>
table>
div>
<script>
var app = angular.module('hmApp',[]);
app.controller('processDataView',['$scope',function ($scope) {
$scope.lessons = ['html','css','js','jQuery'];
$scope.student = {
name:"杰克",
age:18,
gender:"男"
};
}])
script>
body>
html>
ng-switch
该指令的作用与js的switch-case非常相像
判断变量的值,如果变量的之与列出的任意1个值相等,则执行其中的逻辑.
语法规则
<li ng-repeat="item in list" ng-switch on="item">
<span ng-switch-when="html">我喜欢htmlspan>
<p ng-switch-when="ccs">哈哈cssp>
<a ng-switch-when="js" href="#">哦jsa>
<span ng-switch-when="jQuery">like jQueryspan>
<span ng-switch-default>啦啦啦,没有匹配到span>
li>
也可以下面这样使用
<html lang="en" ng-app="hmApp">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.min.js">script>
head>
<body>
<div ng-controller="demoCtrl" ng-switch="name">
<div ng-switch-when="rose">Rosediv>
<div ng-switch-when="jack">Jackdiv>
<div ng-switch-when="lily">Lilydiv>
<div ng-switch-when="poly">Polydiv>
<div ng-switch-default>默认选项div>
div>
<script>
var app = angular.module('hmApp',[]);
app.controller('demoCtrl',['$scope',function ($scope) {
$scope.name = "jack";
}]);
script>
body>
html>
Angular作用域
1.相互独立的视图/控制器,他们的作用域($scope)也是相互独立,互不影响的.
<html lang="en" ng-app="hmApp">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.min.js">script>
head>
<body>
<div ng-controller="demoCtrl1">div>
<div ng-controller="demoCtrl2">div>
<div ng-controller="demoCtrl3">div>
<script>
var app = angular.module('hmApp',[]);
app.controller('demoCtrl1',['$scope',function ($scope) {
$scope.name = "杰克";
//为第1个控制器/视图的$scope绑定了一个数据.
//这个数据只能在与当前控制器相互关联的视图上访问.
//不能在与别的控制器关联的视图上访问.
}]);
app.controller('demoCtrl2',['$scope',function ($scope) {
}]);
app.controller('demoCtrl3',['$scope',function ($scope) {
}]);
script>
body>
html>
2.父子关系的视图所对应的控制器,在子视图/控制器中
可以访问父视图/控制器中$scope的数据.
* 子视图中可以访问父视图中的数据.
* 父视图不能访问子视图中的数据.
* 如果访问的变量在视图中有定义,那么优先访问子视图自己的,
如果该变量子视图自己没有定义,
这个时候再去尝试查找父视图中是否有定义这个数据.
<html lang="en" ng-app="hmApp">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.min.js">script>
head>
<body>
<div ng-controller="fatherCtrl">
<div ng-controller="sonCtrl">
<span>在子视图中访问父视图作用域中的数据: span>
div>
div>
<script>
var app = angular.module('hmApp',[]);
app.controller('fatherCtrl',['$scope',function ($scope) {
$scope.name = "杰克";
}]);
app.controller('sonCtrl',['$scope',function ($scope) {
}]);
script>
body>
html>
3.根作用域
- 我们可以使用ng-init指令来初始化一个变量,在父级元素上.
- 那么这个父级元素下所有的子视图都可以访问.
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="lib/angular.min.js">script>
head>
<body>
<div ng-app="hmApp" ng-init="name='jack'">
<div ng-controller="demoCtrl1">div>
<div ng-controller="demoCtrl2">div>
<div ng-controller="demoCtrl3">div>
div>
<script>
var app = angular.module('hmApp',[]);
app.controller('demoCtrl1',['$scope',function ($scope) {
}]);
app.controller('demoCtrl2',['$scope',function ($scope) {
}]);
app.controller('demoCtrl3',['$scope',function ($scope) {
}]);
script>
body>
html>
过滤器
在AngularJS中使用过滤器格式化展示数据,
在“{{}}”中使用“|”来调用过滤器,使用“:”传递参数。
1.内置过滤器
日期过滤器 {{now|date:’yyyy-MM-dd hh:mm:ss’}}
货币符号过滤器 {{price|currency}}
- 默认是美元符号
- 如果想要显示其它货币符号
{{price|currency:'¥'}}
这样显示的就是人民币符号
保留指定位数的小树
{{price|currency:'¥':2}}
这样显示的就是人民币符号 并四舍五入保留两位小数
转换为大写
{{info|uppercase}}
将info的值转换为大写显示
转换为大写
{{info|lowercase}}
将info的值转换为大写显示
截取数组/字符串中指定位数的元素.
{{arr|limitTo:n}}
正数从左往右截取n个元素
负数从右往左截取n个元素
arr可以是1个数组
也可以是一个字符串.字符串的话就截取每一个字符.
处理数字的过滤器number可以控制小数的位数
{{num|number:2}} 保留2位小数输出.
json过滤器 将对象转换为json字符串
{{obj|json}} 将obj对象转换为json字符串输出.
实际上,不使用json过滤器它也会这么做.
orderBy过滤器
{{students|orderBy:'score'}}
将student数组中的对象以score属性为参照进行排序 默认是升序.
{{students|orderBy:'score':true}} 降序
filter 过滤器
{{students|filter:{score:90} }}
过滤出数组中对象分数等于90的对象.
特别注意: 参数对象必须和后面的大括弧有一个空格.
自定义过滤器
模块对象提供了一个filter方法,允许我们自定义过滤器.
该方法的参数说明
- 第一个参数: 过滤器名称
第二个参数: 回调函数 - 当执行我们自定义的过滤器的时候,就会自动来执行这个回调函数.
"demoCtrl">
{{name|highven}}
</div>
$interval服务
作用:每隔指定的时间就做指定的事情.
<div ng-controller="demoCtrl">
<p>现在时间是:{{now|date:'yyyy-MM-dd HH:mm:ss'}}p>
div>
<script>
var app = angular.module('hmApp', []);
app.controller('demoCtrl', ['$scope','$interval', function ($scope,$interval) {
$interval(function () {
$scope.now = new Date();
},1000);//每隔一秒钟就为now重新赋值.
}]);
script>
body>
interval函数,返回1个数据.调用 interval.cancel(返回值)可以停止计时器
<div ng-controller="demoCtrl">
<p>现在时间是:{{now|date:'yyyy-MM-dd HH:mm:ss'}}p>
<button ng-click="stop()">停止button>
div>
<script>
var app = angular.module('hmApp', []);
app.controller('demoCtrl', ['$scope','$interval', function ($scope,$interval) {
var stop = $interval(function () {
$scope.now = new Date();
},1000);
$scope.stop = function () {
$interval.cancel(stop);
}
}]);
script>
body>
$filter服务
作用:格式化数据 filter这是1个函数狭义的解释:在控制器中格式化数据.过滤器在视图中格式化数据,而 filter在控制器中格式化数据.
用法实际上,和过滤器用法非常相像.并且$filter服务支持自定义过滤器
var item = $filter('过滤器名称'); //返回值是1个函数.
var val = item(待格式化的数据,过滤器参数...);//返回值就是过滤器格式化后的数据
案例演示 :
<div ng-controller="demoCtrl">
<ul>
<li><span>现在时间是:{{now}}span>li>
<li><span>{{intro}}span>li>
ul>
div>
<script>
var app = angular.module('hmApp', []);
app.controller('demoCtrl', ['$scope','$filter',function ($scope,$filter) {
//传入date 返回的函数就是和处理日期相关的函数
var date = $filter('date');
//第1个参数: 待处理的日期数据
//第2个参数: 过滤器参数
//返回值:被处理过的数据
$scope.now = date(new Date,'yyyy-MM-dd HH:mm:ss');
var uCase = $filter('uppercase');
$scope.intro = uCase('this is itcast!');
}]);
script>
body>
$http服务
1.$http服务基本使用
作用:用于向服务器发送异步请求 这是1个函数.
参数:传入1个符合条件的对象.
* url:请求地址
* method:请求方式get/post
* data:{} 当请求方式为post的时候 使用data传递数据.
* params:{} 当请求方式为get的时候,使用params传递数据.
* headers:{} 设置请求头信息
* .then()函数传入两个回调函数.
* 第1个回调,当请求成功后执行,参数info。通过info.data拿到返回的数据
* 第2个回调,当请求失败后执行.
案例演示
<div ng-controller="demoCtrl">
<table>
<tr>
<th>姓名th>
<th>年龄th>
tr>
<tr ng-repeat="stu in data"><td>{{stu.name}}td><td>{{stu.age}}td>tr>
table>
div>
<script>
var app = angular.module('hmApp', []);
app.controller('demoCtrl', ['$scope','$http',function ($scope,$http) {
$http({
url:'example.php',
method:'get'
}).then(function (info) {
$scope.data = info.data;
},function () {
});
}]);
script>
body>
2. $http服务传递数据的 格式
当请求类型是get时,向服务器传递的数据必须要放在params中,以对象的形式.
get请求发送的数据格式是 queryString类型的
"loginCtrl">
用户名: <input type="text" ng-model="uName">
<br>
密码: <input type="password" ng-model="pwd">
<br>
<button ng-click="login()">登录button>
div>
<script>
app.controller('loginCtrl', ['$scope', '$http',function ($scope,$http) {
$scope.login = function () {
$http({
url:'login.php',
method:'get',
params:{
uName:$scope.uName,
pwd:$scope.pwd
}
}).then(function (info) {
}).catch(function () {
});
}
}]);
script>
当请求类型是post时,向服务器发送的数据必须要放在data中.
设置发送数据的类型.
data要以键值对的字符串形式.
客户端以post方式向服务器发送数据时.必须要指定发送数据的类型.
因为不同的数据格式在服务器处理的方式是不一样的.
常见的数据格式
application/json –> 这种格式叫做formdata
application/x-www-form-urlencoded –> 这种格式叫做payload
jQuery的Ajax默认的数据类型就是application/x-www-form-urlencoded
AngularJS的$http默认支持的是application/json
$http跨域
基于浏览器的同源策略,Ajax异步是无法实现跨域请求的.
实现跨域请求的解决方案
* src属性是天然支持跨域的.
* 所以,我们可以利用script标签的src属性来跨域请求数据.
* 如果使用script标签的src属性来跨域请求,会将请求回来的数据作为js代码执行.
3.1 AngularJS的跨域
AngularJS已经帮我们封装好了一切.
$http({
url:'',//请求地址
method:'jsonp', //jsonp就可以(只支持不带有 . 的callback接口)
params:{
//传递额外数据.
}
}).then(function(info){
}).catch(function(){
});
自定义服务
1. 使用factory自定义服务
模块对象有一个方法叫做factory允许我们自定义服务.
该方法需要两个参数
- 第1个参数,服务名称.
- 第2个参数,是1个数组,前面的元素写依赖,最后一个元素是回调函数.
你可能感兴趣的:(前端框架)