Luwang
文章54
标签57
分类25

UV
PV
wallleap
每日一句
:D 获取中...

欣赏音乐
文章列表
Vue学习笔记

Vue学习笔记

一、Vue概述

1.1 前言

1.1.1 前端开发模式的发展

  1. 静态页面
  • 最初的网页以HTML为主,是纯静态的网页。网页是只读的,信息流只能从服务端到客户端单向流通。开发人员也只关心页面的样式和内容。
  1. 异步刷新,操作DOM
  • 1995年,网景工程师Brendan Eich花了10天时间设计了JavaScript语言。
    • 随着JavaScript的诞生,我们可以操作页面的DOM元素及样式,页面有了一些动态地效果,但是依然是以静态为主。
  • ajax盛行:
    • 2005年开始,ajax逐渐被前端开发人员所重视,因为不用刷新页面就可以更新页面的数据和渲染效果。
    • 此时的开发人员不仅需要编写HTML样式,还要动ajax与后端交互,然后通过js操作DOM元素来实现页面动态效果。比较流行的框架如jQuery就是典型代表。
  1. MVVM,关注模型和视图
  • 2008年,Google的Chrome发布,随后就以极快的速度占领市场,超过IE称为浏览器市场的主导者。
  • 2009年,Ryan Dahl在谷歌的Chrome V8引擎基础上,打造了基于事件循环的异步IO框架:Node.js。
    • 基于事件循环的异步IO
    • 单线程运行,避免多线程的变量同步问题
    • js可以编写后台diamante,前后台统一编程语言
  • Node.js的伟大之处不在于让js迈向了后端开发,而是构建了一个庞大的生态系统。
  • 2010年,NPM作为node.js的包管理系统首次发布,开发人员可以遵循CommonJS规范来编写Node.js模块,然后发布到NPM上供其他开发人员使用。目前已经是世界上最大的包模块管理系统。
  • 随后,在Node的基础上,涌现了一大批的前端框架:

image-20200827092403465

1.1.2 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上。

MVVM通过视图模型双向绑定,简化前端操作。

Vue就是一款MVVM模式的框架

1.2 认识Vue

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

  • 前端框架三巨头:Vue.js、React.js、Angular.js,Vue.js以其轻量易用著称,Vue.js和React.js发展速度最快。
  • 遵循 MVVM 模式
  • 渐进式 JavaScript 框架
    • 渐进式:可以选择性地使用该框架的一个或一些组件,这些组件的使用也不需要将框架全部组件都应用,而且用了这些组件也不要求你的系统全部都使用该框架。
  • 中文官网:https://cn.vuejs.org/
  • 英文官网:https://vuejs.org/
  • 参考:https://cn.vuejs.org/v2/guide/
  • 作用: 动态构建用户界面
  • 特点:
    • 易用
    • 灵活
    • 高效
  • 编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发
  • 它本身只关注 UI, 可以轻松引入 vue 插件或其它第三库开发项目
  • GitHub地址:https://github.com/vuejs
  • 作者:尤雨溪(一位华裔前 Google 工程师),Vue.js创作者,Vue Technology创始人,致力于Vue的研究开发。

1.3 与其它前端JS框架的关联

  • 借鉴 angular 的模板数据绑定技术
  • 借鉴 react 的组件化虚拟 DOM 技术

1.4 Vue扩展插件

  • vue-cli: vue 脚手架
  • vue-resource(axios): ajax 请求
  • vue-router: 路由
  • vuex: 状态管理
  • vue-lazyload: 图片懒加载
  • vue-scroller: 页面滑动相关
  • mint-ui: 基于 vue 的 UI 组件库(移动端)
  • element-ui: 基于 vue 的 UI 组件库(PC 端)

二、快速入门

搭建示例工程

2.1创建工程

创建一个目录,例如:testVue

2.2 引入Vue

  • npm安装(推荐)

    • 进入工程目录:cd testVue
    • 初始化项目:npm init -y
    • 下载安装Vue:npm install vue --save

2.3、演示双向绑定与事件处理

需求:创建testVue.html页面并初始化Vue实例,通过console修改Vue数据实现双向绑定效果和创建按钮实现点击自增效果。

  • 创建页面,初始化Vue
  • { {} }获取显示数据
  • v-model实现双向绑定
  • v-on演示事件处理

在刚刚testVue目录中新建testVue.html文件,书写代码,测试初始化Vue实例、{ {} }使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <!-- 引入vue.js
        1. cdn
        2. 下载后本地引入
        3. npm安装后从node_modules中引入(项目中import引入)
    -->
    <script src="node_modules/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2>{{name}}</h2>  <!-- 插值 -->
        <p>渐进式JavaScript框架</p>
    </div>
    <script type="text/javascript">
        /* 创建Vue实例,在里面可以指定一些vue的参数 */
        var app = new Vue({ // 键值对
            el: "#app", // 指定需要渲染的元素
            data:{ // 指定数据
                name: "Vue.js"
            }
        })
    </script>
</body>
</html>

在控制台输入命令:

app.name="Vue"

Vue.js也会变成Vue

测试v-model

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <input type="text" v-model="num"> <!-- 模型 -->
        <h2>{{name}} 创建 {{num}} 年了</h2>
    </div>
    <script type="text/javascript">
        var app = new Vue({
            el: "#app",
            data:{
                name: "Vue.js",
                num: 5
            }
        })
    </script>
</body>
</html>

修改文本框中的值,下面的xx年也会变(模型改变,视图也改变;视图改变也会影响对应的模型),控制台中也能修改

上面的演示的是双向绑定,下面接着演示事件处理

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <input type="text" v-model="num"> <!-- 模型 -->
        <h2>{{name}} 创建 {{num}} 年了</h2>
        <button v-on:click="num++">num+1</button>
    </div>
    <script type="text/javascript">
        var app = new Vue({ 
            el: "#app",
            data:{
                name: "Vue.js",
                num: 5
            }
        })
    </script>
</body>
</html>

点击按钮,num会自增

三、Vue实例

在创建Vue实例的时候可以指定模板id、数据和方法;而如果要在实例化、模板渲染的过程中需要执行一些其他操作的话,那么可以使用生命周期钩子函数

3.1 创建Vue实例

每个Vue应用都是通过用Vue函数创建一个新的Vue实例开始的(常用变量vm——ViewModel接收)

var vm = new Vue({ // 传入的参数——对象
  // 选项
})

在构造函数中传入一个对象,并且在对象中声明各种Vue需要的数据和方法,包括:

  • el:指定一个页面元素,受Vue实例的管理,只有被Vue实例管理的元素内部才能使用Vue的语法。

  • data:定义Vue实例中使用到的数据,本身就是一个对象,里面的键值对可以随意写。使用时可以直接vm.msg使用这个数据(不需要vm.data.msg),在声明周期钩子函数中使用this.msg

  • methods:定义Vue实例的一些函数

  • computed:计算属性,可以将一些属性数据经过方法处理之后返回。

  • watch:监控属性,可以指定一些方法,监控指定的值的变化。

    • 监控简单数据:定义一个和监控的变量名称一致的函数即可,函数的参数为新值和旧值。例如,要监控data中的message,在watch中message(newValue,oldValue){}

    • 监控对象中的数据——深度监控:定义一个和监控的对象名称一致的属性,值是一个对象。内部设置deep属性为true代表深度监控开启,回调函数为handler,会传过来新的对象,例如:

      watch:{
        person: {
          /* 开启深度监控,监控对象中的属性值变化 */
          deep: true,
          // 可以获取到最新的对象属性数据
          handler(obj){
           console.log("姓名:" + obj.name + ",年龄:" + obj.age)
        }
      }
  • template

  • 声明周期钩子函数名(ES6语法created: function(){}简化为created(){}),本质仍是键值对

3.2 模板或元素

每个Vue实例都需要关联一段HTML模板,Vue会基于此模板进行视图渲染;可以通过el属性来指定。

例如一段HTML模板:

<div id="app">

</div>

然后创建Vue实例,关联这个div

var vm = new Vue({
  el: "#app"
})

这样,Vue就可以基于id为app的div元素作为模板进行渲染了,在这个div范围以外的部分是无法使用vue特性的。

3.3 数据

当Vue实例被创建时,它会尝试获取在data中定义的所有属性,用于视图的渲染,并且监视data中的属性变化,当data发生改变,所有相关的视图都将重新渲染,这就是“响应式”系统。

例如在HTML中模型指定name

<div id="app">
  <input type="text" v-model="name" />
</div>

js中vue的data属性设置一个name

var vm = new Vue({
  el: "#app",
  data:{
    name: "Vue.js"
  }
})
  • name的变化会影响到input的值
  • input中输入的值,也会导致vm中的name发生改变

3.4 方法

Vue实例中除了可以定义data属性,也可以定义方法,并且在Vue的作用范围内使用。

例如HTML模板中指定v-on事件click之后执行的代码(可以是一个语句也可以是个函数)

<div id="app">
  <button v-on:click="add">add</button>
</div>

在创建实例时到methods属性中定义add方法

var vm = new Vue({
  el: "#app",
  data:{},
  methods:{
    add: function(){
      console.log("add被点击了")
    }
  }
})

3.5 声明周期及钩子函数

3.5.1 声明周期

每个Vue实例在被创建时都要经过一系列的初始化过程——创建实例、装载模板、渲染模板等,Vue为生命周期中的每个状态都设置了钩子函数(监听函数),每当Vue实例处于不同的声明周期时,对应的函数就会被触发调用。

所有的生命周期钩子自动绑定this上下文到实例中,因此你可以访问数据,对属性和方法进行运算。这意味着你不能使用箭头函数来定义一个声明周期方法(比如 created: () => console.log(this.a)vm.$watch('a', newValue => this.myMethod())),因为箭头函数的 this与你期待的Vue实例不同,this 会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function 之类的错误。

声明周期:

实例化——>挂载——>销毁 (如果使用构建步骤,模板编译会提前执行,例如单文件组件)

钩子函数会在Vue实例的各个声明周期阶段自动调用,具体有:

(1) 初始化显示:

  • beforeCreate:在实例初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用。
  • created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event事件回调。然而,挂载阶段还没开始,$el属性目前不可见。
  • beforeMount:在挂载开始之前被调用。相关的render函数首次被调用。
  • mounted:el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素。当mounted被调用时vm.$el也在文档内。[vm.$el:Vue实例使用的根DOM元素]

(2) 更新状态:(this.xxx=value)

  • beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前,这里适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器。
  • updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。当这个 钩子被调用时,组件DOM已经更新,所以此时可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,如果要相应状态改变,通常最好使用计算属性或watcher取而代之。

(3) 销毁Vue实例:(vm.$destory())

  • beforeDestroy:实例在销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed:Vue实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

[vm.$root:当前组件树的根Vue实例,如果当前实例没有父实例,此实例将会是其自己]

3.5.2 钩子函数

例如:created代表在Vue实例创建后

可以在Vue中定义一个created函数,代表这个时期的构造函数:

创建页面vueLifCycle.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js声明周期钩子created测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2>{{msg}}</h2>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                msg: ""
            },
            // 钩子函数
            created(){
                this.msg = "Hello Vue.js created"
                console.log(this)
            }
        })
    </script>
</body>
</html>

created常用于数据的初始化,即发送ajax,获取后台数据,给data属性中的数据进行赋值,接着在模板中应用数据,视图也会发生改变,从而实现数据的异步加载。

mounted发送 ajax 请求, 启动定时器等异步任务

beforeDestory做收尾工作, 如: 清除定时器

3.5.3 this

打印的this(这个Vue实例对象)如下:

image-20200827155038198

四、指令

指令(Directives)是带有v-前缀的特殊属性。例如在入门案例中的v-model,代表双向绑定。

4.1 插值表达式

4.1.1 大括号

  • 格式:

  • 说明:

    • 该表达式支持JS语法,可以调用js内置函数(必须有返回值)
    • 表达式必须有返回结果。例如1+1,没有结果的表达式不允许使用,如var a = 1 + 1
    • 可以直接获取Vue实例data中定义的数据或函数
    • 可以直接写""
  • 示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js插值表达式测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2>{{msg}}</h2>
    <h2>{{"说的没错"}}</h2>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                msg: "Vue.js 轻便好用"
            }
        })
    </script>
</body>
</html>

4.1.2 插值闪烁

使用{ {} }方式在网速较慢的时候会出现问题,在数据未加载完成时,页面会显示出原始的,加载完毕后才显示正确数据,这种情况称为插值闪烁(在最新的Vue中几乎没有这个问题)。

在以前版本中,出现插值闪烁,可以使用v-text、v-html解决。

4.1.3 v-text和v-html

  • 使用v-text和v-html指令来代替{ {} }

  • 说明:

    • v-text:将数据输出到元素内部,如果输出的数据有HTML代码,会作为普通文本输出
    • v-html:将数据输出到元素内部,如果输出的数据有HTML代码,会被渲染
  • 示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js v-html/v-text测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2 v-text="msg"></h2>
        <h2 v-html="msg"></h2>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                msg: "<span style='color: red;'>Vue.js 很好用</span>"
            }
        })
    </script>
</body>
</html>

image-20200827164954008

插值可以使用在需要显示Vue实例数据的地方,可以在插值表达式中调用实例的属性和函数。

v-text和v-html的作用:可以将数据在模板中进行显示;区别:v-html会对内容中出现的HTML标签进行渲染,而v-text会将内容当作普通文本输出到元素里面。(有点像js中的innerHTML和innerText)

4.2 v-model

刚才的v-text和v-html可以看做是单向绑定,数据影响了视图渲染,但是反过来就不行。接下来的v-model就是双向绑定的了,视图(View)和模型(Model)之间会相互影响。

既然是双向绑定,一定是在视图中可以修改数据,这样就限定了视图的元素类型。目前v-model可以使用的元素有:

  • input
  • select
  • textarea
  • checkbox
  • radio
  • components(Vue中的自定义组件)

基本上除了最后一项,其它都是表单的输入项。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js v-model测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <label><input type="checkbox" value="C/C++" v-model="language" />C/C++ </label><br>
        <label><input type="checkbox" value="Java" v-model="language" />Java </label><br>
        <label><input type="checkbox" value="PHP" v-model="language" />PHP </label><br>
        <h2>你选择了:{{language.join(", ")}}</h2>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                language: []
            }
        })
    </script>
</body>
</html>

demo

  • 多个checkbox对应一个model时,model的类型是一个数组,单个checkbox值是boolean类型
  • radio对应的值是input的value值
  • inputtextarea默认对应的model是字符串
  • select单选对应字符串,多选对应也是数组

补充:插件Vue.js devtools

https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?utm_source=chrome-ntp-icon

4.3 v-on

4.3.1 基本用法

在没有使用Vue之前,页面标签可以通过设置onXXX响应事件;在Vue中可以通过v-on指令响应事件(给页面元素绑定事件)。

语法:v-on:事件名="js片段或函数名"

简写语法:@事件名="js片段或函数名"

例如v-on:click="add"可以简写为@click="add"

示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js v-on测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <button v-on:click="num++">增加</button>
        <button @click="decrement">减少</button>
        <h2>num的数值为 {{num}}</h2>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                num: 1
            },
            methods: {
                decrement: function(){
                    this.num--
                }
            },
        })
    </script>
</body>
</html>

on

默认事件形参: event
隐含属性对象: $event

4.3.2 事件修饰符

在事件处理程序中调用event.preventDefault()event.stopPropagation()是非常常见的需求(阻止默认事件)。尽管我们可以在方法中轻松实现这点,但方法只有纯粹的数据逻辑,而不是去处理DOM事件细节。

为了解决这个问题,Vue.js为v-on提供了事件修饰符。修饰符是由点开头的指令后缀来表示的。

语法:v-on:事件名.修饰符="js片段或函数名"@事件名.修饰符="js片段或函数名"

常用修饰符:

  • .stop:阻止事件冒泡
    • 事件冒泡:默认情况下,在某个页面元素上触发的事件,在当前元素处理完之后会自动传递给祖先元素,祖先的相同事件也会执行
  • .prevent:阻止默认事件发生
    • 浏览器默认的一些事件行为,例如:
      • 获取焦点事件会把光标放入输入框
      • 表单提交事件会提交数据到action指定的url
      • 点击a标签会跳转到href指定的地址
  • .capture:使用事件捕获模式
    • 相当于和冒泡相反,父元素先于子元素获取事件
  • .self:只有元素自身触发事件才执行(冒泡或捕获的都不执行)
  • .once:只执行一次

冒泡测试:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2>事件冒泡测试</h2>
        <div v-on:click="print('点击了div')" style="background: skyblue; width: 400px; height: 400px;">
            <button @click="print('点击了button')">按钮</button>
        </div>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                num: 1
            },
            methods: {
                print(str){
                    console.log(str)
                }
            },
        })
    </script>
</body>
</html>

bubble

阻止冒泡:

<div id="app">
    <h2>阻止事件冒泡测试</h2>
    <div v-on:click="print('点击了div')" style="background: skyblue; width: 400px; height: 400px;">
        <button @click.stop="print('点击了button')">按钮</button>
    </div>
</div>

stopbubble

阻止默认事件:点击超链接不会跳转

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2>阻止默认事件</h2>
        <a href="https://www.baidu.com" @click.prevent="print('点击了a')">百度</a>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                num: 1
            },
            methods: {
                print(str){
                    console.log(str)
                }
            },
        })
    </script>
</body>
</html>

4.3.3 按键修饰符

  • .keycode : 操作的是某个 keycode 值的键
  • .keyName : 操作的某个按键名的键(少部分)
<h2>3. 按键修饰符</h2>
<input @keyup.8="test"> <!-- 按键8 -->
<input @keyup.enter="test"> <!-- 按键enter -->
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
  el: '#example',
  data: {
      name: 'Vue.js'
  },
  methods: {
    test(event) {
          alert(event.keyCode + '---' + event.target.value)
      }
  }
})
</script>

4.4 v-for

可以在Vue实例化的时候指定要遍历的数据,然后通过v-for指令在模板中遍历显示数据。一般情况下,要遍历的数据可以通过钩子函数created发送异步请求获取数据。

4.4.1 遍历数组

  • 语法:v-for="item in items"
    • items:要遍历的数组名或对象名,需要在Vue的data中定义好。
    • item:循环遍历
  • 示例:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="user in users">
                我是{{user.name}},今年{{user.age}}岁了,性别{{user.gender}}
            </li>
        </ul>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                users:[
                    {"name":"唐僧","age":24,"gender":"男"},
                    {"name":"孙悟空","age":26,"gender":"男"},
                    {"name":"猪八戒","age":30,"gender":"男"},
                    {"name":"沙和尚","age":32,"gender":"男"},
                    {"name":"蜘蛛精","age":18,"gender":"女"}
                ]
            }
        })
    </script>
</body>
</html>

image-20200827200311325

4.4.2 数组角标

在遍历的过程中,如果需要知道数组角标/索引号,可以指定第二个参数

  • 语法:v-for="(item,index) in items"
    • items:要遍历的数组
    • item:遍历得到的数组元素别名
    • index:遍历到的当前元素索引,从0开始
  • 示例:
<div id="app">
    <ul>
        <li v-for="(user, index) in users">
            {{index}}:我是{{user.name}},今年{{user.age}}岁了,性别{{user.gender}}
        </li>
    </ul>
</div>

image-20200827200727402

4.4.3 遍历对象

v-for除了可以迭代数组,也可以迭代对象,语法基本类似

语法:

v-for="value in object"
v-for="(value,key) in object"
v-for="(value,key,index) in object"
  • 1个参数时,得到的是对象的值
  • 2个参数时,第一个是值,第二个是键
  • 3个参数时,第三个是索引,从0开始

示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="(value, key, index) in person">
                {{index}}——{{key}}——{{value}}
            </li>
        </ul>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                person:{"name":"孙悟空","age":26,"gender":"男","address":"花果山"}
            }
        })
    </script>
</body>
</html>

image-20200827201525393

4.4.4 key

当Vue.js用v-for正在更新已渲染过的元素列表时,它默认用”就地复用”策略。如果数据项的顺序被改变,,Vue 将不会移动DOM元素来匹配数据项的顺序,而是简单复用此处每个元,并且确保它在特定索引下显示已被渲染过的每个元素。
如果使用key这个功能可以有效的提高渲染的效率; key一般使用在遍历完后,还要增、减集合元素的时候更有意义。
但是要实现这个功能,你需要给Vue-些提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一key 属性。理想的key值是每项都有的且唯一的id。也就是key是该项的唯一标识。

示例:

    <ul>
        <li v-for="(user, index) in users" :key="index">
            {{index}}:我是{{user.name}},今年{{user.age}}岁了,性别{{user.gender}}
        </li>
    </ul>

这里使用了一个特殊的语法::key="",它可以读取Vue中的属性,并赋值给key属性

这里绑定的key是数组的索引,是唯一的(以后可以加其他的唯一的数据,例如user.id)

4.5 v-if和v-show

4.5.1 基本使用

v-if,条件判断,当得到的结果为true时,所在的元素才会被渲染。

语法:v-if="布尔表达式"

示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <button @click="show=!show">按钮</button>
        <h2 v-if="show">Hello Vue.js</h2>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                show: true
            }
        })
    </script>
</body>
</html>

4.5.2 与v-for结合

当v-if和v-for一起出现的时候,v-for优先级更高,即会先遍历,再判断条件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2>女性人物</h2>
        <ul>
            <li v-for="(user, index) in users" v-if="user.gender=='女'">
                {{index}}:我是{{user.name}},今年{{user.age}}岁了,性别{{user.gender}}
            </li>
        </ul>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                users:[
                    {"name":"唐僧","age":24,"gender":"男"},
                    {"name":"孙悟空","age":26,"gender":"男"},
                    {"name":"猪八戒","age":30,"gender":"男"},
                    {"name":"沙和尚","age":32,"gender":"男"},
                    {"name":"蜘蛛精","age":18,"gender":"女"}
                ]
            }
        })
    </script>
</body>
</html>

4.5.3 v-else

可以使用v-else指令来表示v-if的“else块”,需要注意,v-else元素必须紧跟在带有v-if或v-else-if的元素的后面,否则它将不会被识别(两者之间不能插入其他元素)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2>西游人物</h2>
        <ul>
            <li v-for="(user, index) in users" v-if="user.gender=='女'" style="color:pink;">
                {{index+1}}:我是{{user.name}},今年{{user.age}}岁了,性别{{user.gender}}
            </li>
            <li v-else style="color:skyblue;">
                {{index+1}}:我是{{user.name}},今年{{user.age}}岁了,性别{{user.gender}}
            </li>
        </ul>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                users:[
                    {"name":"唐僧","age":24,"gender":"男"},
                    {"name":"孙悟空","age":26,"gender":"男"},
                    {"name":"猪八戒","age":30,"gender":"男"},
                    {"name":"沙和尚","age":32,"gender":"男"},
                    {"name":"蜘蛛精","age":18,"gender":"女"}
                ]
            }
        })
    </script>
</body>
</html>

v-else-if,充当v-if的”else-if块“,可以连续使用(v-else-if也必须紧跟在带v-if或v-else-if的元素后)

<div id="app">
    <h2>西游人物</h2>
    <ul>
        <li v-for="(user, index) in users" v-if="user.age<20" style="color:yellowgreen;">
            {{index+1}}:我是{{user.name}},今年{{user.age}}岁了,性别{{user.gender}}
        </li>
        <li v-else-if="user.age<30" style="color:skyblue;">
            {{index+1}}:我是{{user.name}},今年{{user.age}}岁了,性别{{user.gender}}
        </li>
        <li v-else style="color:yellow;">
            {{index+1}}:我是{{user.name}},今年{{user.age}}岁了,性别{{user.gender}}
        </li>
    </ul>
</div>

4.5.4 v-show

v-show也可以根据条件是否展示元素,例如:

<h1 v-show="ok">Hello Vue.js.</h1>

但是,带有v-show的元素始终会被渲染并保留在DOM中,v-show只是简单地切换元素的CSS属性display的值。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <button @click="show=!show">切换</button>
        <h2 v-if="show">Vue.js</h2>
        <h2 v-show="show">Vue.js</h2>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                show: true
            }
        })
    </script>
</body>
</html>

image-20200828082250681

v-if在条件不满足的时候元素会消失;v-show条件不满足的时候只是display:none

4.6 v-bind

4.6.1 属性上使用vue数据

插值表达式不能用在属性中,会报错<div id="box" class="">点击按钮改变背景颜色</div>

v-bind作用:可以对所有元素的属性值设置为vue中data的数据

语法:在属性名之前加上v-bind:(v-bind:属性名='Vue中的变量'),简写为:属性名='属性值'

<img src="" height="" />其中src和height的值如果不想写死,而是想获取Vue实例中的数据属性值的话,那么可以通过使用v-bind实现

<img v-bind:src="vue实例中的数据属性名" :height="vue实例中的数据属性名" />

利用v-bind实现点击切换背景颜色

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
        #box{
            width: 150px;
            height: 150px;
            color: white;
        }
        .red{
            background-color: red;
        }
        .green{
            background-color: green;
        }
    </style>
</head>
<body>
    <div id="app">
        <button @click="bgcolor='red'">切换红色</button>
        <button @click="bgcolor='green'">切换绿色</button>
        <br><br>
        <div id="box" v-bind:class="bgcolor">点击按钮改变背景颜色</div>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                bgcolor: "red"
            }
        })
    </script>
</body>
</html>

4.6.2 class属性的特殊用法

上面虽然实现了颜色切换,但是比较麻烦。

Vue对class属性进行了特殊处理,可接受数组或对象格式

对象语法:

可以传给:class一个对象,用于动态切换class:

<div :class="{red: true, green: false}"></div>
  • 对象中,key是已经定义的class样式的名称,比如上面的redgreen
  • 对象中,value是一个布尔值,如果为true,则这个样式会生效,如果为false,则不生效。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
        #box{
            width: 150px;
            height: 150px;
            color: white;
        }
        .red{
            background-color: red;
        }
        .green{
            background-color: green;
        }
    </style>
</head>
<body>
    <div id="app">
        <button @click="bool=!bool">切换颜色</button>
        <br><br>
        <div id="box" v-bind:class="{red: bool, green: !bool}">点击按钮改变背景颜色</div>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                bgcolor: "red",
                bool: true
            }
        })
    </script>
</body>
</html>

4.7 计算属性

在插值表达式中使用js表达式是非常方便的,而且也经常被用到。

但是如果表达式的内容很长,就会显得不够优雅,而且后期维护起来也不方便。

例如,将一个日期的毫秒值显示转为格式化的yyyy-MM-dd:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2>日期:{{new Date(date).getFullYear()}}-{{new Date(date).getMonth()+1}}-{{new Date(date).getDate()}}</h2> <!-- 日期:2020-8-28 -->
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                date: 1598580451457 // 毫秒值
            }
        })
    </script>
</body>
</html>

这样利用js的方法能够实现需求,但是很麻烦。

Vue中提供了计算属性,来替代复杂的表达式:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <h2>日期:{{new Date(date).getFullYear()}}-{{new Date(date).getMonth()+1}}-{{new Date(date).getDate()}}</h2>
        <hr>
        <h2>computed,日期:{{getDay}}</h2>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                date: 1598580451457 // 毫秒值
            },
            computed: {
                getDay(){
                    const date = new Date(this.date)
                    return date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
                }
            }
        })
    </script>
</body>
</html>

computed计算属性可以应用在插值或者指令表达式复杂的时候,它可以将一些属性数据经过方法处理之后返回。

4.8 watch

4.8.1 监控

在vue实例中,数据属性因为在页面中修改而产生了变化,可以通过watch监控获取其改变前后的值。

watch使用场景:可以监控视图中的数据变化从而做出相应的反应,例如,下拉列表中,如果选择了对应的下拉列表选项之后,要根据最新的值去加载一些其他数据。

4.8.2 深度监控

如果是修改的对象数据属性,可以开启深度监控获取修改后最新的对象数据。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <input type="text" v-model="message">
        <br><hr>
        <input type="text" v-model="person.name">
        <input type="text" v-model="person.age">
        <button @click="person.age++">年龄+1</button>
        <h2>姓名:{{person.name}},年龄:{{person.age}}</h2>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data:{
                message: "你好啊!",
                person:{
                    name: "张三",
                    age: 21
                }
            },
            watch: {
                message(newValue, oldValue){
                    console.log("新值:"+ newValue +",旧值:" + oldValue)
                },
                person: {
                    /* 开启深度监控,监控对象中的属性值变化 */
                    deep: true,
                    // 可以获取到最新的对象属性数据
                    handler(obj){
                        console.log("姓名:" + obj.name + ",年龄:" + obj.age)
                    }
                }
            },
        })
    </script>
</body>
</html>

五、组件化

在大型应用开发的时候,页面可以划分成很多部分。

但是如果每个页面都独自开发,无疑会增加开发的成本,因此会把页面的不同部分拆分成独立的组件,然后再不同的页面共享这些组件,避免重复开发。

5.1 定义全局组件

通过Vue的component方法来定义一个全局组件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <!-- 引入组件(使用) -->
        <counter />
    </div>
    <script type="text/javascript">
        // 定义组件
        const counter = {
            // el 组件不需要el绑定一个具体的元素
            template: "<button @click='num++'>你点击了{{num}}次</button>",
            data() {
                return {
                    num: 0
                }
            } // data只能是一个函数,并且有返回
            /* data: {
                num: 0
            } */
        }
        // 全局注册组件:在所有的vue实例中都可以使用组件
        Vue.component("counter", counter) // 参数1:组件内名称,参数2:具体的组件
        var vm = new Vue({
            el: "#app"
        })
    </script>
</body>
</html>
  • 组件其实也是一个Vue实例,因此它在定义时也会接收data、methods、生命周期函数等

  • 不同的是,组件不会与页面的元素绑定,否则就无法复用了,因此没有el属性

  • 但是组件渲染需要hmtl模板,所以添加了template属性,值就是HTML模板

  • 全局组件定义完毕,任何Vue实例都可以直接在HTML中通过组件名称来使用该组件

  • data的定义方式比较特殊,必须是一个函数

5.2 组件的复用

定义好的组件,可以任意复用多次:

<div id="app">
    <!-- 引入组件(使用) -->
    <counter></counter>
    <counter></counter>
    <counter></counter>
</div>

image-20200828121651862

每个组件互不干扰,有自己的num值,这是因为

组件的data属性必须是函数

当定义这个组件的时候,它的data并不是像这样直接提供一个对象:

data:{
    num: 0
}

而必须是一个函数,因此每个实例可以维护一分被返回对象的独立的拷贝

data: function() {
  return {
    num: 0
  }
}

如果Vue没有这条规则,点击一个按钮就会影响到其他所有实例

5.3 局部注册

一旦全局注册,就意味着即便以后不再使用这个组件,它依然会随着Vue的加载而加载。因此,对于一些使用并不频繁的组件,会采用局部注册。

先在外部定义一个对象,结构与创建组件时传递的第二个参数一致,然后再Vue中使用它:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <!-- 引入组件(使用) -->
        <counter></counter>
        <counter></counter>
        <counter></counter>
    </div>
    <script type="text/javascript">
        // 定义组件
        const counter = {
            // el 组件不需要el绑定一个具体的元素
            template: "<button @click='num++'>你点击了{{num}}次</button>",
            data() {
                return {
                    num: 0
                }
            } // data只能是一个函数,并且有返回
        }
        // 全局注册组件:在所有的vue实例中都可以使用组件
        /* Vue.component("counter", counter) */ // 参数1:组件内名称,参数2:具体的组件
        var vm = new Vue({
            el: "#app",
            // 局部注册组件
            components:{
                counter: counter // 组件名: 具体的某个组件
            }
        })
    </script>
</body>
</html>
  • components就是当前vue对象子组件集合
    • 其key就是子组件名称
    • 其值就是组件对象的属性
  • 效果与全局注册时一样的,但是这个局部注册的counter组件只能在当前的Vue实例中使用

组件使用场景:在项目需要重用某个模块(头部、尾部、内容……)的时候,可以将模块抽取成组件,其他页面中注册组件并引用。

全局注册:在任何Vue实例中都可以引用,如:网站的头部导航菜单

局部注册:可以在有需要的页面引入组件,如:商城网站首页页面中各种活动模块

5.4 组件通信

通常一个单页面应用会以一颗嵌套的组件树的形式来组织:

img

  • 页面首先分为了顶部导航、左侧内容区、右侧边栏三个部分
  • 左侧内容区又分为上下两个组件
  • 右侧边栏中包含了3个子组件

各个组件之间以嵌套的关系组合在一起,那么这个时候不可避免地会有组件间通信的需求。

5.4.1 父向子传递 props

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <introduce :title="msg"></introduce>
    </div>
    <script type="text/javascript">
        const introduce = {
            template:"<h2>{{title}}</h2>",
            // 定义接收父组件的属性
            props:["title"]
        }
        Vue.component("introduce", introduce)
        var vm = new Vue({
            el: "#app",
            data:{
                msg:"父组件的msg属性数据内容"
            }
        })
    </script>
</body>
</html>

introduce这个子组件中要使用title属性渲染页面,但是自己并没有title属性。通过props来接收父组件属性,名为title。父组件使用子组件,同时传递title属性。

5.4.2 传递复杂数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <my-list :items="lessons"></my-list> <!-- 这里就不能用驼峰命名法了 -->
    </div>
    <script type="text/javascript">
        const myList = {
            template:`
                <ul>
                    <li v-for="item in items" :key="item.id">{{item.id}}——{{item.name}}</li>
                </ul>
            `, // 这里用的是模板字符串,也可以用双引号、单引号
            props:{ // 通过props来接收父组件传递来的属性
                items:{ // 这里定义items属性
                    // 数据类型,如果是数组则是Array,如果是对象则是Object
                    type:Array,
                    // 默认值(如果父组件没有传值,那么就是一个空数组)
                    default:[]
                }
            }
        }
        var vm = new Vue({
            el: "#app",
            data:{
                msg:"父组件的msg属性数据内容",
                lessons:[
                    {"id":1, "name":"语文"},
                    {"id":2, "name":"数学"},
                    {"id":3, "name":"英语"},
                    {"id":4, "name":"物理"},
                    {"id":5, "name":"化学"},
                    {"id":6, "name":"生物"}
                ]
            },
            components:{
                myList // ES6语法
            }
        })
    </script>
</body>
</html>
  • 这个子组件可以对items进行迭代,并输出到页面

  • 但是组件中并没有定义items属性

  • 可以通过props来定义需要从父组件中接收的属性

    • items:要接收的属性名称

      • type:限定父组件传递来的必须是数组,否则报错[type的值可以是Array或者Object,传递对象的时候使用]

      • default:默认值,如果是对象则需要写成方法的方式返回默认值,如:

        default(){
            return {"xxx":"默认值"}
        }

5.4.3 子向父的通信

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue.js测试</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        num = {{num}}
        <counter @plus="numPlus" @reduce="numReduce()" :snum="num"></counter>
    </div>
    <script type="text/javascript">
        const counter = {
            template:`
                <div> 
                    <button @click='incrNum'>增加</button>
                    <button @click='decrNum'>减少</button>
                </div>
            `,/* 只能是一个容器包裹多个元素 */
            props:["snum"],
            methods: {
                incrNum(){
                    // 调用到父组件中的方法
                    return this.$emit("plus")
                },
                decrNum(){
                    // 调用到父组件中的方法
                    return this.$emit("reduce")
                }
            },
        }
        Vue.component("counter", counter)
        var vm = new Vue({
            el: "#app",
            data:{
                num: 0
            },
            methods: {
                numPlus(){
                    this.num++
                },
                numReduce(){
                    this.num--
                }
            }
        })
    </script>
</body>
</html>

实现了在子组件中点击对应按钮,父组件中属性数据的改变

子组件绑定自定义事件,在子组件中通过$emit触发这个事件,去调用执行外界父组件传递过来的函数操作,函数调用时可以传递参数,从而间接地传递数据给父组件。(父组件给子组件传递一个函数,然后子组件调用此函数,调用时可以传参,函数执行时,实际执行的是父组件中的逻辑,从而可以拿到子组件传参过来的数据)

$emit可以传值,this.$emit("自定义事件名",要传的数据),接着会在父组件中以方法的参数传过去

六、Vuejs ajax

Vuejs并没有直接处理ajax的组件,但可以使用axios或vue-resource组件实现对异步请求的操作。

6.1 vue-resource

vue-resource是Vue.js的插件,提供了使用XMLHttpRequest或JSONP进行web请求和处理响应的服务。当Vue更新到2.0之后,作者就宣告不再对vue-resource更新,而是推荐使用axios。

GitHub地址:https://github.com/pagekit/vue-resource

6.2 axios简介

axios是一个基于promise的HTTP库,可以用在浏览器和Node.js中。

GitHub地址:https://github.com/axios/axios

# npm 安装
npm install axios

也可以直接使用cdn服务:

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

6.3 axios应用

6.3.1 方法说明

axios可以使用的方法有:

  • axios(config)
  • axios.get(url[,config])
  • axios.delete(url[,config])
  • axios.head(url[,config])
  • axios.post(url[,data[,config]])
  • axios.put(url[,data[,config]])
  • axios.patch(url[,data[,config]])

1、config请求配置

这些是创建请求时可以用的配置选项。只有url是必须的,如果没有指定method,请求将默认使用get方法。

{
  // url是用于请求的服务器URL
  url: '/user',

  // method是创建请求时使用的方法
  method: 'get', // 默认是get方式

  // baseURL将自动加在url的前面,除非url是一个绝对URL。
  // 它可以通过设置一个baseURL,便于为axios实例的方法传递相对URL。
  baseURL: 'https://some-domain.com/api/',

  // transformRequest允许在将请求数据发送到服务器之前对数据进行修改
  // 只能用在'PUT'、'POST'、'PATCH' and 'DELETE'这些请求方法
  // 数组中的最后一个函数必须返回字符串或Buffer,ArrayBuffer,FormData或Stream的实例
  // You may modify the headers object.
  transformRequest: [function (data, headers) {
    // 对data进行任意转换处理

    return data;
  }],

  // transformResponse在传递给then/catch前,运行修改响应数据
  transformResponse: [function (data) {
    // 对data进行任意转换处理

    return data;
  }],

  // headers是即将被发送的自定义请求头
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
    'Content-Type': 'application/json'
  },

  // params是即将与请求一起发送的URL参数
  // 必须是一个无格式对象(plain object)或URLSearchParams对象
  params: {
    ID: 12345
  },

  // paramsSerializer是用于序列化params的可选功能(e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function (params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

  // data是作为请求主体被发送的数据
  // 只适用于'PUT'、'POST'、'DELETE'和'PATCH'请求方法
  // 在没有设置transformRequest时, 必须是一下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属: FormData, File, Blob
  // - Node专属: Stream, Buffer
  data: {
    firstName: 'Fred'
  },

  // syntax alternative to send data into the body
  // method post
  // only the value is sent, not the key
  data: 'Country=Brasil&City=Belo Horizonte',

  // timeout指定请求超时的毫秒数(0表示无超时时间)
  // 如果请求花费超过timeout的时间, 请求将被中断
  timeout: 1000, // 默认是`0` (no timeout)

  // withCredentials表示跨域请求时是否需要凭证
  withCredentials: false, // 默认是false

  // adapter` allows custom handling of requests which makes testing easier.
  // Return a promise and supply a valid response (see lib/adapters/README.md).
  adapter: function (config) {
    /* ... */
  },

  // `auth` indicates that HTTP Basic auth should be used, and supplies credentials.
  // This will set an `Authorization` header, overwriting any existing
  // `Authorization` custom headers you have set using `headers`.
  // Please note that only HTTP Basic auth is configurable through this parameter.
  // For Bearer tokens and such, use `Authorization` custom headers instead.
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },

  // responseType表示服务器响应的数据类型,可以是'arraybuffer', 'document', 'json', 'text', 'stream'
  // 浏览器专属: 'blob'
  responseType: 'json', // 默认是json

  // responseEncoding` indicates encoding to use for decoding responses (Node.js only)
  // Note: Ignored for `responseType` of 'stream' or client-side requests
  responseEncoding: 'utf8', // default

  // `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` is the name of the http header that carries the xsrf token value
  xsrfHeaderName: 'X-XSRF-TOKEN', // default

  // `onUploadProgress` allows handling of progress events for uploads
  // browser only
  onUploadProgress: function (progressEvent) {
    // Do whatever you want with the native progress event
  },

  // `onDownloadProgress` allows handling of progress events for downloads
  // browser only
  onDownloadProgress: function (progressEvent) {
    // Do whatever you want with the native progress event
  },

  // maxContentLength定义运行的响应内容的最大尺寸
  maxContentLength: 2000,

  // `maxBodyLength` (Node only option) defines the max size of the http request content in bytes allowed
  maxBodyLength: 2000,

  // validateStatus` defines whether to resolve or reject the promise for a given HTTP response status code. 如果validateStatus返回true(或者设置为null或undefined), promise将被resolved; 否则promise将被rejected.
  validateStatus: function (status) {
    return status >= 200 && status < 300; // 默认是200~300
  },

  // maxRedirects定义在node.js中执行重定向的最大数目
  // 如果设置为0,将不执行任何重定向。
  maxRedirects: 5, // 默认是5

  // `socketPath` defines a UNIX Socket to be used in node.js.
  // e.g. '/var/run/docker.sock' to send requests to the docker daemon.
  // Only either `socketPath` or `proxy` can be specified.
  // If both are specified, `socketPath` is used.
  socketPath: null, // default

  // `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
  // and https requests, respectively, in node.js. This allows options to be added like
  // `keepAlive` that are not enabled by default.
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // `proxy` defines the hostname and port of the proxy server.
  // You can also define your proxy using the conventional `http_proxy` and
  // `https_proxy` environment variables. If you are using environment variables
  // for your proxy configuration, you can also define a `no_proxy` environment
  // variable as a comma-separated list of domains that should not be proxied.
  // Use `false` to disable proxies, ignoring environment variables.
  // `auth` indicates that HTTP Basic auth should be used to connect to the proxy, and
  // supplies credentials.
  // This will set an `Proxy-Authorization` header, overwriting any existing
  // `Proxy-Authorization` custom headers you have set using `headers`.
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },

  // `cancelToken` specifies a cancel token that can be used to cancel the request
  // (see Cancellation section below for details)
  cancelToken: new CancelToken(function (cancel) {
  }),

  // `decompress` indicates whether or not the response body should be decompressed 
  // automatically. If set to `true` will also remove the 'content-encoding' header 
  // from the responses objects of all decompressed responses
  // - Node only (XHR cannot turn off decompression)
  decompress: true // default

}

2、响应结构

{
  // data是由服务器提供的响应数据
  data: {},

  // status是来自服务器响应的HTTP状态码
  status: 200,

  // statusText服务器响应的HTTP状态信息
  statusText: 'OK',

  // headers是服务器响应的头,服务器使用所有标头名称响应的HTTP标头均使用小写字母,并且可以使用方括号表示法进行访问。
  // 例如: `response.headers['content-type']`
  headers: {},

  // config是为请求提供给axios的配置
  config: {},

  // `request` is the request that generated this response
  // It is the last ClientRequest instance in node.js (in redirects)
  // and an XMLHttpRequest instance in the browser
  request: {}
}

使用then时,将会收到如下响应:

axios.get('/user/12345')
  .then(function (response) {
    console.log(response.data);
    console.log(response.status);
    console.log(response.statusText);
    console.log(response.headers);
    console.log(response.config);
  });

当使用catch或将拒绝回调作为then的第二个参数传递时,响应将通过error对象提供,如“处理错误”部分所述。

6.3.2 axios方法示例

6.3.3 get方法示例

6.3.4 post方法示例

Ajax学习笔记

Ajax学习笔记

一、概述

Web程序的最初的目的就是将信息(数据)放到公共的服务器,让所有的网络用户都可以通过浏览器访问

image-20200815183450778

在此之前,我们可以通过以下几种方式让浏览器发出服务端的请求,获得服务端的数据:

  • 地址栏输入地址,回车,刷新

  • 特定元素的href或src属性

  • 表单提交

这些方案都是我们无法通过或者很难通过代码的方式进行编程(对服务器发出请求并且接收服务端返回的响应),如果我们可以通过JavaScript直接发送网络请求,那么web的可能就会更多,随之能够实现的功能也会更多,至少不再是“单机游戏”。

1、AJAX(Asynchronous JavaScript and XML,异步的JS和XML),最早出现在2005年的Google Suggest,是在浏览器端进行网络编程(发送请求,接收响应)的技术方案,它使我们可以通过JavaScript直接获取服务端最新的内容而不必重新加载页面,让web更能接近桌面应用的用户体验。

说白了,AJAX就是浏览器提供的一套API,可以通过JavaScript调用,从而实现代码控制请求与响应,实现网络编程。(AJAX不是新的编程语言,而是一种将现有标准组合在一起使用的新的方式)

能力不够API凑。

对xxx进行编程指的就是用代码的方式操作它

2、XML

可扩展标记语言,被设计用来传输和存储数据,和HTML有点像,但是HTML中都是预定义标签,而XML中没有预定义标签,全都是自定义标签,用来表示一些数据。

现在已经被json取代了。

3、AJAX的特点

(1) 优点

可以无需刷新页面而与服务器端进行通信。

允许你根据用户事件来更新部分页面内容。

(2) 缺点

没有浏览历史,不能回退

存在跨域问题(同源)

SEO不友好

补充:搭建环境

使用Express搭建后台

Express中文网

1、安装

需要有node,没有的可以先下载

进入一个文件夹,输入命令创建node应用

npm init --yes

node初始化命令

接着输入命令,安装express

npm install express

express安装

2、基本使用

(1) 编写代码

// 1、引入express
const express = require('express')
const { response } = require('express')

// 2、创建应用对象
const app = express()

// 3、创建路由规则 
// reques是对请求报文的封装;response是对响应报文的封装
app.get('/', (request, response) => { // 前端访问http://localhost:8000/
  // 设置响应
  response.send('Hello Express')
})

// 4、监听端口启动服务
app.listen(8000, () => {
  console.log('服务已经启动,8000端口监听中,请访问http://localhost:8000/')
})

(2) 运行代码

在该目录下输入命令

node 文件名.js

eg:

node expressTest.js

接着访问地址http://localhost:8000/即可

3、准备服务端代码

server.js

const express = require('express')
const { response } = require('express')
const app = express()
// 这里设置为get则请求方式为GET、'/server'则请求url需要加上这个
app.get('/server', (request, response) => {
  // 设置响应头
  response.setHeader('Access-Control-Allow-origin','*') // 设置允许跨域
  // 设置响应体
  response.send('Hello AJAX')
})
// 在这里接着加上接口
app.listen(8000, () => {
  console.log('服务已经启动,8000端口监听中,请访问http://localhost:8000/')
})

关闭上面那个,将这个启动:

node server.js

reload包——nodemon

自动检测js代码变化,restart服务

1、安装

npm install -g nodemon

2、利用nodemon执行文件

nodemon 文件名.js

例如:

nodemon server.js

二、快速上手

1、AJAX基础

(一个构造函数、两个方法、一个事件)

发送请求

<style>
#result{
    width:200px;
    height:100px;
    border:1px solid skyblue;
}
</style>
<button id="btn">点击发送请求</button>
<div id="result"></div>
<script>
btn.onclick = function(){
  // 涉及到AJAX操作的页面不能使用文件协议访问(文件的方式访问)
  // AJAX是一套API,核心提供的类型:XMLHttpRequest XML-->JSON(现在使用的是JSON格式的了)
  // 1、安装浏览器(用户代理)——创建对象
  var xhr = new XMLHttpRequest() // xhr就类似于浏览器的作用(发送请求接收响应)
  // 2、打开浏览器 输入网址——初始化 设置请求方式和url
  xhr.open('GET', 'http://127.0.0.1:8000/server')  // 这一步只是在搭桥铺路
  // 3、敲回车键 开始请求——发送
  xhr.send()  // send才是开始请求
  /* 上面三个是ajax核心代码 */
}
</script>

接收响应

<script>
  var xhr = new XMLHttpRequest()
  xhr.open('GET', 'http://127.0.0.1:8000/server') // 方式,url
  xhr.send()
  /* 因为响应需要时间,所以无法通过返回值的方式返回响应 */
  // var response = xhr.send()
  // console.log(response) // undefine

  // 4、等待响应——事件绑定 处理服务端返回的结果
  /* 如果需要捕获状态的变化,需要注意代码的执行顺序的问题(不要出现来不及的情况)
  * 因为客户端永远不知道服务端何时才能返回我们需要的响应,所以AJAX API采用事件的机制(通知的感觉)
  */
  xhr.onreadystatechange = function(){ // 建议事件使用addEventListener方式
    // 这个事件并不是只在响应时触发,XHR状态改变就触发
    console.log(this.readyState)
    if(this.readyState!==4) return
    // 所以下面就是为4的情况
    // 5、看结果
    console.log(this.responseText) // 获取响应内容(响应体)
    result.innerHTML = xhr.response
  }
</script>

ajax基础操作

2、理解readyState

onreadystatechange是XHR状态改变时触发的

<script>
  var xhr = new XMLHttpRequest()
  console.log(xhr.readyState) // 0
  xhr.open('GET', './time.php')
  console.log(xhr.readyState) // 1
  xhr.send()
  // console.log(xhr.readyState) // 1 取上面那个
  xhr.addEventListener('readystatechange', function(){
    // if(this.readyState !== 4) return
    // console.log(this.responseText)
    console.log(this.readyState) // 2 3 4
  })
</script>

readyState:

readyState代码

new XMLHttpRequest –> 0 初始化 请求代理对象

1–-> open方法已经调用,建立一个与服务端特定端口的连接

2 –-> 已经接收到了响应报文的响应头 console.log(this.getAllResponseHeaders()) 可以拿到响应头,拿不到响应体

拆分:console.log(this.getAllResponseHeaders().splite(‘\n’).splite(‘:’))

获取指定键:console.log(this.getAllResponseHeaders(‘data’))

3 –-> 正在下载响应报文的响应体,可能响应体为空或不完整

4 –-> 一切OK,整个响应报文已经下载下来console.log(this.responseText)

readyState

image-20200815183934889

所以应该在readyState的值为4时,才去处理后续的逻辑

可以用xhr.onload替代

<script>
  var xhr = new XMLHttpRequest()
  xhr.open('GET', 'time.php')
  xhr.send(null) // send可以传请求体,传null代表没有请求体
  xhr.onload = function (){ // 加载完成 H5中提供的XMLHttpRequest version 2.0定义的
    // 相当于readyState为4之后的
    console.log(this.responseText)
  }
</script>

ps: console.log(this) 显示readyState是2、3、4可展开来全都是4,这个是console.log的机制问题,展开的时候只会显示此时的状态

例如:

log机制测试

在浏览器上看,不展开没问题显示123,展开的一瞬间都是456

3、AJAX遵循HTTP协议

HTTP协议(Hypertext Transport Protocol,超文本传输协议)详细规定了浏览器和万维网服务器之间互相通信的规则。

本质上XMLHttpRequest就是JavaScript在web平台中发送HTTP请求的手段,所以我们发送出去的请求仍然是HTTP请求,同样符合HTTP约定的格式

请求报文:

  • 请求行 POST /s?ie=utf-8 HTTP/1.1

GET /s?ie=utf-8 HTTP/1.1

  • 请求头 Host: atguigu.com

Cookie: name=guigu

Content-Type: application/x-www-form-urlencoded

User-Agent: chrome 83

  • 空行(必须得有)

  • 请求体 (GET请求这里为空,POST可不为空) username=admin&password=admin

响应报文:

  • HTTP/1.1 200 OK

​ 404

​ 403

​ 401

​ 500

  • Content-Type: text/html;charset=utf-8

    Content-length: 2048

    Content-encoding: gzip

  • 空行

<html>
  <head>
  </head>
  <body>
    <h1>wallleap</h1>
  </body>
</html>

HTTP响应报文信息

<script>
  // 1、创建对象
  var xhr = new XMLHttpRequest()
  // 2、初始化 设置请求方法和url
xhr.open('POST', 'add.php') // 设置请求行
  xhr.setRequestHeader('Foo', 'Bar') // 设置一个请求头
  // 一旦请求体是urlencoded格式的内容,一定要设置请求头中的Content-Type为下面这个
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') // 设置第二请求头
  xhr.send('key1=value1&key2=value2') // 以urlencoded格式设置请求体
  // xhr.send('{"foo": "123"}') // 以json格式设置请求体,上面设置application/json
  xhr.onload = function (){
    // 响应行
    console.log(this.status) // 获取响应状态码
    console.log(this.statusText) // 获取响应状态描述
    // 获取响应头信息
    console.log(this.getResponseHeader('Content-Type')) // 指定响应头
    console.log(this.getAllResponseHeaders()) // 全部响应头
    // 获取响应体
    console.log(this.responseText) // 文本形式
    console.log(this.responseXML) // XML形式,了解即可
  }
</script>

补充:

xhr.addEventListener('readystatechange', function(){
    if(this.readyState === 4 && this.status === 200){
      console.log(this)
    }
})

可能有的人会同时判断状态码200,事实上没有必要,状态码404也需要处理,可以到里面嵌套,例如:

xhr.onreadystatechange=function(){
  if(this.readyState === 4){
    if(this.status >= 200  && xhr.status < 300){
      // 处理结果
    }else{
      ...
    }
  }
})

Chrome打开开发者模式

点击Network能够看到传输的文件

点击XHR查看

Request Headers——请求头

点击view source可以看到请求行

Response Header——响应头

点击view source可以看到响应行

Response——响应体

Preview——预览,对响应体解析之后的页面

三、具体用法

1、数据接口的概念

服务器端返回的响应就是一个JSON内容(返回的就是数据)

对于返回数据的地址一般我们称之为接口(形式上是web形式)

http://api.douban.com/v2/movie/top250

提供一定的能力,有输入有输出就可以称为接口

2、AJAX发送GET请求并传递参数

<script>
  var xhr = new XMLHttpRequest()
  // GET请求传递参数通常使用URL中的问号传递数据
  xhr.open('GET', 'http://127.0.0.1:8000/server?a=100&b=200&c=300')
  // 一般在GET请求时无需设置响应体,可以传null或者干脆不传
  xhr.send(null)
  xhr.onreadystatechange = function (){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  }
</script>
<!-- 一般情况下URL传递的都是参数性质的数据,而POST一般都是业务数据 -->

例子:将得到的四个用户名称{}放到ul>li中,点击li能够获取到该用户的年龄

<ul id="list"></ul>
<script>
  var listElement = document.getElementById('list')
  /* 发送请求获取到列表数据,呈现在页面上 */
  var xhr = new XMLHttpRequest()
  xhr.open('POST', 'user.php?id=2')
  xhr.send(null)
  xhr.onreadystatechange = function (){
    if(this.readyState !== 4) return
    var data = JSON.parse(this.responseText)
    // console.log(data)
    for(var i = 0; i < data.length; i++){
      // console.log(data[i])
      var liElement = document.createElement('li')
      liElement.innerHTML = data[i].name
      liElement.id = data[i].id
      listElement.appendChild(liElement)
      /* 给每一个li注册点击事件 */
      // 由于li是动态创建的,因此需要移到创建li的时候
      listElement.addEventListener('click', function (){
          // TODO: 通过AJAX操作获取服务端对应数据的信息
          // 获取当前被点击元素对应数据的id
          // console.log(this.id)
          var xhr1 = new XMLHttpRequest()
          xhr1.open('GET', 'users.php?id=' + this.id)
          xhr1.send()
          xhr1.onreadystatechange = function (){
            if(this.readyState !== 4) return
            var obj = JSON.parse(this.responseText)
            alert(obj.age)
          }
      })
    }
  }
</script>

3、POST请求

POST请求过程中,都是采用请求体承载需要提交的数据

// 1.创建对象
const xhr = new XMLHttpRequest()
// 2.初始化 设置类型与URL(open的第一个参数的作用就是设置请求的method)
xhr.open('POST','http://127.0.0.1:8000/server')
// 设置请求头信息
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded') // 设置请求头中的Content-Type为application/x-www-form-urlencoded——标识这次请求得请求体格式为urlencoded以便于服务端接收数据
// xhr.setRequestHeader('name','wallleap') // 也可以自定义
// 3.发送 POST方式需要提交到服务端的数据可以通过send方法的参数传递,格式:key1=value1&key2=value2
xhr.send("key1=value1&key2=value2")
// xhr.send("key1:value1&key2:value2")
// xhr.send("value1")  // --->可以随意写,但是需要按格式,方便后台处理
// 4.事件绑定
xhr.onreadystatechange = function(){
  // 判断
  if(xhr.readyState===4){
    if(xhr.status >= 200 && xhr.status < 300){
      // 处理服务端返回的结果
      result.innerHTML = xhr.response
    }
  }
}

由于server.js中只设置了get的允许跨域,因此需要在文件中加入允许post跨域的代码

// app.get('/server', (request, response) => {
app.post('/server', (request, response) => {
  // 设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  // 设置允许所有头信息,就比如上面设置的自定义响应头会报错,就需要加上这个
  // response.setHeader('Access-Control-Allow-Headers', '*');
  // 设置响应体
  response.send('HELLO AJAX POST');
});

可以改为all

//可以接收任意类型的请求(get/post/options/...)
app.all('/server', (request, response) => {
  //设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  //响应头
  response.setHeader('Access-Control-Allow-Headers', '*');
  //设置响应体
  response.send('HELLO AJAX POST');
});

测试

<style>
#result{
  width:200px;
  height:100px;
  border:solid 1px #903;
}
</style>
</head>
<body>
<div id="result"></div>
<script>
  //获取元素对象
  const result = document.getElementById("result");
  //绑定事件
  result.addEventListener("mouseover", function(){
    //1. 创建对象
    const xhr = new XMLHttpRequest();
    //2. 初始化 设置类型与 URL
    xhr.open('POST', 'http://127.0.0.1:8000/server');
    //设置请求头
    xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
    xhr.setRequestHeader('name','wallleap');
    //3. 发送
    xhr.send('a=100&b=200&c=300');
    // xhr.send('a:100&b:200&c:300');
    // xhr.send('1233211234567');
    //4. 事件绑定
    xhr.onreadystatechange = function(){
      //判断
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
          //处理服务端返回的结果
          result.innerHTML = xhr.response;
        }
      }
    }
  });
</script>

例子:点击登录按钮不刷新页面将数据传到后台

<style>
.loading{
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #555;
  text-align: center;
  padding-top: 200px;
  opacity: .5;
}
.loading::after{
  content: '加载中……';
  font-size: 60px;
  color: #fff;
}
</style>
<div class="loading"></div>
<table border="1">
  <tr>
    <td>用户名</td>
    <td><input type="text" name="" id="username"></td>
  </tr>
  <tr>
    <td>密码</td>
    <td><input type="password" name="" id="password"></td>
  </tr>
  <tr>
    <td></td>
    <td><button id="btn">登录</button></td>
  </tr>
</table>
<script>
  // 找一个合适的时机,做一件合适的事情(时间、内容)
  // 1、获取界面上的元素 value
  var textUsername = document.getElementById('username')
  var textPassword = document.getElementById('password')
  var btn = document.getElementById('btn')
  var loading = document.querySelector('.loading')
  btn.onclick = function (){
    loading.style.display = 'block'
    var username = textUsername.value
    var password = textPassword.value
    // 2、通过XHR发送一个POST请求
    var xhr = new XMLHttpRequest
    xhr.open('POST', 'login.php')
    // 一定注意:如果请求体是urlencoded格式,必须设置这个请求头!
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    // xhr.send('username=' + username + '&password=' + password)
    xhr.send(`username=${username}&password=${password}`)
    // 3、根据服务端的反馈,作出界面提示
    xhr.onreadystatechange = function (){
      if(this.readyState !== 4) return
      console.log(this.responseText)
      loading.style.display = 'none'
    }
  }
</script>

4、同步和异步

生活中:

同步:一个人在同一个时刻只能做一件事情,在执行一些耗时的操作(不需要看管)不去做别的事情,只是等待

异步:在执行一些耗时的操作(不需要看管)去做别的事,而不是等待

xhr.open()第三个参数(async)要求传入的是一个bool值,其作用就是设置此次请求是否采用异步方式执行,默认为true,如果需要同步执行可以通过传递false实现

image-20200815184831019

console.time(‘标识’) 启动一个秒表

中间写代码

console.timeEnd(‘标识’) 结束这个秒表

这样就能知道用了多长时间(标识名称得相同)

image-20200815184905369

如果采用同步方式执行,则代码会卡死在xhr.send()这一步

image-20200815184920339

send方法会不会出现等待情况(区分同异步)

image-20200815184938765

知道同步模式即可(已被遗弃)

image-20200815184955646

同步模式注册时间时机问题

image-20200815185017871

image-20200815185028276

5、响应数据格式

如果希望服务器返回一个复杂数据,该如何处理:

服务器发出何种格式的数据,这个格式如何在客户端用JavaScript解析

5.1 XML

一种数据描述手段

老掉牙的东西,现在项目中基本不使用

淘汰的原因:数据冗余太多

image-20200815185057257

image-20200815185106691

image-20200815185114823

5.2 JSON

也是一种数据描述手段,类似于JavaScript字面量方式

服务端采用JSON格式返回数据,客户端按照JSON格式解析数据

image-20200815185140660

image-20200815185147671

来测试一下json的

server.js中添加

//JSON 响应
app.all('/json-server', (request, response) => {
  //设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  //响应头
  response.setHeader('Access-Control-Allow-Headers', '*');
  //响应一个数据
  const data = {
    name: 'wallleap'
  };
  //对对象进行字符串转换
  let str = JSON.stringify(data);
  //设置响应体
  response.send(str);
});

测试

<style>
  #result{
    width:200px;
    height:100px;
    border:solid 1px #89b;
  }
</style>
<div id="result"></div>
<script>
  //绑定键盘按下事件
  window.onkeydown = function(){
    //发送请求
    const xhr = new XMLHttpRequest();
    //设置响应体数据的类型
    xhr.responseType = 'json'; // 自动转换
    //初始化
    xhr.open('GET','http://127.0.0.1:8000/json-server');
    //发送
    xhr.send();
    //事件绑定
    xhr.onreadystatechange = function(){
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status < 300){
          // console.log(xhr.response);
          // result.innerHTML = xhr.response;
          // 1. 手动对数据转化
          // let data = JSON.parse(xhr.response);
          // console.log(data);
          // result.innerHTML = data.name;
          // 2. 自动转换
          console.log(xhr.response);
          result.innerHTML = xhr.response.name;
        }
      }
    }
  }
</script>

注意:

不论是JSON,还是XML,只是在AJAX请求过程中用到,并不代表它们之间有必然的联系,它们只是数据协议罢了

不管服务器使用XML还是JSON本质上都是将数据返回给客户端

服务端应该设置一个合理的Content-Type

6、处理服务器端响应的数据

动态渲染数据到表格中

image-20200815185217284

image-20200815185225225

现在一般都不会这样操作,太繁琐了

模板引擎

image-20200815185251189

常见模板引擎列表:https://github.com/tj/consolidate.js#supported-template-engines

artTemplate: https://aui.github.io/art-template

模板引擎实际上就是一个API,模板引擎有很多种,使用方式大同小异,目的为了可以更容易地将数据渲染到HTML中

<table id="comment" border="1"></table>
<!-- // 1.选择一个模板引擎 https://github.com/tj/consolidate.js#supported-template-engines
// 2、下载模板引擎JS库
// 3、引入到页面中 -->
<script src="https://cdn.bootcdn.net/ajax/libs/art-template/4.13.2/lib/template-web.min.js"></script>
<!-- 
  script标签的特点:
  1、innerHTML永远不会显示在界面上(display: none;)
  2、如果type属性不是text/javascript,内部的内容不会作为JavaScript执行
 -->
<!-- // 4、准备一个模板 -->
<!-- 
  JavaScript中用变量保存(维护不方便、不能换行)
  ——> HTML中放到一个div中通过样式隐藏(添加误用元素、多余样式)
  ——> script标签type修改之后不会显示在页面中,建议text/x-开头
-->
<script id="tmpl" type="text/x-art-template">
{{each comments}}  
<tr>
  <td>{{$index+1}}</td>
  <td>{{$value.author}}</td>
  <td>{{$value.content}}</td>
  <td>{{$value.created}}</td>
</tr>
{{/each}}
</script>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'test.php')
xhr.send()
xhr.onreadystatechange = function (){
  if(this.readyState !== 4) return
  var res = JSON.parse(this.responseText)
  // // 5、准备一个数据
  var context = {comments: res.data}  // 上面的关键词就是comments
  console.log(context)
  var html = template('tmpl', context)
  console.log(html)
  document.getElementById('comment').innerHTML = html

  // 6、通过模板引擎的JS提供一个函数将模板和数据整合得到渲染结果HTML
  // 7、将渲染结果的HTML设置到某个元素的innerHTML中
}
</script>

7、兼容方案

XMLHttpRequest在老板浏览器(IE5/6)中有兼容问题,可以通过另一种方式代替

var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')

8、补充

(1) response、responseText

都是获取的响应

response: 获取到的结果或根据this.responseType的变化而变化(可以表述二进制数据)

responseText: 永远获取的是字符串形式的响应体

var xhr = new XMLHttpRequest
xhr.open('GET', 'test.php')
xhr.send()
xhr.responseType = 'json'  // 通过代码告诉请求代理对象,服务端响应给我们的是JSON
xhr.onreadystatechange = function (){
  if(this.readyState !== 4) return
  console.log(this)
  console.log(this.response)
  console.log(this.responseText)  // 由于设置了json,因此不存在
}

(2) IE缓存问题:IE浏览器会将ajax返回结果缓存起来,再次发送请求时,显示的是本地缓存,而不是最新的请求到的数据

在服务端添加一个:

//针对 IE 缓存
app.get('/ie', (request, response) => {
  //设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  //设置响应体
  response.send('HELLO AJAX IE');
});

解决

<style>
  #result{
    width:200px;
    height:100px;
    border:solid 1px #258;
  }
</style>
<button>点击发送请求</button>
<div id="result"></div>
<script>
  const btn = document.getElementsByTagName('button')[0];
  const result = document.querySelector('#result');

  btn.addEventListener('click', function(){
    const xhr = new XMLHttpRequest();
    xhr.open("GET",'http://127.0.0.1:8000/ie?t='+Date.now()); // 解决方案:加上参数,这样浏览器认为每次请求url都不一样
    xhr.send();
    xhr.onreadystatechange = function(){
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status< 300){
          result.innerHTML = xhr.response;
        }
      }
    }
  })
</script>

(3) 请求超时和网络异常

server.js中添加

//延时响应
app.all('/delay', (request, response) => {
  //设置响应头  设置允许跨域
  response.setHeader('Access-Control-Allow-Origin', '*');
  response.setHeader('Access-Control-Allow-Headers', '*');
  // 手动设置一个延时效果
  setTimeout(() => {
    //设置响应体
    response.send('延时响应');
  }, 1000) // 3000
});

进行处理,在2s内还没有响应则取消

<style>
  #result{
    width:200px;
    height:100px;
    border:solid 1px #90b;
  }
</style>
<button>点击发送请求</button>
<div id="result"></div>
<script>
  const btn = document.getElementsByTagName('button')[0];
  const result = document.querySelector('#result');
  btn.addEventListener('click', function(){
    const xhr = new XMLHttpRequest();
    //超时设置 2s 设置
    xhr.timeout = 2000;
    //超时回调
    xhr.ontimeout = function(){
      alert("网络异常, 请稍后重试!!");
    }
    //网络异常回调
    xhr.onerror = function(){
      alert("你的网络似乎出了一些问题!");
    }
    xhr.open("GET",'http://127.0.0.1:8000/delay');
    xhr.send();
    xhr.onreadystatechange = function(){
      if(xhr.readyState === 4){
        if(xhr.status >= 200 && xhr.status< 300){
          result.innerHTML = xhr.response;
        }
      }
    }
  })
</script>

网络异常可以利用浏览器调试中Network一栏,设置为Offline

(3) 取消请求

利用abort()方法取消请求

<button>点击发送</button>
<button>点击取消</button>
<script>
  //获取元素对象
  const btns = document.querySelectorAll('button');
  let x = null; // 由于第二个按钮也需要用到
  btns[0].onclick = function(){
    x = new XMLHttpRequest();
    x.open("GET",'http://127.0.0.1:8000/delay');
    x.send();
  }
  // abort
  btns[1].onclick = function(){
    x.abort();
  }
</script>

(3) ajax重复发送请求问题:用户频繁发送请求,对服务器压力很大

请求时,可以判断,如果前面有一条这样的请求,那么将前面的请求取消掉

<button>点击发送</button>
<script>
  //获取元素对象
  const btns = document.querySelectorAll('button');
  let x = null;
  //标识变量
  let isSending = false; // 是否正在发送AJAX请求
  btns[0].onclick = function(){
    //判断标识变量
    if(isSending) x.abort();// 如果正在发送, 则取消该请求, 创建一个新的请求
    x = new XMLHttpRequest();
    //修改 标识变量的值
    isSending = true;
    x.open("GET",'http://127.0.0.1:8000/delay');
    x.send();
    x.onreadystatechange = function(){
      if(x.readyState === 4){
        //修改标识变量
        isSending = false;
      }
    }
  }
</script>

四、封装

1、AJAX请求封装

封装的套路:

(1) 写一个相对比较完善的用例

var xhr = new XMLHttpRequest
xhr.open('GET', 'test.php', true)
xhr.send(null)
xhr.addEventListener('readystatechange', function(){
  if(this.readyState !== 4) return
  console.log(this.responseText)
})

(2) 写一个空函数,没有形参,将刚刚的用例直接作为函数的函数体

function ajax(){
  var xhr = new XMLHttpRequest
  xhr.open('GET', 'test.php', true)
  xhr.send(null)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  })
}

(3) 根据使用过程中的需求抽象参数

/*
Ajax请求 version1
method: 请求方式 GET/POST
url: 请求地址 'http://xxx.com/api'
*/
function ajax(method, url){
  var xhr = new XMLHttpRequest
  xhr.open(method, url, true)
  xhr.send(data)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  })
}
ajax('GET', 'test.php')

send需要传参

/*
Ajax请求 version2
method: 请求方式 GET/POST
url: 请求地址 'http://xxx.com/api'
params: 键值对字符串
*/
function ajax(method, url, params){
  var xhr = new XMLHttpRequest
  var data = null
  if(method === 'GET'){
    url += '?' + params
  }
  xhr.open(method, url, true)
  if(method === 'POST'){
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    data = params || null
  }
  xhr.send(data)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  })
}
ajax('GET', 'test.php')
ajax('POST', 'post.php', 'key1=value1&key2=value2')

send实参可以传对象

/*
Ajax请求 version3
method: 请求方式 GET/POST
url: 请求地址 'http://xxx.com/api'
params: 对象
*/
function ajax(method, url, params){
  var xhr = new XMLHttpRequest
  // 将Object类型的参数转换为 key1=value1&key2=value2的形式
  if(typeof params === 'object'){
    var tempArr = []
    for (var key in params){
      var value = params[key]
      tempArr.push(key + '=' + params[key])
      // tempArr => ['key1=value1', 'key2=value2']
    }
    params = tempArr.join('&')
    // tempArr => ['key1=value1&key2=value2']
  }
  if(method === 'GET'){
    params ? url += '?' + params : url
  }
  xhr.open(method, url, true)
  var data = null
  if(method === 'POST'){
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    data = params || null
  }
  xhr.send(data)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    console.log(this.responseText)
  })
}
ajax('GET', 'test.php')
ajax('GET', 'time.php', {id: 1})
ajax('POST', 'post.php', {key1: 'value1', key2: 'value2', key3: 'value3'})
ajax('POST', 'post.php', 'key1=value1&key2=value2')

不应该在封装的函数中主观地处理响应结果

/*
Ajax请求 version4
method: 请求方式 GET/POST/get/post
url: 请求地址 'http://xxx.com/api'
params: 对象
*/
function ajax(method, url, params){
  var res = null
  method= method.toUpperCase()
  var xhr = new XMLHttpRequest
  // 将Object类型的参数转换为 key1=value1&key2=value2的形式
  if(typeof params === 'object'){
    var tempArr = []
    for (var key in params){
      var value = params[key]
      tempArr.push(key + '=' + params[key])
      // tempArr => ['key1=value1', 'key2=value2']
    }
    params = tempArr.join('&')
    // tempArr => ['key1=value1&key2=value2']
  }
  if(method === 'GET'){
    params ? url += '?' + params : url
  }
  xhr.open(method, url, false) // 改为同步了
  var data = null
  if(method === 'POST'){
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    data = params || null
  }
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    // console.log(this.responseText)
    // return xhr.responseText // 无法再给外部函数返回结果,利用闭包、原型链
    res = this.responseText
  })
  xhr.send(data)
  // 由于是异步,会先执行(用同步,事件放到send前x不推荐)
  return res
}
console.log(ajax('post', 'post.php', {key1: 'value1', key2: 'value2', key3: 'value3'}))

不能使用同步模式

补充一个概念:

委托(或回调)

image-20200815185827212

函数可以理解为一个想要做的事情,函数体中约定了这件事情做的过程,直到调用时才开始工作。

将函数作为参数传递就像是将一件事情交给别人,这就是委托。

异步编程中回调/委托使用频率很高。(由于是异步的,你先执行,我告诉你做什么,我就不等了——等不及,你执行就行了)

image-20200815185845589

image-20200815185856765

回调地狱/黑洞:死循环

image-20200815185913313

最终版本

/*
发送一个Ajax请求 version5
@param (String) method  请求方式 GET/POST/get/post
@param (String) url     请求地址 'http://xxx.com/api'
@param (Object) params  请求参数
@param (Function) done  请求完成过后需要做的事情(委托/回调)
*/
function ajax(method, url, params, done){
  var res = null
  method= method.toUpperCase()
  var xhr = new XMLHttpRequest
  // 将Object类型的参数转换为 key1=value1&key2=value2的形式
  if(typeof params === 'object'){
    var tempArr = []
    for (var key in params){
      var value = params[key]
      tempArr.push(key + '=' + params[key])
    }
    params = tempArr.join('&')
  }
  if(method === 'GET'){
    params ? url += '?' + params : url
  }
  xhr.open(method, url, true)
  var data = null
  if(method === 'POST'){
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
    data = params || null
  }
  xhr.send(data)
  xhr.addEventListener('readystatechange', function(){
    if(this.readyState !== 4) return
    res = this.responseText
    done(res)
  })
}

var ondone = function (res){
  console.log('准备执行')
  // console.log(res)
  alert(res)
  console.log('执行完成了')
}
ajax('post', 'post.php', {key1: 'value1', key2: 'value2'}, ondone)
ajax('get', 'time.php', {}, function(res){
  console.log(res)
})

2、jQuery中的Ajax

jQuery中有一套专门针对AJAX的封装,功能十分完善,经常使用,需要注意。

https://www.jquery123.com/category/ajax/

(1)通用方法$.ajax

底层接口(其他接口依赖于这个)

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 1、最基础的调用
$.ajax('./time.php', { // url: 
  type: 'POST',  // method: 请求方法
  success: function(res){
    // res => 拿到的只是响应体
    console.log(res)
  }
})
// 可以把url写到里面去
$.ajax({ 
  url: './time.php',// url: 
  type: 'POST',  // method: 请求方法
  data: {id: 1, name: '张三'}, // 用于提交到服务端的参数
  /*
    get方式通过url传递
    post方式按照请求体传递
  */
  success: function(res){
    // res => 拿到的只是响应体
    console.log(res)
  }
})
// res返回格式
$.ajax({ 
  url: './json.php',// url: 请求地址
  type: 'get',  // method: 请求方法
  success: function(res){
    // res会根据服务器响应的Content-Type自动转换为对象
    // 这是jQuery内部实现的
    console.log(res)
  }
})
// 指定响应体类型
$.ajax({ 
  url: './json.php',// url: 
  type: 'get',  // method: 请求方法
  // data: {id: 1, name: '张三'}, // 设置请求参数
  dataType: 'json', // 用于设置响应体的类型(与data参数没关系)
  success: function(res){
    // 一旦设置了dataType选项,就不再关心服务端响应的Content-Type了
    // 客户端会主观地认为服务端返回的就是json格式
    console.log(res)
  }
})
</script>

原生操作中不论请求状态码是多少都会触发回调

jQuery中ajax的回调

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// jQuery中ajax的回调
$.ajax({ 
  url: './time.php', // url
  data: {key1:value1, key2:value2}, // 参数
  type: 'POST',  // method: 请求方法
  dataType: 'json', // 响应体结果
  beforeSend: function(xhr){
    // 在所有发送请求得操作(open、send)之前执行
    console.log('beforeSend', xhr)
  }
  success: function(res){ // 成功的回调
    // 只有请求成功(状态码为200)才会执行这个函数
    // res => 拿到的只是响应体
    console.log(res)
  },
  // timeout: 2000, // 超时时间
  error: function(xhr){ // 失败的回调
    // 只有请求不正常才会执行(状态码不为200)
    console.log('error',xhr)
  },
  complete: function(xhr){
    // 不管成功还是失败都是完成,都会执行这个complete函数
    console.log('complete', xhr)
  }
})
</script>

(2) 高级封装

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 2、jQuery中ajax的快捷方法
$.get('time.php', function(res){
  console.log(res)
})
$.post('time.php', function(res){
  console.log(res)
})
// 服务端设置了json能转换对象,没设置不行
$.get('time.php', {id: 1}, function(res){
  console.log(res)
})
$.post('time.php', {id: 1}, function(res){
  console.log(res)
})
// 无视服务端Content-Type,视作JSON格式
$.getJSON('json.php', {id: 1}, function(res){
  console.log(res)
})
$.postJSON('json.php', {id: 1}, function(res){
  console.log(res)
})
明确请求的方式,根据方式选择快捷方法
</script>

image-20200815190206531

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 跳转其他页面,会白屏,但是有些页面只是部分不相同,可以局部刷新
$(function($){
  // 有一个独立的作用域,顺便确保页面加载完成执行
  $('a.item').on('click', function(){
    var url = $(this).attr('href')
    $('#main').load(url + ' #main>*') // 元素.load(链接+空格+选择器)
    // 阻止a的默认行为
    return false
  })
})
</script>

将jQuery的几种方式汇总一下:

server.js中:

//jQuery 服务
app.all('/jquery-server', (request, response) => {
    //设置响应头  设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*');
    response.setHeader('Access-Control-Allow-Headers', '*');
    // response.send('Hello jQuery AJAX');
    const data = {name:'wallleap'};
    response.send(JSON.stringify(data));
});

前端

<link crossorigin="anonymous" href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script crossorigin="anonymous" src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <!-- crossorigin="anonymous":跨域源请求设置,加上之后向这个资源发送请求时不会携带当前域名下的cookie -->
<div class="container">
    <h2 class="page-header">jQuery发送AJAX请求 </h2>
    <button class="btn btn-primary">GET</button>
    <button class="btn btn-danger">POST</button>
    <button class="btn btn-info">通用型方法ajax</button>
</div>
<script>
  $('button').eq(0).click(function(){
    $.get('http://127.0.0.1:8000/jquery-server', {a:100, b:200}, function(data){
      console.log(data);
    },'json');
  });
  $('button').eq(1).click(function(){
    $.post('http://127.0.0.1:8000/jquery-server', {a:100, b:200}, function(data){
      console.log(data);
    });
  });
  $('button').eq(2).click(function(){
    $.ajax({
      //url
      url: 'http://127.0.0.1:8000/jquery-server',
      //参数
      data: {a:100, b:200},
      //请求类型
      type: 'GET',
      //响应体结果
      dataType: 'json',
      //成功的回调
      success: function(data){
        console.log(data);
      },
      //超时时间
      timeout: 2000,
      //失败的回调
      error: function(){
        console.log('出错啦!!');
      },
      //头信息
      headers: {
        c:300,
        d:400
      }
    });
  });
</script>

(3) 全局事件及配置NProgress显示加载进度

<style>
  .loading{
    display: none;
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background-color: rgba(85, 85, 85, .5);
    text-align: center;
    padding-top: 200px;
    color: #fff;
    font-size: 50px;
  }
</style>
<div class="loading">正在玩命加载中……</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 3、jQuery全局事件处理函数
/*
$.ajax({
  url: 'time.php',
  beforeSend: function(xhr){
    // 显示加载提示
    $('.loading').fadeIn()
    console.log('即将开始请求')
  },
  complete: function(xhr){
    // 结束提示
    $('.loading').fadeOut()
    console.log('请求结束了')
  }
})
*/
// 所有的ajax请求开始和结束
$(document).ajaxStart(function(){
  // 只要有ajax请求发生就会执行
  // 显示加载提示
  $('.loading').fadeIn()
  console.log('即将开始请求')
})
$(document).ajaxStop(function(){
  // 只要有ajax请求发生就会执行
  // 显示加载提示
  $('.loading').fadeOut()
  console.log('请求结束了')
})
$('body').on('click', function(){
  // $.ajax({
  //   url: 'time.php'
  // })
  $.get('time.php')
})
</script>

可以写成链式的:

$(document)
  .ajaxStart(function(){
    ……
  })
  .ajaxStop(function(){
    ……
  })

搭配NProgress

<link rel="stylesheet" href="https://unpkg.com/[email protected]/nprogress.css">
<script src="https://unpkg.com/[email protected]/nprogress.js"></script>
</head>
<body>
<div style="margin-top: 100px;text-align: center;">点击加载</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
// 3、jQuery全局事件处理函数搭配nprogress
// 所有的ajax请求开始和结束
$(document)
  .ajaxStart(function(){
    // 只要有ajax请求发生就会执行
    NProgress.start()
  })
  .ajaxStop(function(){
    // 只要有ajax请求发生就会执行
    NProgress.done()
  })
$('body').on('click', function(){
  $.get('time.php')
})
</script>
</body>

3、axios

(1) 使用教程:http://www.axios-js.com/zh-cn/docs/

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

(2) 特性

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

(3) 安装

使用 npm:

$ npm install axios

使用 bower:

$ bower install axios

使用 cdn:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

(4) 案例

server.js

//axios 服务
app.all('/axios-server', (request, response) => {
    //设置响应头  设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*');
    response.setHeader('Access-Control-Allow-Headers', '*');
    // response.send('Hello jQuery AJAX');
    const data = {name:'wallleap'};
    response.send(JSON.stringify(data));
});

axios发送Ajax请求

<script crossorigin="anonymous" src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.js"></script>
<button>GET</button>
<button>POST</button>
<button>AJAX</button>
<script>
  // https://github.com/axios/axios
  const btns = document.querySelectorAll('button');
  //配置 baseURL
  axios.defaults.baseURL = 'http://127.0.0.1:8000';
  btns[0].onclick = function () {
    //GET 请求 get(url, 其它配置)
    // axios.get('http://127.0.0.1:8000/axios-server', {
    axios.get('/axios-server', { // 配置了baseURL
      //url 参数——>id=100&vip=7
      params: {
        id: 100,
        vip: 7
      },
      //请求头信息
      headers: {
        name: 'wallleap',
        age: 20
      }
    }).then(value => { // 基于promise 处理返回结果 value.config、data、headers、request、status、statusText
      console.log(value);
    });
  }
  btns[1].onclick = function () {
    // POST请求 post(url, 请求体, 其它配置)
    axios.post('/axios-server', { // 请求体
      username: 'admin',
      password: 'password'
    }, {
      //url 
      params: {
        id: 200,
        vip: 9
      },
      //请求头参数
      headers: {
        height: 180,
        weight: 180,
      }
    }).then(response=>{
      // 配置
      console.log(response.config);
      // XMLHttpRequest
      console.log(response.request);
      //响应状态码
      console.log(response.status);
      //响应状态字符串
      console.log(response.statusText);
      //响应头信息
      console.log(response.headers);
      //响应体
      console.log(response.data);
    })
  }
  btns[2].onclick = function(){
    // 通用方式 axios(对象) --> {method, url, 参数, 头信息, 请求体参数}
    axios({
      //请求方法
      method : 'POST',
      //url
      url: '/axios-server',
      //url参数
      params: {
        vip:10,
        level:30
      },
      //头信息
      headers: {
        a:100,
        b:200
      },
      //请求体参数
      data: {
        username: 'admin',
        password: 'password'
      }
    }).then(response=>{
      //响应状态码
      console.log(response.status);
      //响应状态字符串
      console.log(response.statusText);
      //响应头信息
      console.log(response.headers);
      //响应体
      console.log(response.data);
    }).catch(function (error) {
      console.log(error);
    })
  }
</script>

4、fetch

fetch使用:https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch

fetch发送AJAX请求

server.js

//fetch 服务
app.all('/fetch-server', (request, response) => {
    //设置响应头  设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*');
    response.setHeader('Access-Control-Allow-Headers', '*');
    // response.send('Hello jQuery AJAX');
    const data = {name:'wallleap'};
    response.send(JSON.stringify(data));
});
<button>AJAX请求</button>
<script>
  //文档地址
  //https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/fetch
  const btn = document.querySelector('button');
  btn.onclick = function(){
    fetch('http://127.0.0.1:8000/fetch-server?vip=10', {
      //请求方法
      method: 'POST',
      //请求头
      headers: {
        name:'atguigu'
      },
      //请求体
      body: 'username=admin&password=admin'
    }).then(response => {
      // return response.text();
      return response.json();
    }).then(response=>{
      console.log(response);
    });
  }
</script>

五、跨域

1、概念

(1) 同源策略(Same-Origin Policy):最早由Netscape公司提出,是浏览器的一种安全策略,所谓同源是指协议、域名、端口完全相同,只有同源的地址才可以相互通过AJAX的方式请求。

下面来一个同源的案例:

重写一个server.js

const express = require('express');
const app = express();
app.get('/home', (request, response)=>{
    //响应一个页面
    response.sendFile(__dirname + '/index.html');
});
app.get('/data', (request, response)=>{
    response.send('用户数据');
});
app.listen(9000, ()=>{
    console.log("服务已经启动...");
});

运行起来

nodemon server.js

在这个目录下新建index.html

<h1>wallleap</h1>
<button>点击获取用户数据</button>
<script>
  const btn = document.querySelector('button');
  btn.onclick = function(){
    const x = new XMLHttpRequest();
    //这里因为是满足同源策略的, 所以 url 可以简写
    x.open("GET",'/data');
    //发送
    x.send();
    //
    x.onreadystatechange = function(){
      if(x.readyState === 4){
        if(x.status >= 200 && x.status < 300){
          console.log(x.response);
        }
      }
    }
  }
</script>

访问http://127.0.0.1:9000/home,可以访问这个index.html,点击按钮可以获取到数据

(2) 同源或者不同源说的是两个地址之间的关系,不同源地址之间请求我们称之为跨域请求。

image-20200815190429165

跨域的案例:

image-20200815190439633

跨域会报错:

image-20200815190455302

2、解决方案

不同源地址之间如果需要相互请求,必须服务端和客户端配合才能完成

尝试找到一种可以发送不同源请求的方式

可能可以解决跨域的方法

正常图片标签

尝试使用img标签解决跨域

正常link标签

尝试使用link标签解决跨域

尝试使用script标签解决跨域

初级跨域解决方案

服务器端将json用函数包裹返回

客户端使用该函数拿到数据

(1) JSONP

JSON with Padding,是一种借助于script标签发送跨域请求的技巧。这个是非官方的跨域解决方案,是程序员们机智地想出来的,只支持get请求。

其原理就是在客户端借助script标签请求服务端的一个动态网页(php等),服务端的这个动态网页返回一段带有函数调用的JavaScript全局函数调用的脚本,将原本需要返回给客户端的数据传递进去。

以后绝大多数情况都是采用JSONP的手段完成不同源地址之间的跨域请求。

  • 原理演示:

当前目录下,新建js/app.js

const data = {
    name: '测试jsonp'
};
/* 把这个挪走,到测试的html文件中
//处理数据
    function handle(data) {
        //获取 result 元素
        const result = document.getElementById('result');
        result.innerHTML = data.name;
    }
*/
handle(data);

html

<style>
  #result {
    width: 300px;
    height: 100px;
    border: solid 1px #78a;
  }
</style>
<div id="result"></div>
<script>
    //处理数据
    function handle(data) {
        //获取 result 元素
        const result = document.getElementById('result');
        result.innerHTML = data.name;
    }
</script>
<!-- <script src="http://127.0.0.1:5500/jsonp/js/app.js"></script> --> <!-- 利用file://方式访问这个html页面 -->
<!-- 进阶版 -->
<script src="http://127.0.0.1:8000/jsonp-server"></script>

server.js

//jsonp服务
app.all('/jsonp-server',(request, response) => {
    // response.send('console.log("hello jsonp")'); // 前端拿到js代码
    const data = {
        name: '测试jsonp'
    };
    //将数据转化为字符串
    let str = JSON.stringify(data);
    //返回结果
    response.end(`handle(${str})`); // 会返回一个函数调用,实参是想返回给前端的数据(前端需要先声明这个函数)
});
  • 用php演示一下

server.php

<?php
$conn = mysqli_connect('localhost', 'root', '123456', 'demo');
$query = mysqli_query($conn, 'select * from users');
while ($row = mysqli_fetch_assoc($query)) {
  $data[] = $row;
}
if (empty($_GET['callback'])) {
  header('Content-Type: application/json');
  echo json_encode($data);
  exit();
}
// 如果客户端采用的是 script 标记对我发送的请求
// 一定要返回一段 JavaScript
header('Content-Type: application/javascript');
$result = json_encode($data);
$callback_name = $_GET['callback'];
echo "typeof {$callback_name} === 'function' && {$callback_name}({$result})";

image-20200815191013064

  • 原生方式实践jsonp

server.js

//用户名检测是否存在
app.all('/check-username',(request, response) => {
    // response.send('console.log("hello jsonp")');
    const data = {
        exist: 1,
        msg: '用户名已经存在'
    };
    //将数据转化为字符串
    let str = JSON.stringify(data);
    //返回结果
    response.end(`handle(${str})`);
});

前端代码:

用户名: <input type="text" id="username">
<p></p>
<script>
  //获取 input 元素
  const input = document.querySelector('input');
  const p = document.querySelector('p');
  //声明 handle 函数
  function handle(data){
    input.style.border = "solid 1px #f00";
    //修改 p 标签的提示文本
    p.innerHTML = data.msg;
  }
  //绑定事件
  input.onblur = function(){
    //获取用户的输入值
    let username = this.value;
    //向服务器端发送请求 检测用户名是否存在
    //1. 创建 script 标签
    const script = document.createElement('script');
    //2. 设置标签的 src 属性
    script.src = 'http://127.0.0.1:8000/check-username';
    //3. 将 script 插入到文档中
    document.body.appendChild(script);
  }
</script>
  • 封装成一个函数
function jsonp (url, params, callback) {
  var funcName = 'jsonp_' + Date.now() + Math.random().toString().substr(2, 5)

  if (typeof params === 'object') {
    var tempArr = []
    for (var key in params) {
      var value = params[key]
      tempArr.push(key + '=' + value)
    }
    params = tempArr.join('&')
  }

  var script = document.createElement('script')
  script.src = url + '?' + params + '&callback=' + funcName
  document.body.appendChild(script)

  window[funcName] = function (data) {
    callback(data)

    delete window[funcName]
    document.body.removeChild(script)
  }
}

jsonp('http://localhost/jsonp/server.php', { id: 123 }, function (res) {
  console.log(res)
})

jsonp('http://localhost/jsonp/server.php', { id: 123 }, function (res) {
  console.log(res)
})
  • jQuery方式实践jsonp

server.js

// jQuery jsonp
app.all('/jquery-jsonp-server',(request, response) => {
  // response.send('console.log("hello jsonp")');
  const data = {
    name:'尚硅谷',
    city: ['北京','上海','深圳']
  };
  //将数据转化为字符串
  let str = JSON.stringify(data);
  //接收 callback 参数
  let cb = request.query.callback; // 函数名从前端获取
  //返回结果
  response.end(`${cb}(${str})`);
});

前端代码:

<style>
  #result{
    width:300px;
    height:100px;
    border:solid 1px #089;
  }
</style>
<script crossorigin="anonymous" src='https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js'></script>
<button>点击发送 jsonp 请求</button>
<div id="result">

</div>
<script>
  $('button').eq(0).click(function(){
    $.getJSON('http://127.0.0.1:8000/jquery-jsonp-server?callback=?', function(data){
      $('#result').html(`
                名称: ${data.name}<br>
                校区: ${data.city}
            `)
    });
  });
</script>

(2)CORS

HTTP访问控制(CORS)https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

Cross Origin Resource Share,跨域资源共享。CORS是官方的跨域解决方案,它的特点是不需要再客户端做任何特殊的操作,完全在服务器中进行处理,支持get和post请求(其他也支持)。跨域资源共享标准新增了一组HTTP首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。

CORS是通过设置一个响应头来告诉浏览器,这个请求允许跨域,浏览器收到该响应以后就会对响应放行。

server.js

app.all('/cors-server', (request, response)=>{
  //设置响应头
  response.setHeader("Access-Control-Allow-Origin", "*"); // 允许所有源站发送请求
  response.setHeader("Access-Control-Allow-Headers", '*'); // 允许携带的响应头
  response.setHeader("Access-Control-Allow-Method", '*'); // 允许请求方法
  // response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5500"); // 允许单个
  response.send('hello CORS');
});

测试:

<style>
    #result{
        width:200px;
        height:100px;
        border:solid 1px #90b;
    }
</style>
<button>发送请求</button>
<div id="result"></div>
<script>
  const btn = document.querySelector('button');
  btn.onclick = function(){
    //1. 创建对象
    const x = new XMLHttpRequest();
    //2. 初始化设置
    x.open("GET", "http://127.0.0.1:8000/cors-server");
    //3. 发送
    x.send();
    //4. 绑定事件
    x.onreadystatechange = function(){
      if(x.readyState === 4){
        if(x.status >= 200 && x.status < 300){
          //输出响应体
          console.log(x.response);
        }
      }
    }
  }
</script>
// 允许远端访问(甚至直接打开文件的方式也可以file://……)
header('Access-Control-Allow-Origin: *') // 允许所有
header('Access-Control-Allow-Origin: http://localhost/index.html') // 允许单个

eg:

<?php

$conn = mysqli_connect('localhost', 'root', '123456', 'demo');

$query = mysqli_query($conn, 'select * from users');

while ($row = mysqli_fetch_assoc($query)) {
  $data[] = $row;
}

// 一行代码搞定
// 允许跨域请求
header('Access-Control-Allow-Origin: *');

header('Content-Type: application/json');
echo json_encode($data);

客户端

<script src="jquery.js"></script>
<script>
  $.get('http://localhost/cors.php', {}, function (res) {
    console.log(res)
  })
</script>

这种方案无序客户端作出任何变化(不用改代码),只是在被请求的服务端响应的时候添加一个Access-Control-Allow-Origin的响应头,表示这个资源是否允许指定域请求。

六、XMLHttpRequest2.0

暂作了解,无需着重看待

更易用,更强大。

React全家桶(技术栈)学习笔记

React全家桶(技术栈)学习笔记

第1章 React入门

1.1 React的基本认识

1.1.1 官网

1) 英文官网: https://reactjs.org/

2) 中文官网: https://doc.react-china.org/

1.1.2 介绍描述

1) 用于构建用户界面的 JavaScript 库(只关注于View)

  • JS库:
    • jQuery——函数库(方法、函数包装DOM操作)
    • React 基本上不操作DOM——JS框架
  • 构建用户界面:把数据展现出来

2) 由Facebook开源

1.1.3 React的特点

1) Declarative(声明式编码)

(申请一块内存,只需要声明一个变量即可)不需要亲自操作DOM,只需要告诉它,我要更新,就会帮你更新,只需要更新数据,界面不需要手动更新(以前需要更新DOM)

2) Component-Based(组件化编码)

简化特别复杂的功能,可以拆分为多个简单的部分(一个小的界面功能就是一个组件),维护也方便

3) Learn Once, Write Anywhere(支持客户端与服务器渲染)

一次学习,随处编写:不仅能写web应用,还能写React Native打包为Android、IOS应用

4) 高效

5) 单向数据流

1.1.4 React高效的原因(区域、次数——更新界面效率提高)

1) 虚拟(virtual)DOM,,不总是直接操作DOM

  • 虚拟DOM:对象——与组件对应,修改映射到真实的DOM上(批量修改、界面重绘次数少)

2) DOM Diff算法,,最小化页面重绘

  • 界面中组件是否更新(更新区域小)

1.2 React的基本使用

注意: 此时只是测试语法使用, 并不是真实项目开发使用

1.2.1 效果

React基本使用

将h1标签利用react放到test中

1.2.3 相关js库

可以到bootcdn引用地址,访问链接Ctrl+S保存到本地

1) react.js: React的核心库

  • development.js:开发版,开发编写的时候使用
  • production.min.js:生产版,上线的时候使用,压缩过的

2) react-dom.js: 提供操作DOM的react扩展库

3) babel.min.js: 解析JSX语法代码转为纯JS语法代码的库,这里不是ES6转ES5(jsx是js扩展语法)

1.2.4 在页面中导入js

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>

1.2.5 编码

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>01_HelloWorld</title>
</head>
<body>
  <div id="test"></div>

  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <script type="text/babel">/*告诉babel.js解析里面的jsx的代码*/
    // 1. 创建虚拟DOM元素对象
    var vDom = <h1>Hello React!</h1>   // jsx,不是字符串,不能加引号
    // 2. 将虚拟DOM渲染到页面真实DOM容器中
    ReactDOM.render(vDom, document.getElementById('test'))  // react-dom.js提供的  render——渲染   将vDom加入到#test中
  </script>
</body>
</html>

1.2.6 使用React开发者工具调试

React开发调试插件

React Developer Tool.crx

1.3 React JSX

1.3.1 效果

ReactJSX

两个#test分别加入相应内容

代码:

<div id="test1"></div>
<div id="test2"></div>

<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>

<script> // 还没用到jsx语法,不需要babel
  const msg = 'I Like You!'
  const myId = 'Atguigu'

  // 1.创建虚拟DOM
  // var element = React.createElement('h1', {id:'myTitle'},'hello')
  const vDom1 = React.createElement('h2', {id:myId.toLowerCase()},msg.toUpperCase())
  // 2.渲染虚拟DOM
  ReactDOM.render(vDom1, document.getElementById('test1'))
</script>

<script type="text/babel">
  // 1.创建虚拟DOM
  const vDom2 = <h3 id={myId.toUpperCase()}>{msg.toLowerCase()}</h3>  // 变量用{}括起来
  // 2.渲染虚拟DOM
  ReactDOM.render(vDom2, document.getElementById('test2'))
</script>

1.3.2 虚拟DOM

1) React提供了一些API来创建一种 特别 的一般js对象

a. var element = React.createElement('h1', {id:'myTitle'},'hello')

b. 上面创建的就是一个简单的虚拟DOM对象,babel将会把jsx语法转为上述的形式

2) 虚拟DOM对象最终都会被React转换为真实的DOM(虚拟DOM中的对应真实DOM中的标签元素)

3) 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界面

补充知识:debugger可以在某条js代码处添加断点

虚拟DOM——轻对象,更新虚拟DOM页面不重绘

真实DOM——重对象,更新真实DOM页面会发生变化(页面重绘)

1.3.3 JSX

1) 全称: JavaScript XML

2) react定义的一种类似于XML的JS扩展语法: XML+JS

3) 作用: 用来创建react虚拟DOM(元素)对象

  • var ele = <h1>Hello JSX!</h1>

  • 注意1: 它不是字符串, 也不是HTML/XML标签

  • 注意2: 它最终产生的就是一个JS对象

4) 标签名任意: HTML标签或其它标签

5) 标签属性任意: HTML标签属性或其它

6) 基本语法规则

  • 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析

  • 遇到以 { 开头的代码,以JS语法解析: 标签中的js代码必须用{ }包含

7) babel.js的作用

  • 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行

  • 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理

1.3.4 渲染虚拟DOM(元素)

1) 语法: ReactDOM.render(virtualDOM, containerDOM)

2) 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示

3) 参数说明

  • 参数一: 纯js或jsx创建的虚拟dom对象

  • 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

1.3.5 建虚拟DOM的2种方式

1) 纯JS(一般不用)

React.createElement('h1', {id:'myTitle'}, title)

2) JSX:

<h1 id='myTitle'>{title}</h1>

1.3.6 JSX练习

需求: 动态展示列表数据

JSX

代码:

<h2>前端JS框架列表</h2>
<div id="example1"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>

<script type="text/babel">
  /*
    功能: 动态展示列表数据
    - 如何将一个数据的数组,转换为一个标签的数组
      使用数组的map()方法
  */
  // 数据:名称、类型,数组存放
  const names = ['jQuery', 'zepto', 'angular', 'react', 'vue']
  // 1.创建虚拟DOM,有嵌套结构,最好用小括号括起来
  const ul = (
    <ul>
      {
        names.map((name, index) => <li key={index}>{name}</li>)
      }
    </ul>
  )
  // 2.渲染虚拟DOM
  ReactDOM.render(ul, document.getElementById('example1'))
</script>

1.4 模块与组件和模块化与组件化的理解

1.4.1 模块

1) 理解: 向外提供特定功能的js程序, 一般就是一个js文件

2) 为什么: js代码更多更复杂

3) 作用: 复用js, 简化js的编写, 提高js运行效率

有特定功能的js文件,内部有数据及对数据的操作

  • 数据:变量

  • 操作:函数

私有的函数向外暴露

  • 暴露一个函数:暴露函数本身
  • 暴露多个函数:以对象形式暴露

1.4.2 组件

1) 理解: 用来实现特定(局部)功能效果代码集合(html/css/js)

2) 为什么: 一个界面的功能更复杂

3) 作用: 复用编码, 简化项目编码, 提高运行效率

1.4.3 模块化

当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

形容项目或编码

1.4.4 组件化

当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

component

第2章 React面向组件编程

面向对象——面向模块——面向组件

2.1 基本理解和使用

2.1.1 效果

组件

组件标签:可以随便取的标签,首字母大写,与HTML标签区分开

2.1.2 自定义组件(Component) :

1) 定义组件(2种方式)

方式1: 工厂函数组件(简单组件:没有状态的组件)——效率高,不需要创建对象

function MyComponent(){
    return <h2>工厂函数组件(简单组件)</h2>
}

方式2: ES6类组件(复杂组件)——需要创建对象,有了状态只能使用这种方式

class MyComponent2 extends React.Component{
  render(){
    console.log(this)  // 组件类对象 MyComponent2{...}
    return <h2>ES6类组件(复杂组件)</h2>
  }
}

2) 渲染组件标签

ReactDOM.render(<MyComponent  />, document.getElementById('example1'))
ReactDOM.render(<MyComponent2  />, document.getElementById('example2'))

2.1.3 注意

1) 组件名必须首字母大写

2) 虚拟DOM元素只能有一个根元素

3) 虚拟DOM元素必须有结束标签

2.1.4 render()渲染组件标签的基本流程

1) React内部会创建组件实例对象

2) 得到包含的虚拟DOM并解析为真实DOM

3) 插入到指定的页面元素内部

2.2 组件三大属性1: state

2.2.1 效果

state

2.2.2 理解

1) state是组件对象最重要的属性, 值是对象(可以包含多个数据)

2) 组件被称为”状态机”, 通过更新组件的state来更新对应的页面显示(重新渲染组件)

2.2.3 编码操作

1) 初始化状态:

constructor (props) {
  super(props)
  this.state = {
   stateProp1 : value1,
   stateProp2 : value2
  }
}

2) 读取某个状态值

this.state.statePropertyName

3) 更新状态—->组件界面更新

this.setState({
  stateProp1 : value1,
  stateProp2 : value2
})

2.2.4 代码

<div id="example"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>

<script type="text/babel">
  /*
  需求: 自定义组件, 功能说明如下
    1. 显示h2标题, 初始文本为: 你喜欢我
    2. 点击标题更新为: 我喜欢你
  */
  // 1.定义组件
  class Like extends React.Component {
    constructor(props){
      super(props)
      // 初始化状态
      this.state = {
        isLikeMe: false
      }

      // 将新增方法中的this强制绑定为组件对象
      this.handleClick = this.handleClick.bind(this) // 也可以不在这里绑定
    }

    handleClick(){
      // console.log(this) // handleClick是新添加方法,内部this默认不是组件对象,而是undefined  render是重写组件类的方法 到上面或下面绑定this
      // 得到状态,并取反
      const isLikeMe = !this.state.isLikeMe
      // 更新状态
      this.setState({isLikeMe}) // isLikeMe: isLikeMe
    }
    render() {
      // 读取状态
      // const isLikeMe = this.state.isLikeMe
      const {isLikeMe} = this.state // 解构赋值
      return <h2 onClick={this.handleClick}>{isLikeMe?'你喜欢我':'我喜欢你'}</h2> // this——组件对象 // this.handleClick.bind(this) —— 在这里绑定也可以
    }
  }
  // 2.渲染组件标签
  ReactDOM.render(<Like />, document.getElementById('example'))
</script>

2.3 组件三大属性2: props

2.3.1 效果

需求: 自定义用来显示一个人员信息的组件
  1). 姓名必须指定
  2). 如果性别没有指定, 默认为男
  3). 如果年龄没有指定, 默认为18

props

2.3.2 理解

1) 每个组件对象都会有props(properties的简写)属性

2) 组件标签的所有属性都保存在props中

2.3.3 作用

1) 通过标签属性从组件外向组件内传递变化的数据

2) 注意: 组件内部不要修改props数据

2.3.4 编码操作

1) 内部读取某个属性值

this.props.propertyName

2) 对props中的属性值进行类型限制和必要性限制

注意:

自 React v15.5 起,React.PropTypes 已移入另一个包中。请使用 prop-types 代替。需要先引入该库。

我们提供了一个 codemod 脚本来做自动转换。

Person.propTypes = {
    name: React.PropTypes.string.isRequired,
    age: React.PropTypes.number.isRequired
}

3) 扩展属性: 将对象的所有属性通过props传递

<Person {...person}/>

4) 默认属性值

Person.defaultProps = {
    name: 'Mary'
}

5) 组件类的构造函数

constructor (props) {
    super(props)
    console.log(props) // 查看所有属性

}

2.3.5 代码

<div id="example1"></div>
<div id="example2"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script> <!-- 验证类型和必要性 -->
<script type="text/javascript" src="../js/babel.min.js"></script>

<script type="text/babel">

  /*
需求: 自定义用来显示一个人员信息的组件, 效果如页面. 说明
  1). 如果性别没有指定, 默认为男
  2). 如果年龄没有指定, 默认为18
  */

  // 1、定义组件
  /*function Person(props){
    return (
      <ul>
        <li>姓名:{props.name}</li>
        <li>性别:{props.sex}</li>
        <li>年龄:{props.age}</li>
      </ul>
    )
  }*/
  class Person extends React.Component{
    render(){
      return (
        <ul>
          <li>姓名:{this.props.name}</li>  // this——组件对象
          <li>性别:{this.props.sex}</li>
          <li>年龄:{this.props.age}</li>
        </ul>
      )
    }
  }
  // 指定属性默认值
  Person.defaultProps = {
    sex: '男',
    age: 18
  }
  // 指定属性值的类型和必要性
  Person.propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number
  }

  // 2、渲染组件标签
  const p1 = {
    name: 'Tom',
    sex: '女',
    age: 18
  }
  // ReactDOM.render(<Person name={p1.name} sex={p1.sex} age={p1.age}/>, document.getElementById('example1'))
  ReactDOM.render(<Person {...p1}/>, document.getElementById('example1')) // ...作用:1.打包:function fn(...as){} fun(1,2,3) 2.解包:const arr1=[1,2,3] const arr2=[6,...arr1,9] 这里也是在解包
  const p2 = {
    name: 'JACK',
    age: 17
  }
  ReactDOM.render(<Person name={p2.name} age={p2.age}/>, document.getElementById('example2'))
</script>

2.3.6 面试题

问题: 请区别一下组件的props和state属性

1) state: 组件自身内部可变化的数据

2) props: 从组件外部向组件内部传递数据,组件内部只读不修改

2.4 组件三大属性3: refs与事件处理

2.4.1 效果

需求: 自定义组件, 功能说明如下:

  1. 点击按钮, 提示第一个输入框中的值
  2. 当第2个输入框失去焦点时, 提示这个输入框中的值

props_event

2.4.2 组件的3大属性之二: refs属性

1) 组件内的标签都可以定义ref属性来标识自己

a. <input type=”text” ref={input => this.msgInput = input}/>

b. 回调函数在组件初始化渲染完或卸载时自动调用

2) 在组件中可以通过this.msgInput来得到对应的真实DOM元素

3) 作用: 通过ref获取组件内容特定标签对象, 进行读取其相关数据

2.4.3 事件处理

1) 通过onXxx属性指定组件的事件处理函数(注意大小写)

a. React使用的是自定义(合成)事件, 而不是使用的原生DOM事件

b. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)

2) 通过event.target得到发生事件的DOM元素对象

handleFocus(event) {
 event.target // 返回input对象
}

2.4.4 强烈注意

1) 组件内置的方法中的this为组件对象

2) 在组件类中自定义的方法中this为null

a. 强制绑定this: 通过函数对象的bind()

b. 箭头函数(ES6模块化编码时才能使用)

2.4.5 代码

<div id="example"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>

<script type="text/babel">
  /*
  需求: 自定义组件, 功能说明如下:
    1. 界面如果页面所示
    2. 点击按钮, 提示第一个输入框中的值
    3. 当第2个输入框失去焦点时, 提示这个输入框中的值
 */
  // 1、定义组件 // React中必须要结束标签
  class MyComponent extends React.Component{

    constructor(props){
      super(props)
      this.showInput = this.showInput.bind(this)
      this.handleBlur = this.handleBlur.bind(this)
    }

    showInput(){
      const input = this.refs.content
      // alert(input.value)
      alert(this.input.value)
    }

    handleBlur(event){
      alert(event.target.value)
    }

    render(){
      return(
        <div>
          <input type="text" ref="content"/>&nbsp;&nbsp;
          <input type="text" ref={input => this.input = input}/>&nbsp;&nbsp;
          <button onClick={this.showInput}>提示输入</button>&nbsp;&nbsp;
          <input type="text" placeholder="失去焦点提示内容" onBlur={this.handleBlur}/>
        </div>
      )
    }
  }

  // 2、渲染组件标签
  ReactDOM.render(<MyComponent/>, document.getElementById('example'))
</script>

2.4.6 区别

  1. state
  2. props
  3. refs

2.5 组件的组合

2.5.1 效果

功能: 组件化实现此功能
  1. 显示所有todo列表
  2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本

component组合使用

2.5.2 功能界面的组件化编码流程(无比重要)

1) 拆分组件: 拆分界面,抽取组件

2) 实现静态组件: 使用组件实现静态页面效果(只有静态界面,没有动态数据和交互)

3) 实现动态组件

  • 实现初始化数据动态显示

  • 实现交互功能(从绑定事件监听开始)

2.5.3 代码

<div id="example"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
  // 静态组件——>动态组件
  /*
  * -名称、类型:todos、数组,Add需要、List需要
  * -数据保存在哪个组件内?
  *   放到App
  *   看数据是某个组件需要(给这个),还是某些组件需要(给共同的父组件)
  * -需要在子组件中改变父组件的状态
  *    子组件中不能直接改变父组件的状态
  *    状态在哪个组件,更新状态的行为就应该定义在哪个组件,由子组件来调用(通过组件属性传递)
  *    父组件定义函数,传递给子组件,子组件调用
  */
  class App extends React.Component{

    constructor(props){
      super(props)
      // 初始化状态
      this.state = {
        todos: ['吃饭', '睡觉', '敲代码', '打游戏']
      }

      this.addTodo = this.addTodo.bind(this)  // 没定义加上bind
    }

    addTodo(todo){
      // this.state.todos.unshift(todo) // 不能这样做
      const {todos} = this.state
      todos.unshift(todo)
      // 更新状态
      this.setState({todos})
    }

    render(){
      const {todos} = this.state
      return(
        <div>
          <h1>Simple TODO List</h1>
          <Add count={todos.length} addTodo={this.addTodo}/>
          <List todos={todos}/>
        </div>
      )
    //  <List todos={this.state.todos}/> 前面赋值了
    }
  }

  class Add extends React.Component{
    constructor(props){
      super(props)
      this.add = this.add.bind(this)
    }
    add(){
      // 1、读取输入的数据
      const todo = this.todoInput.value.trim()
      // 2、检查合法性
      if(!todo){
        return
      }
      // 3、添加
      this.props.addTodo(todo)
      // 4、清除输入
      this.todoInput.value = ''
    }
    render(){
      return(
        <div>
          <input type="text" ref={input => this.todoInput=input}/>
          <button onClick={this.add}>add #{this.props.count+1}</button>
        </div>
      )
    }
  }
  Add.propTypes = {
    count: PropTypes.number.isRequired,
    addTodo: PropTypes.func.isRequired
  }

  class List extends React.Component{
    render(){
      return(
        <ul>
          {
            this.props.todos.map((todo, index) => <li key={index}>{todo}</li>)
          }
        </ul>
      )
      /*this.props.todos.map((todo, index) => {return <li key={index}>{todo}</li>})*/
      /* 加了大括号需要加return */
    }
  }

  List.protoTypes = {
    todos: PropTypes.array.isRequired
  }


  ReactDOM.render(<App />,document.getElementById('example'))
</script>

2.6 收集表单数据

2.6.1 效果

需求: 自定义包含表单的组件
  1. 输入用户名密码后, 点击登陆提示输入信息
  3. 不提交表单

component表单 (2)

2.6.2 理解

1) 问题: 在react应用中, 如何收集表单输入数据

2) 包含表单的组件分类

a. 受控组件: 表单项输入数据能自动收集成状态

b. 非受控组件: 需要时才手动读取表单输入框中的数据

2.6.3 代码

<div id="example"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
  /*
  需求: 自定义包含表单的组件
    1. 界面如下所示
    2. 输入用户名密码后, 点击登陆提示输入信息
    3. 不提交表单
  */
  class LoginForm extends React.Component{
    constructor(props){
      super(props)
      // 初始化状态
      this.state  = {
        pwd: ''
      }
      this.handleSubmit = this.handleSubmit.bind(this)
      this.handleChange = this.handleChange.bind(this)
    }

    handleSubmit(event){
      const name = this.nameInput.value // 非受控组件
      const {pwd} = this.state // 受控组件
      alert(`准备提交的用户名为${name},密码为${pwd}`)
      // 阻止事件的默认行为
      event.preventDefault()
    }
    // 当事件和标签是同一个时,使用event更方便

    handleChange(event){
      // 读取输入的值
      const pwd = event.target.value
      // 更新pwd的状态
      this.setState({pwd})
    }

    render(){
      return(
        <form action="/test" onSubmit={this.handleSubmit}>
          用户名:<input type="text" ref={input => this.nameInput = input}/>
          密码:<input type="password" value={this.state.pwd} onChange={this.handleChange}/>
          <input type="submit" value="登录"/>
        </form>
      )
    }
    // 原生jsonChange事件是在失去焦点时触发,react中是输入则触发
  }

  ReactDOM.render(<LoginForm />, document.getElementById('example'))
</script>

2.7 组件生命周期

2.7.1 效果

需求: 自定义组件
  1. 让指定的文本做显示/隐藏的渐变动画
  2. 切换持续时间为2S
  3. 点击按钮从界面中移除组件界面

component生命周期

2.7.2 理解

1) 组件对象从创建到死亡它会经历特定的生命周期阶段

2) React组件对象包含一系列的勾子函数(生命周期回调函数), 在生命周期特定时刻回调

3) 我们在定义组件时, 可以重写特定的生命周期回调函数, 做特定的工作

2.7.3 生命周期流程图

生命周期流程图

Mount:挂载,将虚拟标签放到容器(页面)中

render()渲染

左边初始化过程,这些方法称为声明周期回调函数,或称为生命周期的勾子,这些方法在特定的时刻调用

(回调函数:你定义的,你没有调用,但是最终执行了;声明式编程,流程设定好,、命令式编程jQuery,每一步自己操作)

will将、did完成

2.7.4 生命周期详述

1) 组件的三个生命周期状态:

  • Mount:插入真实 DOM

  • Update:被重新渲染

  • Unmount:被移出真实 DOM

2) React 为每个状态都提供了勾子(hook)函数,可重写

  • componentWillMount()

  • componentDidMount()

  • componentWillUpdate()

  • componentDidUpdate()

  • componentWillUnmount()

3) 生命周期流程:

a. 第一次初始化渲染显示: ReactDOM.render()

  • constructor(): 创建对象初始化state

  • componentWillMount() : 将要插入回调

  • render() : 用于插入虚拟DOM回调

  • componentDidMount() : 已经插入回调

b. 每次更新state: this.setSate()

  • componentWillUpdate() : 将要更新回调

  • render() : 更新(重新渲染)

  • componentDidUpdate() : 已经更新回调

c. 移除组件: ReactDOM.unmountComponentAtNode(containerDom)

  • componentWillUnmount() : 组件将要被移除回调

三个阶段,可以都打印一下,看下方法执行的过程(与写的顺序无关)

2.7.5 重要的勾子

1) render(): 初始化渲染或更新渲染调用

2) componentDidMount(): 开启监听, 发送ajax请求

3) componentWillUnmount(): 做一些收尾工作, 如: 清理定时器

4) componentWillReceiveProps(): 后面需要时讲

2.7.6 代码

<div id="example"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
  /*
  需求: 自定义组件
    1. 让指定的文本做显示/隐藏的动画
    2. 切换时间为2S
    3. 点击按钮从界面中移除组件界面
   */
  class Life extends React.Component{

    constructor(props){
      super(props)
      // 初始化状态
      this.state = {
        opacity: 1
      }

      this.distroyComponent = this.distroyComponent.bind(this)
    }

    distroyComponent(){
      ReactDOM.unmountComponentAtNode(document.getElementById('example'))
    }

    // 重写方法
    componentDidMount(){
      // 启动循环定时器
      this.intervalId = setInterval(function () { // 两个函数需要同一个变量,放到上一层共同组件上
        console.log('定时器执行……')
        let {opacity} = this.state
        opacity -= 0.1
        if(opacity<=0){
          opacity = 1
        }
        // 更新状态
        this.setState({opacity})
      }.bind(this), 200) // componentDidMount的this
    }
    componentWillUnmount(){
      // 清理定时器
      clearInterval(this.intervalId)
    }

    render(){ // 一旦改变,就会重新调用;永远写在其他下方,构造器在最上方
      const {opacity} = this.state
      return(
        <div>
          <h2 style={{opacity: opacity}}>{this.props.msg}</h2>
          <button onClick={this.distroyComponent}>不活了</button>
        </div>
      )
    }// style中两个大括号,外面的代表写的是js代码,里面的是对象(样式名:值,也可以写ES6)
  }

  ReactDOM.render(<Life msg="react太难了"/>, document.getElementById('example'))
</script>

2.8 虚拟DOM与DOM Diff算法

虚拟DOM:减少操作真实DOM的次数,更新界面次数变少

DOM Diff算法:计算哪里需要更新,哪里不需要更新,减少更新界面的区域

共同提高更新界面的效率

2.8.1 效果

component虚拟DOM

<div id="example"></div>
<br>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
  /*
  验证:
  虚拟DOM+DOM Diff算法: 最小化页面重绘
  */

  class HelloWorld extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
          date: new Date()
      }
    }

    componentDidMount () {
      setInterval(() => {
        this.setState({
            date: new Date()
        })
      }, 1000)
    }

    render () {
      console.log('render()')
      return (
        <p>
          Hello, <input type="text" placeholder="Your name here"/>!&nbsp;
          <span>It is {this.state.date.toTimeString()}</span>
        </p>
      )
    }
  }

  ReactDOM.render(
    <HelloWorld/>,
    document.getElementById('example')
  )
</script>

只有时间更新,其他不更新

2.8.2 基本原理图

基本原理

  • 初始化:虚拟DOM树(div>p>span……),更新虚拟DOM界面不会变——>更新真实DOM界面才会变化(更新状态)

  • 更新(关键):调用setState()更新状态(会进行对比)——>根据差异更新真实DOM、重绘页面变化的区域

第3章 react应用(基于react脚手架)

3.1 使用create-react-app创建react应用

3.1.1 react脚手架

1) xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目

  • 包含了所有需要的配置

  • 指定好了所有的依赖

  • 可以直接安装/编译/运行一个简单效果

2) react提供了一个用于创建react项目的脚手架库: create-react-app

3) 项目的整体技术架构为: react + webpack + es6 + eslint

4) 使用脚手架开发的项目的特点: 模块化、组件化、工程化

3.1.2 创建项目并启动

npm install -g create-react-app 全局下载

create-react-app hello-react

cd hello-react

npm start

浏览器访问http://localhost:3000

注:

npm root -g 查看全局下载目录
C:\Users\Shinelon\AppData\Roaming\npm\node_modules

3.1.3 react脚手架项目结构

ReactNews

 |--node_modules---第三方依赖模块文件夹

 |--public

   |-- index.html-----------------主页面

 |--scripts

   |-- build.js-------------------build打包引用配置

   |-- start.js-------------------start运行引用配置

 |--src------------源码文件夹

   |--components-----------------react组件

      |-- app.jsx

   |--index.css

   |--index.js-------------------应用入口js(main.js)

 |--.gitignore------git版本管制忽略的配置

 |--package.json----应用包配置文件 

 |--README.md-------应用描述说明的readme文件

package.json

  • "dependencies":运行时依赖
  • "devDependencies":开发时依赖,编译打包时需要,开发时不需要,编译打包时工具包

public/index.html主界面

  • 只有一个<div id="root"></div>,依靠组件

src/index.js应用入口

  • 引入包(import * from "*")、CSS(import "*.css")
  • 渲染组件

README.md对项目的说明文件

SPA(Single Page Application):单应用

  • index.html
<div id="root"></div>
  • src/components/app.jsx
import React, {Component} from 'react'
import logo from '../logo.svg'

export default class App extends Component {
  render() {
    return(
      <div>
        <img className='logo' src={logo} alt="logo"/>
        <p className="title">react组件</p>
      </div>
    )
  }
}
  • src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/app'

import './index.css'

ReactDOM.render(<App />, document.getElementById('root'))
  • src/index.css
.logo{
  width: 200px;
  height: 200px;
}
.title{
  color: red;
  font-size: 25px;
}

cd react_app
npm startnpm run start

3.2 demo: 评论管理

3.2.1 效果

demo_comment

3.2.2 拆分组件

image-20200708170522553

应用组件: App

  • state: comments/array

添加评论组件: CommentAdd

  • state: username/string, content/string

  • props: add/func

评论列表组件: CommentList

  • props: comment/object, delete/func, index/number

评论项组件: CommentItem

  • props: comments/array, delete/func

3.2.3 实现静态组件

render(){return}中的内容

  • src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/app/app'

ReactDOM.render(<App/>, document.getElementById('root'))
  • src/components/app/app.jsx
import React from 'react'
import CommentAdd from '../comment-add/comment-add'
import CommentList from '../comment-list/comment-list'

export default class App extends React.Component {

  constructor (props) {
    super(props)

    this.state = {
      comments: []
    }

    this.delete = this.delete.bind(this)
  }

  componentDidMount () {
    //模拟异步获取数据
    setTimeout(() => {
      const comments = [
        {
          username: "Tom",
          content: "ReactJS好难啊!",
          id: Date.now()
        },
        {
          username: "JACK",
          content: "ReactJS还不错!",
          id: Date.now() + 1
        }
      ]
      this.setState({
        comments
      })
    }, 1000)
  }

  add = (comment) => {
    let comments = this.state.comments
    comments.unshift(comment)
    this.setState({ comments })
  }

  delete (index) {
    let comments = this.state.comments
    comments.splice(index, 1)
    this.setState({ comments })
  }

  render () {
    return (
      <div>
        <header className="site-header jumbotron">
          <div className="container">
            <div className="row">
              <div className="col-xs-12">
                <h1>请发表对React的评论</h1>
              </div>
            </div>
          </div>
        </header>
        <div className="container">
          <CommentAdd add={this.add}/>
          <CommentList comments={this.state.comments} delete={this.delete}/>
        </div>
      </div>
    )
  }
}

// export default App 可以写到上面
  • src/components/comment-add/comment-add.jsx
import React from 'react'
import PropTypes from 'prop-types'

class CommentAdd extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      username: '',
      content: ''
    }
    this.addComment = this.addComment.bind(this)
    this.changeUsername = this.changeUsername.bind(this)
    this.changeContent = this.changeContent.bind(this)
  }

  addComment () {
    // 根据输入的数据创建评论对象
    let { username, content } = this.state
    let comment = { username, content }
    // 添加到comments中, 更新state
    this.props.add(comment)
    // 清除输入的数据
    this.setState({
      username: '',
      content: ''
    })
  }

  changeUsername (event) {
    this.setState({
      username: event.target.value
    })
  }

  changeContent (event) {
    this.setState({
      content: event.target.value
    })
  }

  render () {
    return (
      <div className="col-md-4">
        <form className="form-horizontal">
          <div className="form-group">
            <label>用户名</label>
            <input type="text" className="form-control" placeholder="用户名"
                   value={this.state.username} onChange={this.changeUsername}/>
          </div>
          <div className="form-group">
            <label>评论内容</label>
            <textarea className="form-control" rows="6" placeholder="评论内容"
                      value={this.state.content} onChange={this.changeContent}></textarea>
          </div>
          <div className="form-group">
            <div className="col-sm-offset-2 col-sm-10">
              <button type="button" className="btn btn-default pull-right" onClick={this.addComment}>提交</button>
            </div>
          </div>
        </form>
      </div>
    )
  }
}
CommentAdd.propTypes = {
  add: PropTypes.func.isRequired
}

export default CommentAdd
  • src/components/comment-list/comment-list.css
.reply {
  margin-top: 0px;
}
  • src/components/comment-list/comment-list.jsx
import React from 'react'
import PropTypes from 'prop-types'
import CommentItem from '../comment-item/comment-item'
import './commentList.css'
class CommentList extends React.Component {
  constructor (props) {
    super(props)
  }
  render () {
    let comments = this.props.comments
    let display = comments.length > 0 ? 'none' : 'block'

下面h2中由于用了双大括号,一直报错,因此请直接到github看源码

    return (
      <div className="col-md-8">
        <h3 className="reply">评论回复:</h3>
        <h2 style=双大括号 display: display 双大括号>暂无评论,点击左侧添加评论</h2>
        <ul className="list-group">
          {
            comments.map((comment, index) => {
              console.log(comment)
              return <CommentItem comment={comment} key={index} index={index} delete={this.props.delete}/>
            })
          }
        </ul>
      </div>
    )
    }
}
CommentList.propTypes = {
  comments: PropTypes.array.isRequired,
  delete: PropTypes.func.isRequired
}
export default CommentList
  • src/components/comment-item/comment-item.css
li {
  transition: .5s;
  overflow: hidden;
}

.handle {
  width: 40px;
  border: 1px solid #ccc;
  background: #fff;
  position: absolute;
  right: 10px;
  top: 1px;
  text-align: center;
}

.handle a {
  display: block;
  text-decoration: none;
}

.list-group-item .centence {
  padding: 0px 50px;
}

.user {
  font-size: 22px;
}
  • src/components/comment-item/comment-item.jsx
import React from 'react'
import PropTypes from 'prop-types'
import './commentItem.css'

class CommentItem extends React.Component {
  constructor (props) {
    super(props)
    this.deleteComment = this.deleteComment.bind(this)
  }

  deleteComment () {
    let username = this.props.comment.username
    if (window.confirm(`确定删除${username}的评论吗?`)) {
      this.props.delete(this.props.index)
    }
  }

  render () {
    let comment = this.props.comment
    return (
      <li className="list-group-item">
        <div className="handle">
          <a href="javascript:" onClick={this.deleteComment}>删除</a>
        </div>
        <p className="user"><span >{comment.username}</span><span>说:</span></p>
        <p className="centence">{comment.content}</p>
      </li>
    )
  }
}
CommentItem.propTypes = {
  comment: PropTypes.object.isRequired,
  index: PropTypes.number.isRequired,
  delete: PropTypes.func.isRequired
}

export default CommentItem

3.2.4 实现动态组件

动态展示初始化数据

  • 初始化状态数据

  • 传递属性数据

响应用户操作, 更新组件界面

  • 绑定事件监听, 并处理

  • 更新state

第4章 react ajax

4.1 理解

4.1.1 前置说明

1) React本身只关注于界面, 并不包含发送ajax请求的代码

2) 前端应用需要通过ajax请求与后台进行交互(json数据)

3) react应用中需要集成第三方ajax库(或自己封装)

4.1.2 常用的ajax请求库

1) jQuery: 比较重, 如果需要另外引入不建议使用

2) axios: 轻量级, 建议使用

​ a. 封装XmlHttpRequest对象的ajax

​ b. promise风格

​ c. 可以用在浏览器端和node服务器端

3) fetch: 原生函数, 但老版本浏览器不支持

​ a. 不再使用XmlHttpRequest对象提交ajax请求

​ b. 为了兼容低版本的浏览器, 可以引入兼容库fetch.js

4.1.3 效果

需求:
  1. 界面效果如下
  2. 根据指定的关键字在github上搜索匹配的最受关注的库
  3. 显示库名, 点击链接查看库
4. 测试接口: https://api.github.com/search/repositories?q=r&sort=stars

ajax

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>11_ajax</title>
  <style>
    h2{
      text-align: center;
      margin-top: 200px;
    }
  </style>
</head>
<body>
<div id="example"></div>

<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>

<script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.js"></script>

<script type="text/babel">
  /*
  需求:
    1. 界面效果如下
    2. 根据指定的关键字在github上搜索匹配的最受关注的库
    3. 显示库名, 点击链接查看库
    4. 测试接口: https://api.github.com/search/repositories?q=r&sort=stars
  */
  class MostStarRepo extends React.Component{
    state = {
      repoName: '',
      repoUrl: ''
    }

    componentDidMount(){
      // 使用axios发送异步的ajax请求
      const url = `https://api.github.com/search/repositories?q=re&sort=stars`
      axios.get(url)
          .then(response => {
            const result = response.data
            // console.log(response)
            // 得到数据
            const {name, html_url} = result.items[0]
            // 更新状态
            this.setState({repoName:name, repoUrl: html_url})
          })
          .catch((error) => {
            alert(error.message)
          })
      // 使用fetch发送异步的ajax请求
      /*fetch(url)
          .then(response => {
            return response.json()
          })
          .then(data => {
            // 得到数据
            const {name, html_url} = data.items[0]
            // 更新状态
            this.setState({repoName:name, repoUrl: html_url})
          })
      */
    }

    render(){
      const {repoName, repoUrl} = this.state
      if(!repoName){
        return(
          <h2>LOADING...</h2>
        )
      }else{
        return(
          <h2>Most star repo is <a href={repoUrl}>{repoName}</a></h2>
        )
      }

    }
  }
  ReactDOM.render(<MostStarRepo />, document.getElementById('example'))
</script>
</body>
</html>

4.2 axios

4.2.1 文档

https://github.com/axios/axios

4.2.2 相关API

1) GET请求

axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

2) POST请求

axios.post('/user', {
    firstName: 'Fred',
axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
})
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});

4.3 Fetch

4.3.1 文档

1) https://github.github.io/fetch/

2) https://segmentfault.com/a/1190000003810652

4.3.2 相关API

1) GET请求

fetch(url).then(function(response) {
  return response.json()
}).then(function(data) {
  console.log(data)
}).catch(function(e) {
  console.log(e)
});

2) POST请求

fetch(url, {
  method: "POST",
  body: JSON.stringify(data),
}).then(function(data) {
  console.log(data)
}).catch(function(e) {
  console.log(e)
})

4.4 demo: github users

4.4.1 效果

demo_users

4.4.2 拆分组件

image-20200711091558440

App

​ * state: searchName/string

Search

​ * props: setSearchName/func

List

​ * props: searchName/string

​ * state: firstView/bool, loading/bool, users/array, errMsg/string

4.4.3 编写静态组件

4.4.4 编写动态组件

componentWillReceiveProps(nextProps): 监视接收到新的props, 发送ajax

​ 使用axios库发送ajax请求

  • public/index.html
<div id="root"></div>
  • src/index.js
import React from 'react'
import { render } from 'react-dom'

import App from './components/app'
import './index.css'

render(<App />, document.getElementById('root'))
  • src/index.css
.album {
  min-height: 50rem; /* Can be removed; just added for demo purposes */
  padding-top: 3rem;
  padding-bottom: 3rem;
  background-color: #f7f7f7;
}

.card {
  float: left;
  width: 33.333%;
  padding: .75rem;
  margin-bottom: 2rem;
  border: 1px solid #efefef;
  text-align: center;
}

.card > img {
  margin-bottom: .75rem;
  border-radius: 100px;
}

.card-text {
  font-size: 85%;
}
  • src/componets/app.jsx
import React from 'react'
import Search from './search'
import UserList from './user-list'

export default class App extends React.Component {

  state = {
    searchName: ''
  }

  refreshName = (searchName) => this.setState({searchName})

  render() {
    return (
      <div className="container">
        <section className="jumbotron">
          <h3 className="jumbotron-heading">Search Github Users</h3>
          <Search refreshName={this.refreshName}/>
        </section>
        <UserList searchName={this.state.searchName}/>
      </div>
    )
  }

}
  • src/componets/search.jsx
/**
 * 上部的搜索模块
 */
import React, {Component} from 'react'
import PropTypes from 'prop-types'

class Search extends Component {

  static propTypes = {
    refreshName: PropTypes.func.isRequired
  }

  search = () => {
    var name = this.nameInput.value
    this.props.refreshName(name)
  }

  render() {
    return (
      <div>
        <input type="text" placeholder="enter the name you search"
               ref={(input => this.nameInput = input)}/>
        <button onClick={this.search}>Search</button>
      </div>
    )
  }
}

export default Search
  • src/componets/user-list.jsx
/**
 * 下部的用户列表模块
 */
import React from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'
// npm install axios --save 

class UserList extends React.Component {

  static propTypes = {
    searchName: PropTypes.string.isRequired
  }

  state = {
    firstView: true,
    loading: false,
    users: null,
    error: null
  }

  async componentWillReceiveProps(nextProps)  {
    let searchName = nextProps.searchName
    console.log('发送ajax请求', searchName)
    const url = `https://api.github.com/search/users?q=${searchName}`
    this.setState({ firstView: false, loading: true })

    // 使用axios库
    axios.get(url)
      .then((response) => {
        console.log(response)
        this.setState({ loading: false, users: response.data.items })
      })
      .catch((error)=>{
        // debugger
        console.log('error', error.response.data.message, error.message)
        this.setState({ loading: false, error: error.message })
      })

    try {
      const result = await axios.get(url)
      this.setState({ loading: false, users: result.data.items })
    } catch(err) {
      // debugger
      console.log('----', err.message)
    }
  }

  render () {

    if (this.state.firstView) {
      return <h2>Enter name to search</h2>
    } else if (this.state.loading) {
      return <h2>Loading result...</h2>
    } else if (this.state.error) {
      return <h2>{this.state.error}</h2>
    } else {
      return (
        <div className="row">
          {
            this.state.users.map((user) => (
              <div className="card" key={user.html_url}>
                <a href={user.html_url} target="_blank">
                  <img src={user.avatar_url} style={{width: '100px'}} alt='user'/>
                </a>
                <p className="card-text">{user.login}</p>
              </div>
            ))
          }
        </div>
      )
    }
  }
}

export default UserList

第5章 几个重要技术总结

5.1 组件间通信

5.1.1 方式一: 通过props传递

1) 共同的数据放在父组件上, 特有的数据放在自己组件内部(state)

2) 通过props可以传递一般数据和函数数据, 只能一层一层传递

3) 一般数据–>父组件传递数据给子组件–>子组件读取数据

4) 函数数据–>子组件传递数据给父组件–>子组件调用函数

父组件传到孙组件、兄弟组件之间不能直接通信,经过子组件、服务器传递

5.1.2 方式二: 使用消息订阅(subscribe)-发布(publish)机制

1) 工具库: PubSubJS

2) 下载: npm install pubsub-js --save

3) 使用:

import PubSub from 'pubsub-js' //引入
PubSub.publish('delete', data) //发布消息
// 消息名,消息
PubSub.subscribe('delete', function(msg, data){ }); //订阅
// 消息名,回调函数

以上一个demo为例(用户搜索的),search和userlist(main)之间需要通信,它们是兄弟组件

在这里是通过父组件,利用props进行通信

props-search

这是以前的app.ejs

app.ejs

现在不通过父组件来通信

src/components/app.ejs

import React from 'react'
import Search from './search'
import UserList from './user-list'

export default class App extends React.Component {

  render() {
    return (
      <div className="container">
        <section className="jumbotron">
          <h3 className="jumbotron-heading">Search Github Users</h3>
          <Search/>
        </section>
        <UserList/>
      </div>
    )
  }

}

src/components/search.ejs

/**
 * 上部的搜索模块
 */
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import PubSub from 'pubsub-js' //引入
class Search extends Component {
  search = () => {
    var searchName = this.nameInput.value
    if(searchName){
      // 搜索
      // 发布消息 search
PubSub.publish('search', searchName)
    }
  }
  render() {
    return (
      <div>
        <input type="text" placeholder="enter the name you search"
               ref={(input => this.nameInput = input)}/>
        <input type="submit" value="Search" onClick={this.search} />
      </div>
    )
  }
}
export default Search

src/components/user-list.ejs

/**
 * 下部的用户列表模块
 */
import React from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'
import PubSub from 'pubsub-js' //引入
class UserList extends React.Component {
  static propTypes = {
    searchName: PropTypes.string.isRequired
  }
  state = {
    firstView: true,
    loading: false,
    users: null,
    error: null
  }
  componentDidMount(){
    // 订阅消息 search
   PubSub.subscribe('search', (msg, searchName) => { // 指定了新的name,需要请求    
      this.setState({ firstView: false, loading: true })
      // 使用axios库
      const url = `https://api.github.com/search/users?q=${searchName}`
      axios.get(url)
        .then((response) => {
    console.log(response)
          this.setState({ loading: false, users: response.data.items })
        })
        .catch((error)=>{
          // debugger       console.log('error', error.response.data.message, error.message)
          this.setState({ loading: false, error: error.message })
        })
      try {
        const result = axios.get(url)
        this.setState({ loading: false, users: result.data.items })
      } catch(err) {
        // debugger
        console.log('----', err.message)
      }
    })
  }
  render () {
    if (this.state.firstView) {
      return <h2>Enter name to search</h2>
    } else if (this.state.loading) {
      return <h2>Loading result...</h2>
    } else if (this.state.error) {
      return <h2>{this.state.error}</h2>
    } else {
      return (
        <div className="row">
          {         this.state.users.map((user) => (
              <div className="card" key={user.html_url}>
                <a href={user.html_url} target="_blank">
                  <img src={user.avatar_url} style={{width: '100px'}} alt='user'/>
                </a>
                <p className="card-text">{user.login}</p>
              </div>
            ))
          }
        </div>
      )
    }
  }
}
export default UserList

再以之前的评论的为例

App.ejs中有个删除评论的函数,传给了List组件(并没有用到),接着传给Item

delete={this.delete}

5.1.3 方式三: redux

后面专门讲解

5.2 事件监听理解

5.2.1 原生DOM事件

1) 绑定事件监听

a. 事件名(类型): 只有有限的几个, 不能随便写

b. 回调函数

2) 触发事件

a. 用户操作界面

b. 事件名(类型)

c. 数据

5.2.2 自定义事件(消息机制)

1) 绑定事件监听

a. 事件名(类型): 任意

b. 回调函数: 通过形参接收数据, 在函数体处理事件

2) 触发事件(编码)

a. 事件名(类型): 与绑定的事件监听的事件名一致

b. 数据: 会自动传递给回调函数

5.3 ES6常用新语法

1) 定义常量/变量: const/let

2) 解构赋值:let {a, b} = this.props import {aa} from 'xxx'

3) 对象的简洁表达: {a, b}

4) 箭头函数:

a. 常用场景

  • 组件的自定义方法: xxx = () => {}

  • 参数匿名函数

b. 优点:

​ * 简洁

​ * 没有自己的this,使用引用this查找的是外部this

5) 扩展(三点)运算符: 拆解对象(const MyProps = {}, <Xxx {...MyProps}>)

6) 类: class/extends/constructor/super

7) ES6模块化: export default | import

第6章 react-router4

6.1 相关理解

6.1.1 react-router的理解

1) react的一个插件库(依赖/基于React)

2) 专门用来实现一个SPA应用

3) 基于react的项目基本都会用到此库

6.1.2 SPA的理解

1) 单页Web应用(single page web application,SPA)

2) 整个应用只有一个完整的页面

3) 点击页面中的链接不会刷新页面, 本身也不会向服务器发请求

4) 当点击路由链接时, 只会做页面的局部更新

5) 数据都需要通过ajax请求获取, 并在前端异步展现

6.1.3 路由的理解

1) 什么是路由?

a. 一个路由就是一个映射关系(key:value)

b. key为路由路径(path), value可能是function(后台路由)/component(前台路由)

2) 路由分类

a. 后台路由: node服务器端路由, value是function, 用来处理客户端提交的请求并返回一个响应数据

b. 前台路由: 浏览器端路由, value是component, 当请求的是路由path时, 浏览器端前没有发送http请求, 但界面会更新显示对应的组件

3) 后台路由

a. 注册路由: router.get(path, function(req, res))

b. 当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

4) 前端路由

a. 注册路由: <Route path="/about" component={About}>

b. 当浏览器的hash变为#about时, 当前路由组件就会变为About组件

6.1.4 前端路由的实现

(底层实现)

1) history库

a. 网址: https://github.com/ReactTraining/history

b. 管理浏览器会话历史(history)的工具库

c. 包装的是原生BOM中window.historywindow.location.hash

2) history API

a. History.createBrowserHistory(): 得到封装window.history的管理对象

b. History.createHashHistory(): 得到封装window.location.hash的管理对象

c. history.push(): 添加一个新的历史记录

d. history.replace(): 用一个新的历史记录替换当前的记录

e. history.goBack(): 回退到上一个历史记录

f. history.goForword(): 前进到下一个历史记录

g. history.listen(function(location){}): 监视历史记录的变化

3) 测试

history-方式1

history-方式2

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>history test</title>
</head>
<body>

  <p><input type="text"></p>
  <a href="/test1" onclick="return push('/test1')">test1</a><br><br>
  <button onClick="push('/test2')">push test2</button><br><br>
  <button onClick="back()">回退</button><br><br>
  <button onClick="forword()">前进</button><br><br>
  <button onClick="replace('/test3')">replace test3</button><br><br>

  <script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
  <script type="text/javascript">
    let history = History.createBrowserHistory() // 方式一
    history = History.createHashHistory() // 方式二
    // console.log(history)

    function push (to) {
      history.push(to)
      return false
    } // 可以返回

    function back() {
      history.goBack()
    }

    function forword() {
      history.goForward()
    }

    function replace (to) {
      history.replace(to)
    } // 不能返回

    history.listen((location) => {
      console.log('请求路由路径变化了', location)
    })
  </script>
</body>
</html>

6.2 react-router相关API

react_router

react-router

web

6.2.1 组件

1) <BrowserRouter>:BrowserRouter是react路由的容器

2) <HashRouter>:这个是用来兼容老浏览器的

3) <Route>:Route的作用就是用来渲染路由匹配的组件。路由渲染有三种方式,每一种方式都可以传递match,location,history对象

4) <Redirect>:路由重定向

5) <Link>:Link的作用和a标签类似

6) <NavLink>:NavLink和Link一样最终都是渲染成a标签,NavLink可以给这个a标签添加额外的属性

7) <Switch>:Switch组件内部可以是Route或者Redirect,只会渲染第一个匹配的元素

6.2.2 其它

1) history对象:这里的history对象是使用history插件生成的,http://www.cnblogs.com/ye-hcj/p/7741742.html已经详细讲过了
记住一点,再使用location做对比的使用,通过history访问的location是动态变化的,最好通过Route访问location

2) match对象:match对象表示当前的路由地址是怎么跳转过来的

3) withRouter函数:当一个非路由组件也想访问到当前路由的match,location,history对象,那么withRouter将是一个非常好的选择

6.3 基本路由使用

6.3.1 效果

![react-router demo1 (3)](https://cdn.jsdelivr.net/gh/wallleap/cdn/img/pic/illustration/react-router demo1 (3).gif)

并没有刷新页面

6.3.2 准备

1) 下载react-router: npm install --save react-router@4

我们只需要web版本:npm install --save react-router-dom

2) 由于使用到了BootStrap,因此在index.html中引入bootstrap.css: <link rel="stylesheet" href="/css/bootstrap.css">

public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React-router</title>
    <link rel="stylesheet" href="/css/bootstrap.css">
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
  </body>
</html>

一般会将路由组件和非路由组件分开写

pages/views存放路由组件

components存放其他组件

6.3.3 路由组件: views/about.jsx

import React,{Component} from 'react'
export default class About extends Component{
  render(){
    return(
        <div>About组件内容</div>
      )
  }
}

6.3.4 路由组件: views/home.jsx

import React, {Component} from 'react'

export default class Home extends Component{
  render(){
    return (
        <div>Home组件内容</div>
      )
  }
}

由于每个NavLink都需要自定义active样式(加入属性activeClassName),因此提出来

import React, {Component} from 'react'
import {NavLink} from 'react-router-dom'

export default class MyNavLink extends Component {
  render(){
    // 利用this.props三点运算符接受所有的属性
    return(
      <NavLink {...this.props} activeClassName='activeClass'/>
    )
  }
}

6.3.6 应用组件: components/app.jsx

import React from 'react'
import {NavLink, Route, Switch, Redirect} from 'react-router-dom'
import MyNavLink from './my-nav-link'
import About from '../views/about'
import Home from '../views/home'

export default class App extends React.Component {

  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header">
              <h2>React Router Demo</h2>
            </div>
          </div>
        </div>

        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              {/*导航路由链接,不能使用a标签,to指向的path*/}
              <MyNavLink className="list-group-item" to='/about'>About</MyNavLink>
              <MyNavLink className="list-group-item" to='/home'>Home</MyNavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/*可切换的路由组件,使用switch只有匹配才显示,route的path对应上方的to、component对应路由组件,Redirect自动重定向到about、默认到about组件*/}
                <Switch>
                  <Route path='/about' component={About}/>
                  <Route path='/home' component={Home}/>
                  <Redirect to='/about'/>
                </Switch>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

6.3.7 自定义样式: index.css

activeClass{
  color: red !important;
}

6.3.8 入口JS: index.js

import React from 'react'
//import ReactDOM from 'react-dom'
import {render} from 'react-dom'
import {BrowserRouter, HashRouter} from 'react-router-dom'
import App from './components/app'

import './index.css'

// ReactDOM.render(
render(
  (
    <BrowserRouter>
      <App/>
    </BrowserRouter>
    /* 组件需要用路由器组件包含起来,两者任选一个 */
    /*<HashRouter>
      <App />
    </HashRouter>*/
  ),
  document.getElementById('root')
)

总结:如何编写路由效果?

  1. 编写路由组件
  2. 在父路由组件中指定
    • 路由连接:<NavLink></NavLink>
    • 路由:<Route></Route>

6.4 嵌套路由使用

嵌套路由——路由组件中的路由

6.4.1 效果

react-router demo2

6.4.2 二级路由组件: views/news.jsx

import React from 'react'

export default class News extends React.Component {
  state = {
    newsArr: ['news001', 'news002', 'news003']
  }

  render () {
    return (
      <div>
        <ul>
          {
            this.state.newsArr.map((news, index) => <li key={index}>{news}</li>)
          }
        </ul>
      </div>
    )
  }
}

6.4.3 二级路由组件: views/message.jsx

import React from 'react'
import {Link, Route} from 'react-router-dom'

export default class Message extends React.Component {
  state = {
    messages: []
  }

  componentDidMount () {
    // 模拟发送ajax请求
    setTimeout(() => {
      const data = [
        {id: 1, title: 'Message001'},
        {id: 3, title: 'Message003'},
        {id: 6, title: 'Message006'},
      ]
      this.setState({
        messages: data
      })
    }, 1000)
  }

  render () {
    const path = this.props.match.path

    return (
      <div>
        <ul>
          {
            this.state.messages.map((m, index) => {
              return (
                <li key={index}>
                   <a href='???'>{m.title}</a>
                </li>
              )
            })
          }
        </ul>
      </div>
    )
  }
}

6.4.4 一级路由组件: views/home.jsx

import React, {Component} from 'react'
import {Switch, Route, Redirect} from 'react-router-dom'
import MyNavLink from '../components/my-nav-link'
import News from './news'
import Message from './message'

export default class Home extends Component{
  render(){
    return (
      <div>
        <h2>Home组件内容</h2>
        <div>
          <ul className="nav nav-tabs">
            <li>
              <MyNavLink to='/home/news'>News</MyNavLink>
            </li>
            <li>
              <MyNavLink to="/home/message">Message</MyNavLink>
            </li>
          </ul>
          <Switch>
            <Route path='/home/news' component={News} />
            <Route path='/home/message' component={Message} />
            <Redirect to='/home/news'/>
          </Switch>
        </div>
      </div>
    )
  }
}

6.5 向路由组件传递参数数据

传递的是id值

6.5.1 效果

react-router demo3 (2)

6.5.2 三级路由组件: views/message-detail.jsx

import React from 'react'

const messageDetails = [
  {id: 1, title: 'Message001', content: '中国,你是最棒的'},
  {id: 3, title: 'Message003', content: '对的,没错,非常赞成楼上'},
  {id: 6, title: 'Message006', content: '我也赞成'},
]

// 函数的组件
export default function MessageDetail(props) {

  const id = props.match.params.id
  const md = messageDetails.find(md => md.id===id*1)

  return (
    <ul>
      <li>ID: {md.id}</li>
      <li>TITLE: {md.title}</li>
      <li>CONTENT: {md.content}</li>
    </ul>
  )
}

6.5.3 二级路由组件: views/message.jsx

import React from 'react'
import {Link, Route} from 'react-router-dom'
import MessageDetail from "./message-detail"

export default class Message extends React.Component {
  state = {
    messages: []
  }

  componentDidMount () {
    // 模拟发送ajax请求
    setTimeout(() => {
      const data = [
        {id: 1, title: 'Message001'},
        {id: 3, title: 'Message003'},
        {id: 6, title: 'Message006'},
      ]
      this.setState({
        messages: data
      })
    }, 1000)
  }

  render () {
    const path = this.props.match.path

    return (
      <div>
        <ul>
          {
            this.state.messages.map((m, index) => {
              return (
                <li key={index}>
                  <Link to={`${path}/${m.id}`}>{m.title}</Link>
                </li>
              )
            })
          }
        </ul>
        <Route path={`${path}/:id`} component={MessageDetail}></Route>
      </div>
    )
  }
}

路由链接与非路由链接:是否发了请求(路由连接不发)

<NavLink to=''></NavLink>`` <Link to=''></Link>

<a href=""></a>

6.6 多种路由跳转方式

前面讲的路由切换都是通过点击链接的方式切换的,不是链接也能够

6.6.1 效果

react-router demo4

6.6.2 二级路由: views/message.jsx

import React from 'react'
import {Link, Route} from 'react-router-dom'
import MessageDetail from "./message-detail"

export default class Message extends React.Component {
  state = {
    messages: []
  }

  componentDidMount () {
    // 模拟发送ajax请求
    setTimeout(() => {
      const data = [
        {id: 1, title: 'Message001'},
        {id: 3, title: 'Message003'},
        {id: 6, title: 'Message006'},
      ]
      this.setState({
        messages: data
      })
    }, 1000)
  }

  // props中有history属性,它由push等方法
  ShowDetail = (id) => {
    this.props.history.push(`/home/message/${id}`)
  }

  ShowDetail2 = (id) => {
    this.props.history.replace(`/home/message/${id}`)
  }

  back = () => {
    this.props.history.goBack()
  }

  forward = () => {
    this.props.history.goForward()
  }

  render () {
    const path = this.props.match.path

    return (
      <div>
        <ul>
          {
            this.state.messages.map((m, index) => {
              return (
                <li key={index}>
                  <Link to={`${path}/${m.id}`}>{m.title}</Link>
                  &nbsp;&nbsp;&nbsp;
                  <button onClick={() => this.ShowDetail(m.id)}>查看详情(push)</button>&nbsp;
                  <button onClick={() => this.ShowDetail2(m.id)}>查看详情(replace)</button>
                </li>
              )
            })
          }
        </ul>
        <p>
          <button onClick={this.back}>返回</button>&nbsp;
          <button onClick={this.forward}>前进</button>&nbsp;
        </p>
        <hr/>
        <Route path={`${path}/:id`} component={MessageDetail}></Route>
        {/*<Route path={`home/message/meassagedetail/:id`} component={MessageDetail}></Route>*/}
      </div>
    )
  }
}

总结:

  • 路由器标签
    • <BrowserRouter>:BrowserRouter是react路由的容器
    • <HashRouter>:多了一个#号
  • 路由
    • <Route>:Route的作用就是用来渲染路由匹配的组件。路由渲染有三种方式,每一种方式都可以传递match,location,history对象
  • <Redirect>:路由重定向
  • 链接
    • <Link>:Link的作用和a标签类似
    • <NavLink>:可以添加其他属性,例如activeClassName
    • <Switch>:Switch组件内部可以是Route或者Redirect,只会渲染第一个匹配的元素
  • this.props.
    • match
      • params
    • history
      • push()
      • replace()
      • goback()
      • goforward()

第7章 react-ui

7.1 最流行的开源React UI组件库

7.1.1 material-ui(国外)

1) 官网: http://www.material-ui.com/#/

2) github: https://github.com/callemall/material-ui

7.1.2 ant-design(国内蚂蚁金服)

1) PC官网: https://ant.design/index-cn

2) 移动官网: https://mobile.ant.design/index-cn

3) Github: https://github.com/ant-design/ant-design/

4) Github: https://github.com/ant-design/ant-design-mobile/

7.2 ant-design-mobile使用入门

7.2.1 效果

antd-mobile

7.2.2 使用create-react-app创建react应用

npm install create-react-app -g
create-react-app antm-demo
cd antm-demo
npm start

7.2.3 搭建antd-mobile的基本开发环境

1) 下载

npm install antd-mobile --save

2) src/components/App.jsx

import React, {Component} from 'react'
import {Button, Toast} from 'antd-mobile'
export default class App extends Component {
  handleClick = () => {
    Toast.info('提交成功', 2)
  }

  render() {
    return (
        <div>
          <Button type="primary" onClick={this.handleClick}>提交</Button>
        </div>   
      )
  }
  /* type值不同样式不同 */
}

3) src/index.js

import React from 'react';
import {render} from 'react-dom'
import App from "./components/App"
// 引入整体css
import 'antd-mobile/dist/antd-mobile.css'

render(<App />, document.getElementById('root'))

4) index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React ant design mobile</title>
    <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
    <script>
      if ('addEventListener' in document) {
        document.addEventListener('DOMContentLoaded', function() {
          FastClick.attach(document.body);
        }, false);
      }
      if(!window.Promise) {
        document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
      }
    </script>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
  </body>
</html>

7.2.4 实现按需打包(组件js/css)

1) 下载依赖包

yarn add react-app-rewired --dev
yarn add babel-plugin-import --dev

2) 修改默认配置:

l package.json

{
  "name": "react_ui",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "antd-mobile": "^2.1.3",
    "react": "^16.2.0",
    "react-dom": "^16.2.0"
  },
  "devDependencies": {
    "babel-plugin-import": "^1.6.3",
    "react-app-rewired": "^1.4.0",
    "react-scripts": "1.0.17"
  },
  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test --env=jsdom"
  }
}

l config-overrides.js

const {injectBabelPlugin} = require('react-app-rewired');
module.exports = function override(config, env) {
  config = injectBabelPlugin(['import', {libraryName: 'antd-mobile', style: 'css'}], config);
  return config;
};

3) 编码

// import 'antd-mobile/dist/antd-mobile.css'
// import Button from 'antd-mobile/lib/button'
// import Toast from 'antd-mobile/lib/toast' 
import {Button, Toast} from 'antd-mobile'

第8章 redux

8.1 redux理解

8.1.1 学习文档

1) 英文文档: https://redux.js.org/

2) 中文文档: http://www.redux.org.cn/

3) Github: https://github.com/reactjs/redux

8.1.2 redux是什么?

1) redux是一个独立专门用于做状态管理JS库(不是react插件库)

2) 它可以用在react, angular, vue等项目中, 但基本与react配合使用

3) 作用: 集中式管理react应用中多个组件共享的状态

8.1.3 redux工作流程

redux

8.1.4 什么情况下需要使用redux

1) 总体原则: 能不用就不用, 如果不用比较吃力才考虑使用

2) 某个组件的状态,需要共享

3) 某个状态需要在任何地方都可以拿到

4) 一个组件需要改变全局状态

5) 一个组件需要改变另一个组件的状态

8.2 redux的核心API

8.2.1 createStore()

1) 作用: 创建包含指定reducer的store对象

2) 编码:

import {createStore} from 'redux'
import counter from './reducers/counter'
const store = createStore(counter)

8.2.2 store对象

1) 作用:

redux库最核心的管理对象

2) 它内部维护着:

  • state

  • reducer

3) 核心方法:

  • getState()

  • dispatch(action)

  • subscribe(listener)

4) 编码:

store.getState()
store.dispatch({type:'INCREMENT', number})
store.subscribe(render)

8.2.3 applyMiddleware()

1) 作用:

应用上基于redux的中间件(插件库)

2) 编码:

import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk' // redux异步中间件
const store = createStore(
 counter,
 applyMiddleware(thunk) // 应用上异步中间件
)

8.2.4 combineReducers()

1) 作用:

合并多个reducer函数

2) 编码:

export default combineReducers({
 user,
 chatUser,
 chat
})

8.3 redux的三个核心概念

8.3.1 action

1) 标识要执行行为的对象

2) 包含2个方面的属性

a. type: 标识属性, 值为字符串, 唯一, 必要属性

b. xxx: 数据属性, 值类型任意, 可选属性

3) 例子:

const action = {
    type: 'INCREMENT',
    data: 2
}

4) Action Creator(创建Action的工厂函数)

const increment = (number) => ({type: 'INCREMENT', data: number})

8.3.2 reducer

1) 根据老的state和action, 产生新的state的纯函数

2) 样例

export default function counter(state = 0, action) {
  switch (action.type) {
     case 'INCREMENT':
        return state + action.data
     case 'DECREMENT':
        return state - action.data
     default:
        return state
  }
}

3) 注意

a. 返回一个新的状态

b. 不要修改原来的状态

8.3.3 store

1) 将state,action与reducer联系在一起的对象

2) 如何得到此对象?

import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)

3) 此对象的功能?

  • getState(): 得到state

  • dispatch(action): 分发action, 触发reducer调用, 产生新的state

  • subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

8.4 使用redux编写应用

8.4.1 效果

redux

使用react实现

app.jsx

index.js

8.4.2 下载依赖包

npm install --save redux

8.4.3 src/redux/action-types.js

/*
Action对象的type常量名称模块
 */
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

8.4.4 src/redux/actions.js

/*
action creator模块
 */
import {INCREMENT, DECREMENT} from './action-types'

export const increment = number => ({type: INCREMENT, number})
export const decrement = number => ({type: DECREMENT, number})

8.4.5 src/redux/reducers.js

/*
包含n个reducer函数的模块
根据老的state和指定action, 处理返回一个新的state
 */
import {INCREMENT, DECREMENT} from './action-types'

export function counter(state = 0, action) {
  console.log('counter', state, action)
  switch (action.type) {
    case INCREMENT:
      return state + action.number
    case DECREMENT:
      return state - action.number
    default:
      return state
  }
}

8.4.6 src/components/app.jsx

/*
应用组件
 */
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import * as actions from '../redux/actions'
import { INCREMENT } from '../redux/action-types'

export default class App extends Component {

  static propTypes = {
    store: PropTypes.object.isRequired,
  }

  increment = () => {
    // 1.得到选择增加数量
    const number = this.refs.numSelect.value * 1
    // 2.调用store的方法更新状态
    this.props.store.dispatch(actions.increment(number))
  }

  decrement = () => {
    // 1.得到选择减小数量
    const number = this.refs.numSelect.value * 1
    // 2.调用store的方法更新状态
    this.props.store.dispatch(actions.decrement(number))
  }

  incrementIfOdd = () => {
    // 1.得到选择增加数量
    const number = this.refs.numSelect.value * 1
    // 2.得到原本的count状态
    let count = this.props.store.getState()
    // 判断,满足条件猜更新状态
    if (count % 2 === 1) {
      // 调用store的方法更新状态
      this.props.store.dispatch(actions.increment(number))
    }
  }

  incrementAsync = () => {
    // 1.得到选择增加数量
    const number = this.refs.numSelect.value * 1
    // 2.启动延时定时器,模拟异步
    setTimeout(() => {
      // 3.调用store的方法更新状态
      this.props.store.dispatch(actions.increment(number))
    }, 1000)
  }

  render() {
    const count = this.props.store.getState()
    return (
      <div>
        <p>
          click {count} times {' '}
        </p>
        <select ref="numSelect">
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>{' '}
        <button onClick={this.increment}>+</button>
        {' '}
        <button onClick={this.decrement}>-</button>
        {' '}
        <button onClick={this.incrementIfOdd}>increment if odd</button>
        {' '}
        <button onClick={this.incrementAsync}>increment async</button>
      </div>
    )
  }
}

8.4.7 src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'

import App from './components/app'
import {counter} from './redux/reducers'

// 根据counter函数创建store对象
const store = createStore(counter) // 内部会第一次调用reduer函数,得到初始state

// 定义渲染根组件标签的函数
const render = () => {
  ReactDOM.render(
    <App store={store}/>,
    document.getElementById('root')
  )
}
// 初始化渲染
render()

// 注册(订阅)监听, 一旦状态发生改变, 自动重新渲染
store.subscribe(render)

也可以把index.js中store提取出来

  • src/redux/store.js
import {createStore} from 'redux'
import {counter} from './reducers'
// 根据counter函数创建store对象
const store = createStore(counter)  // // 内部会第一次调用reduer函数,得到初始state

export default store
  • src/index.js
import React from 'react'
import ReactDOM from 'react-dom'

import App from './components/app'
import store from './redux/store'

// 定义渲染根组件标签的函数
const render = () => {
  ReactDOM.render(
    <App store={store}/>,
    document.getElementById('root')
  )
}
// 初始化渲染
render()

// 注册(订阅)监听, 一旦状态发生改变, 自动重新渲染
store.subscribe(render)

8.4.8 问题

1) redux与react组件的代码耦合度太高(大多数地方用到store)

2) 编码不够简洁

8.5 react-redux

8.5.1 理解

1) 一个react插件库

2) 专门用来简化react应用中使用redux

8.5.2 React-Redux将所有组件分成两大类

1) UI组件

​ a. 只负责 UI 的呈现,不带有任何业务逻辑

​ b. 通过props接收数据(一般数据和函数)

​ c. 不使用任何 Redux 的 API

​ d. 一般保存在components文件夹下

2) 容器组件

​ a. 负责管理数据和业务逻辑,不负责UI的呈现

​ b. 使用 Redux 的 API

​ c. 一般保存在containers文件夹下

8.5.3 相关API

1) Provider

让所有组件都可以得到state数据

<Provider store={store}>
   <App />
</Provider>

2) connect()

用于包装 UI 组件生成容器组件

import { connect } from 'react-redux'
connect(
   mapStateToprops,
   mapDispatchToProps
)(Counter)

3) mapStateToprops()

将外部的数据(即state对象)转换为UI组件的标签属性

const mapStateToprops = function (state) {
  return {
   value: state
  }
}

4) mapDispatchToProps()

将分发action的函数转换为UI组件的标签属性

简洁语法可以直接指定为actions对象或包含多个action方法的对象

8.5.4 使用react-redux

1) 下载依赖包

npm install --save react-redux

2) redux/action-types.js

不变

3) redux/actions.js

不变

4) redux/reducers.js

不变

5) components/counter.jsx

/*
UI组件: 不包含任何redux API
 */
import React from 'react'
import PropTypes from 'prop-types'

export default class Counter extends React.Component {

  static propTypes = {
    count: PropTypes.number.isRequired,
    increment: PropTypes.func.isRequired,
    decrement: PropTypes.func.isRequired
  }

  increment = () => {
    const number = this.refs.numSelect.value * 1
    this.props.increment(number)
  }

  decrement = () => {
    const number = this.refs.numSelect.value * 1
    this.props.decrement(number)
  }

  incrementIfOdd = () => {
    const number = this.refs.numSelect.value * 1
    let count = this.props.count
    if (count % 2 === 1) {
      this.props.increment(number)
    }
  }

  incrementAsync = () => {
    const number = this.refs.numSelect.value * 1
    setTimeout(() => {
      this.props.increment(number)
    }, 1000)
  }

  render() {
    return (
      <div>
        <p>
          click {this.props.count} times {' '}
        </p>
        <select ref="numSelect">
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>{' '}
        <button onClick={this.increment}>+</button>
        {' '}
        <button onClick={this.decrement}>-</button>
        {' '}
        <button onClick={this.incrementIfOdd}>increment if odd</button>
        {' '}
        <button onClick={this.incrementAsync}>increment async</button>
      </div>
    )
  }
}

6) containters/app.jsx

/*
包含Counter组件的容器组件
 */
import React from 'react'
// 引入连接函数
import {connect} from 'react-redux'
// 引入action函数
import {increment, decrement} from '../redux/actions'

import Counter from '../components/counter'

// 向外暴露连接App组件的包装组件
export default connect(
  state => ({count: state}),
  {increment, decrement}
)(Counter)

7) store.js

不变

8) index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'

import App from './containers/app'
import store from './redux/store'

// 定义渲染根组件标签的函数
ReactDOM.render(
  (
    <Provider store={store}>
      <App/>
    </Provider>
  ),
  document.getElementById('root')
)

8.5.5 问题

1) redux默认是不能进行异步处理的, (前面的都是react实现的)

2) 应用中又需要在redux中执行异步任务(ajax, 定时器)

8.6 redux异步编程

8.6.1 下载redux插件(异步中间件)

npm install --save redux-thunk

8.6.2 redux/store.js

import React from 'react'
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'

import reducers from './reducers'

// 根据counter函数创建store对象
export default createStore(
  reducers,
  applyMiddleware(thunk) // 应用上异步中间件
)

8.6.3 index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'

import App from './containers/app'
import store from './redux/store'

// 定义渲染根组件标签的函数
ReactDOM.render(
  (
    <Provider store={store}>
      <App/>
    </Provider>
  ),
  document.getElementById('root')
)

8.6.4 redux/actions.js

/*
action creator模块
 */
import {
  INCREMENT,
  DECREMENT
} from './action-types'

export const increment = number => ({type: INCREMENT, number})

export const decrement = number => ({type: DECREMENT, number})

// 异步action creator(返回一个函数)
export const incrementAsync = number => {
  return dispatch => { // 要在这返回一个函数,得在store中应用上异步中间件
    // 异步的代码
    setTimeout(() => {
      // 1s之后才分发一个增加的action
      dispatch(increment(number))
    }, 1000)
  }
}

同步的action都返回一个对象

异步的action返回的是一个函数

8.6.5 components/counter.jsx

/*
包含Counter组件的容器组件
 */
import React from 'react'
import PropTypes from 'prop-types'

export default class Counter extends React.Component {

  static propTypes = {
    count: PropTypes.number.isRequired,
    increment: PropTypes.func.isRequired,
    decrement: PropTypes.func.isRequired
  }

  increment = () => {
    const number = this.refs.numSelect.value*1
    this.props.increment(number)
  }

  decrement = () => {
    const number = this.refs.numSelect.value*1
    this.props.decrement(number)
  }

  incrementIfOdd = () => {
    const number = this.refs.numSelect.value*1
    let count = this.props.count
    if(count%2===1) {
      this.props.increment(number)
    }
  }

  // Async
  incrementAsync = () => {
    const number = this.refs.numSelect.value*1
    this.props.incrementAsync(number)
  }

  render () {
    return (
      <div>
        <p>
          click {this.props.count} times {' '}
        </p>
        <select ref="numSelect">
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>{' '}
        <button onClick={this.increment}>+</button>{' '}
        <button onClick={this.decrement}>-</button>{' '}
        <button onClick={this.incrementIfOdd}>increment if odd</button>{' '}
        <button onClick={this.incrementAsync}>increment async</button>
      </div>
    )
  }
}

8.6.6 containers/app.jsx

/*
包含Counter组件的容器组件
 */
import React from 'react'
// 引入连接函数
import {connect} from 'react-redux'
// 引入action函数
import {increment, decrement, incrementAsync} from '../redux/actions'
import Counter from '../components/counter'

// 向外暴露连接App组件的包装组件
export default connect(
  state => ({count: state.counter}),
  {increment, decrement, incrementAsync}
)(Counter)

redux/action-types.js

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

8.7 使用上redux调试工具

8.7.1 安装chrome浏览器插件

Redux DevTools

8.7.2 下载工具依赖包

npm install --save-dev redux-devtools-extension

8.7.3 编码

import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(  counter,  composeWithDevTools(applyMiddleware(thunk))
)

8.8 Redux版评论

8.8.1 需求

使用react_redux和中间件实现异步评论功能

8.8.2 安装

……
npm install --save react-redux
npm install --save redux-thunk
npm install --save-dev redux-devtools-extension

8.8.3 目录结构

  • REACT_REDUX
    • public
      • css
        • bootstrap.css
      • index.html
    • src
      • components
        • app
          • app.jsx
        • comment-add
          • comment-add.jsx
        • comment-item
          • comment-item.jsx
          • comment-item.css
        • comment-list
          • comment-list.jsx
          • comment-list.css
      • redux
        • action-types.js
        • actions.js
        • reducers.js
        • store.js
      • index.js

8.8.4 文件内容

  • public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React App</title>
    <link rel="stylesheet" href="/css/bootstrap.css">
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
  </body>
</html>
  • src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'

import App from './components/app/app'
import store from './redux/store'

// 定义渲染根组件标签的函数
ReactDOM.render(
  (
    <Provider store={store}>
      <App/>
    </Provider>
  ),
  document.getElementById('root')
)
  • src/componets/app/app.jsx
import React from 'react'
import {connect} from 'react-redux'
import CommentAdd from '../comment-add/comment-add'
import CommentList from '../comment-list/comment-list'
import {getComments} from '../../redux/actions'

class App extends React.Component {

  componentDidMount() {
    //模拟异步获取数据
    this.props.getComments()
  }

  render() {
    return (
      <div>
        <header className="site-header jumbotron">
          <div className="container">
            <div className="row">
              <div className="col-xs-12">
                <h1>请发表对React的评论</h1>
              </div>
            </div>
          </div>
        </header>
        <div className="container">
          <CommentAdd/>
          <CommentList/>
        </div>
      </div>
    )
  }
}

export default connect(
  null,
  {getComments}
)(App)
  • src/componets/comment-add/comment-add.jsx
    import React from 'react'
    import PropTypes from 'prop-types'
    import {connect} from 'react-redux'
    import {addComment} from '../../redux/actions'
    class CommentAdd extends React.Component {
    constructor (props) {
      super(props)
      this.state = {
        username: '',
        content: ''
      }
      this.addComment = this.addComment.bind(this)
      this.changeUsername = this.changeUsername.bind(this)
      this.changeContent = this.changeContent.bind(this)
    }
    addComment () {
      // 根据输入的数据创建评论对象
      let { username, content } = this.state
      let comment = { username, content }
      // 添加到comments中, 更新state
      this.props.addComment(comment)
      // 清除输入的数据
      this.setState({
        username: '',
        content: ''
      })
    }
    changeUsername (event) {
      this.setState({
        username: event.target.value
      })
    }
    changeContent (event) {
      this.setState({
        content: event.target.value
      })
    }
    render () {
      return (
        <div className="col-md-4">
          <form className="form-horizontal">
            <div className="form-group">
              <label>用户名</label>
              <input type="text" className="form-control" placeholder="用户名"
                     value={this.state.username} onChange={this.changeUsername}/>
            </div>
            <div className="form-group">
              <label>评论内容</label>
              <textarea className="form-control" rows="6" placeholder="评论内容"
                        value={this.state.content} onChange={this.changeContent}></textarea>
            </div>
            <div className="form-group">
              <div className="col-sm-offset-2 col-sm-10">
                <button type="button" className="btn btn-default pull-right" onClick={this.addComment}>提交</button>
              </div>
            </div>
          </form>
        </div>
      )
    }
    }
    CommentAdd.propTypes = {
    addComment: PropTypes.func.isRequired
    }
    export default connect(
    null,
    {addComment}
    )(CommentAdd)
  • src/componets/comment-item/comment-item.jsx
    import React from 'react'
    import PropTypes from 'prop-types'
    import {connect} from 'react-redux'
    import './commentItem.css'
    import {deleteComment} from '../../redux/actions'
    class CommentItem extends React.Component {
    constructor(props) {
      super(props)
    }
    deleteComment = () => {
      let username = this.props.comment.username
      if (window.confirm(`确定删除${username}的评论吗?`)) {
        this.props.deleteComment(this.props.index)
      }
    }
    render() {
      let comment = this.props.comment
      return (
        <li className="list-group-item">
          <div className="handle">
            <a href="javascript:" onClick={this.deleteComment}>删除</a>
          </div>
          <p className="user"><span>{comment.username}</span><span>说:</span></p>
          <p className="centence">{comment.content}</p>
        </li>
      )
    }
    }
    CommentItem.propTypes = {
    comment: PropTypes.object.isRequired,
    index: PropTypes.number.isRequired,
    deleteComment: PropTypes.func.isRequired
    }
    export default connect(
    null,
    {deleteComment}
    )(CommentItem)
    







- `src/componets/comment-item/comment-item.css`
```css
li {
  transition: .5s;
  overflow: hidden;
}
.handle {
  width: 40px;
  border: 1px solid #ccc;
  background: #fff;
  position: absolute;
  right: 10px;
  top: 1px;
  text-align: center;
}
.handle a {
  display: block;
  text-decoration: none;
}
.list-group-item .centence {
  padding: 0px 50px;
}
.user {
  font-size: 22px;
}
  • src/componets/comment-list/comment-list.jsx
    import React from 'react'
    import PropTypes from 'prop-types'
    import {connect} from 'react-redux'
    import CommentItem from '../comment-item/comment-item'
    import './commentList.css'
    class CommentList extends React.Component {
    render () {
      let comments = this.props.comments
      let display = comments.length > 0 ? 'none' : 'block'
      return (
        <div className="col-md-8">
          <h3 className="reply">评论回复:</h3>
          <h2 style=双大括号 display: display 双大括号>暂无评论,点击左侧添加评论!!!</h2>
          <ul className="list-group">
            {
              comments.map((comment, index) => {
                console.log(comment)
                return <CommentItem comment={comment} key={index} index={index}/>
              })
            }
          </ul>
        </div>
      )
    }
    }
    CommentList.propTypes = {
    comments: PropTypes.array.isRequired,
    }
    export default connect(
    state => ({comments: state.comments})
    )(CommentList)
  • src/componets/comment-list/comment-list.css
    .reply {
    margin-top: 0px;
    }
  • redux/action-types.js
    export const ADD_COMMENT = 'ADD_COMMENT'
    export const DELETE_COMMENT = 'DELETE_COMMENT'
    export const RECEIVE_COMMENTS = 'RECEIVE_COMMENTS'
  • redux/actions.js
    import {
    ADD_COMMENT,
    DELETE_COMMENT,
    RECEIVE_COMMENTS
    } from './action-types'
    export const addComment = (comment) => ({type: ADD_COMMENT, data: comment})
    export const deleteComment = (index) => ({type: DELETE_COMMENT, data: index})
    const receiveComments = (comments) => ({type: RECEIVE_COMMENTS, data: comments})
    export const getComments = () => {
    return dispatch => {
      setTimeout(() => {
        const comments = [
          {
            username: "Tom",
            content: "ReactJS好难啊!",
            id: Date.now()
          },
          {
            username: "JACK",
            content: "ReactJS还不错!",
            id: Date.now() + 1
          }
        ]
        dispatch(receiveComments(comments))
      }, 1000)
    }
    }
  • redux/reducers.js
import {combineReducers} from 'redux'
import {
  ADD_COMMENT,
  DELETE_COMMENT,
  RECEIVE_COMMENTS
} from './action-types'
const initComments = []
function comments(state = initComments, action) {
  switch (action.type) {
    case ADD_COMMENT:
      return [...state, action.data]
    case DELETE_COMMENT:
      return state.filter((c, index) => index !== action.data)
    case RECEIVE_COMMENTS:
      return action.data
    default:
      return state
  }
}
export default combineReducers({
  comments
})
  • redux/store.js
import React from 'react'
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import reducers from './reducers'
// 根据counter函数创建store对象
export default createStore(
  reducers,
  composeWithDevTools(applyMiddleware(thunk)) // 应用上异步中间件
)

8.9 相关重要知识: 纯函数和高阶函数

8.9.1 纯函数

1) 一类特别的函数: 只要是同样的输入,必定得到同样的输出

2) 必须遵守以下一些约束

a. 不得改写参数

b. 不能调用系统 I/O 的API

c. 能调用Date.now()或者Math.random()等不纯的方法

3) reducer函数必须是一个纯函数

8.9.2 高阶函数

4) 理解: 一类特别的函数

a. 情况1: 参数是函数

b. 情况2: 返回是函数

5) 常见的高阶函数:

a. 定时器设置函数

b. 数组的map()/filter()/reduce()/find()/bind()

c. react-redux中的connect函数

6) 作用:

​ 能实现更加动态, 更加可扩展的功能

Git学习笔记

Git学习笔记

版本控制

集中式(svn)

svn因为每次存的都是差异 需要的硬盘空间会相对的小一点 可是回滚的速度会很慢

  • 优点:
    代码存放在单一的服务器上 便于项目的管理
  • 缺点:
    服务器宕机: 员工写的代码得不到保障
    服务器炸了: 整个项目的历史记录都会丢失

分布式(git)

git每次存的都是项目的完整快照 需要的硬盘空间会相对大一点
(Git团队对代码做了极致的压缩 最终需要的实际空间比svn多不了太多 可是Git的回滚速度极快)

  • 优点:
    完全的分布式
  • 缺点:
    学习起来比SVN陡峭

Git底层命令

git

底层命令

git对象
    git hash-object -w fileUrl : 生成一个key(hash值):val(压缩后的文件内容)键值对存到.git/objects
tree对象
    git update-index --add --cacheinfo 100644 hash test.txt : 往暂存区添加一条记录(让git对象 对应 上文件名)存到.git/index
    git write-tree : 生成树对象存到.git/objects
commit对象
    echo 'first commit' | git commit-tree treehash : 生成一个提交对象存到.git/objects
对以上对象的查询
    git cat-file -p hash       : 拿对应对象的内容
    git cat-file -t hash       : 拿对应对象的类型

查看暂存区

git ls-files -s        

Git高层命令

安装

git --version

初始化配置

git config --global user.name "damu"
git config --global user.email [email protected]    
git config --list

初始化仓库

git init

C(新增)

在工作目录中新增文件
git status
git add ./
git commit -m "msg"    

U(修改)

在工作目录中修改文件
git status
git add ./
git commit -m "msg"     

D(删除 & 重命名)

git rm 要删除的文件 git mv 老文件 新文件
git status git status
git commit -m “msg” git commit -m “msg”

R(查询)

git status : 查看工作目录中文件的状态(已跟踪(已提交 已暂存 已修改) 未跟踪)
git diff : 查看未暂存的修改
git diff –cache : 查看未提交的暂存
git log –oneline : 查看提交记录

分支

分支的本质其实就是一个提交对象!!!
HEAD: 
    是一个指针 它默认指向master分支 切换分支时其实就是让HEAD指向不同的分支
    每次有新的提交时 HEAD都会带着当前指向的分支 一起往前移动
git  log --oneline --decorate --graph --all : 查看整个项目的分支图  
git branch : 查看分支列表
git branch -v: 查看分支指向的最新的提交
git branch name : 在当前提交对象上创建新的分支
git branch name commithash: 在指定的提交对象上创建新的分支
git checkout name :     切换分支
git branch -d name : 删除空的分支 删除已经被合并的分支
git branch -D name : 强制删除分支 

Git分支

git分支本质

分支本质是一个提交对象,所有的分支都会有机会被HEAD所引用(HEAD一个时刻只会指向一个分支)
当我们有新的提交的时候 HEAD会携带当前持有的分支往前移动

git分支命令

创建分支            : git branch branchname
切换分支           : git checkout  branchname
创建&切换分支     : git checkout -b branchname
版本穿梭(时光机) :  git branch branchname commitHash  
普通删除分支      : git  branch -d branchname
强制删除分支      : git  branch -D branchname
合并分支         : git merge branchname
    快进合并 --> 不会产生冲突
    典型合并 --> 有机会产生冲突
    解决冲突 --> 打开冲突的文件 进行修改 add commit 

查看分支列表 : git branch
查看合并到当前分支的分支列表: git branch --merged
    一旦出现在这个列表中 就应该删除
查看没有合并到当前分支的分支列表: git branch --no-merged
    一旦出现在这个列表中 就应该观察一下是否需要合并

git分支的注意点

在切换的时候 一定要保证当前分支是干净的!!!
    允许切换分支: 
        分支上所有的内容处于 已提交状态    
        (避免)分支上的内容是初始化创建 处于未跟踪状态
        (避免)分支上的内容是初始化创建 第一次处于已暂存状态
    不允许切分支:
         分支上所有的内容处于 已修改状态  或 第二次以后的已暂存状态  

在分支上的工作做到一半时 如果有切换分支的需求, 我们应该将现有的工作存储起来
    git stash : 会将当前分支上的工作推到一个栈中
    分支切换  进行其他工作 完成其他工作后 切回原分支
    git stash apply : 将栈顶的工作内容还原 但不让任何内容出栈 
    git stash drop  : 取出栈顶的工作内容后 就应该将其删除(出栈)
    git stash pop   :      git stash apply +  git stash drop 
    git stash list : 查看存储

后悔药

撤销工作目录的修改   :  git checkout -- filename
撤销暂存区的修改     :  git reset HEAD  filename
撤销提交             :  git commit --amend

reset三部曲

git reset --soft commithash    ---> 用commithash的内容重置HEAD内容
git reset [--mixed] commithash ---> 用commithash的内容重置HEAD内容 重置暂存区
git reset --hard commithash    ---> 用commithash的内容重置HEAD内容 重置暂存区 重置工作目录

路径reset

所有的路径reset都要省略第一步!!!
    第一步是重置HEAD内容  我们知道HEAD本质指向一个分支 分支的本质是一个提交对象 
    提交对象 指向一个树对象 树对象又很有可能指向多个git对象 一个git对象代表一个文件!!!
    HEAD可以代表一系列文件的状态!!!!
git reset [--mixed] commithash filename  
     用commithash中filename的内容重置暂存区

checkout深入理解

git   checkout brancname  跟   git reset --hard commithash特别像
    共同点
        都需要重置 HEAD   暂存区   工作目录
    区别
         checkout对工作目录是安全的    reset --hard是强制覆盖
         checkout动HEAD时不会带着分支走而是切换分支
         reset --hard时是带着分支走

checkout + 路径
      git checkout commithash  filename   
           重置暂存区
           重置工作目录
      git checkout -- filename  
          重置工作目录  

eslint

eslint

js代码的检查工具
下载: npm i eslint -D
使用:
    生成配置文件 npx eslint --init
    检查js文件   npx eslint 目录名
    命中的规则:
        字符串必须使用单引号
        语句结尾不能有分号
        文件的最后必须要有换行

eslint结合git

husky: 哈士奇, 为Git仓库设置钩子程序
使用
    在仓库初始化完毕之后 再去安装哈士奇
    在package.json文件写配置
        "husky": {
            "hooks": {
              "pre-commit": "npm run lint"   
              //在git commit之前一定要通过npm run lint的检查
              // 只有npm run lint不报错时 commit才能真正的运行
            }
          }           

远程协作

三个必须懂得概念

本地分支
远程跟踪分支(remote/分支名)
远程分支

远程协作的基本流程

第一步: 项目经理创建一个空的远程仓库
第二步: 项目经理创建一个待推送的本地仓库
第三步: 为远程仓库配别名  配完用户名 邮箱
第四步: 在本地仓库中初始化代码 提交代码
第五步: 推送
第六步: 邀请成员
第七步: 成员克隆远程仓库
第八步: 成员做出修改
第九步: 成员推送自己的修改
第十步: 项目经理拉取成员的修改

做跟踪

克隆才仓库时 会自动为master做跟踪
本地没有分支
    git checkout --track 远程跟踪分支(remote/分支名)
本地已经创建了分支
    git branch -u 远程跟踪分支(remote/分支名)

推送

git push

拉取

git pull

pull request

让第三方人员参与到项目中 fork

使用频率最高的五个命令

git status
git add
git commit
git push
git pull

NodeJs学习笔记

NodeJs学习笔记

一、基本知识

1、命令行窗口

  • 又称cmd窗口、终端、shell

  • 打开方式:

    • win:win+R,输入cmd,打开

    • mac\linux: terminal

  • 前面显示的为目前所在的目录

  • 常用的命令

    • dir 列出当前目录下所有文件
    • cd 目录名 进入到指定的目录
    • 目录
      • . 表示当前目录
      • .. 表示上一级目录
    • md 目录名 创建一个文件夹
    • rd 目录名 删除一个文件夹
    • 输入当前目录下的一个文件名(包括后缀)可以打开这个文件
  • 环境变量(Windows系统中的变量)

    • path
    • 当我们在命令行窗口打开一个文件,或调用一个程序时,系统会首先在当前目录下寻找文件程序,如果找到了则直接打开,如果没有找到则会依次到环境变量path的路径中寻找,知道找到为止,如果没找到则报错
    • 所以我们可以将一些经常需要访问的程序和文件的路径添加到paht中,这样我们就可以在任意位置来访问这些文件和程序了

2、进程和线程

进程

  • 进程负责为程序的运行提供必备的环境
  • 进程就相当于工厂中的车间

线程

  • 线程是计算机中的最小的计算单位,线程负责执行进程中的程序
  • 线程就相当于工厂中的工人
    • 单线程
      • js是单线程
    • 多线程

二、Node.js简介

  • nodejs是一个能够在服务器端运行js的开放源代码、跨平台的js运行环境
  • node采用google开发的v8引擎运行js代码,使用

1588819260571

三、node使用

node
> var i = 0
> console.log(i)
node hello.js

四、COMMONJS规范

1、ECMAScript标准的缺陷

  • 没有模块系统
  • 标准库较少
  • 没有标准接口
  • 缺乏管理系统

2、模块化 module.js

  • 在node中,一个js就是一个模块

  • 在Node中,每一个js文件中的js代码都是独立运行在一个函数中,而不是全局作用域,所以一个模块中的变量和函数在其他模块中无法访问

  • 我们可以通过exports来向外部暴露变量和方法,只需要将要暴露给外部的变量或方法设置为exports的属性即可

    exports.x="我是modeule.js中的x"
    exports.fn = function(){}
  • 在Node中有一个全局对象global,它的作用和网页中window类似,在全局中创建的变量都会作为global的属性保存,在全局中创建的函数都会作为global的方法保存

    var a = 10;
    console.log(global.a);
  • arguments

    • arguments.callee 这个属性保存的是当前执行的函数对象
  • 在node执行模块中的代码时,它首先会在代码的最顶部添加代码function(exports, require, module, __filename, __dirname){,在代码的最底部,添加代码}.实际上模块中的代码都是包装在一个函数中执行的,并且在函数执行时,同时传递进了5个实参(arguments.length)。

    • exports 该对象用来将变量或函数暴露到外部

    • require 函数,用来引入外部的模块

    • module

      • module代表的是当前模块本身

      • exports就是module的属性

      • 既可以使用exports导出,也可以使用modeule.exports导出 module.exports==exportsmodule.exports.a exports.a

      • 通过exports只能使用.的方式来向外暴露内部变量,而modele.exports既可以通过.的形式,也可以直接复制

        exports.xxx = xxx
        module.exports.xxx = xxx
        module.exports = {
            name = "swk"
            age = 15000
            sayName = function{
                console.log(name)
            }
        }
  • __filename 当前模块的完整路径

  • __dirname 当前模块所在文件夹的完整路径

  • 在node中通过require()函数来引入外部的模块,require()可以传递一个文件的路径作为参数,node将会自动根据该路径来引入外部模块,这里的路径如果使用相对路径,必须以.或..开头。使用require引入模块后,该函数会返回一个对象,这个对象代表的是引入的模块
var md = require("./modele.js");
console.log(md)
  • 模块分为两大类

    • 核心模块

      • 由node引擎提供的模块
    • 文件模块

      • 由用户自己创建的模块

3、CommonJS规范

  • CommonJS规范的提出,主要是为了弥补当前JavaScript没有标准的缺陷。
  • CommonJS规范为js制定了一个美好的愿景,希望js能够在任何地方运行。
  • CommonJS规范对模块的定义十分简单
    • 模块引用
    • 模块定义
    • 模块标识
      • 我们使用require()引入外部模块时,使用的就是模块标识,我们可以通过模块标识来找到指定的模块
      • 核心模块的标识就是模块的名字
      • 文件模块的标识就是文件的路径(绝对、相对),相对路径使用.或..开头

五、包 Package

1、简介

  • CommonJS的包规范允许我们将一组相关的模块组合到一起,形成一组完整的工具。
  • CommonJS的包规范由包结构和包描述文件两个部分组成
  • 包结构:用于组织包中的各种文件
  • 包描述文件:描述包的相关信息,以供外部读取分析

2、包结构

包实际上就是一个压缩文件,解压以后还原为目录。

符合规范的目录,应该包含如下文件:

  • package.json 描述文件

    {
        "dependecies": {}, // 依赖
        "description": "描述",
        "devDependencies": { // 开发依赖
            ...
        }
        ...
        "version": "v1.0.0"
    }
  • bin 可执行二进制文件

  • lib js代码

  • doc 文档

  • test 单元测试

3、包描述文件

包描述文件用于表达非代码相关的信息,它是一个JSON格式的文件——package.json,位于包的根目录下,是包的重要组成部分

package.json中的字段(不能写注释)

  • name
  • description
  • version
  • keywords
  • maintainers
  • contributors
  • bugs
  • licenses
  • repositories
  • dependencies
  • homepage
  • os
  • cpu
  • engine
  • builtin
  • directories
  • implements
  • scripts
  • author
  • bin
  • main
  • devDependencies
  1. 标识
  2. 依赖
  3. 运行/打包

通过npm run

六、NPM(Node Package Manager)

1、简介

  • CommonJS包规范是理论,NPM是其中一种实践

  • 对于Node而言,NPM帮助其完成了第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模块之间形成了很好的一个生态系统。

2、NPM命令

  • npm -v 查看npm版本
  • npm version 查看所有模块版本
  • npm 帮助说明
  • npm init 在当前目录下创建package.json文件,驼峰命名改为_连接
  • npm search 包名 搜索包
  • npm install/i 包名 安装包,将会在当前目录的node_modules下,直接通过包名引入即可,var math = require("math")
  • npm install 包名 -g 全局安装包,一般都是一些工具
  • npm remove/r 包名 删除一个模块
  • npm install 包名 --save 安装包并将其添加到依赖中,一般不传node_modules,太大、不能保证是最新的,有了依赖,传package.json直接npm install可以下载当前项目所依赖的包
  • npm uninstall 包名 卸载包
  • npm install 文件路径 从本地安装
  • npm install 包名 -registry=地址 从镜像源安装包
  • npm config set registry 地址 设置镜像源

淘宝镜像:https://npm.taobao.org

一般不直接设置官方原版npm替换为其他源,可以使用cnpm

  • npm install -g cnpm --registry=https://registry.npm.taobao.org

注:

  • Node在使用模块名字来引入模块时,它会首先在当前目录的node_modules中寻找是否含有该模块,如果有则直接使用,如果没有则去上一级目录的node_modules中寻找,如果有则直接使用,如果没有则再去上一级目录寻找,知道找到为止,如果找到磁盘根目录仍没有,则报错。

七、Buffer(缓冲区)

  • Buffer的就结构和数组很像,操作的方法也和数组类似

  • 数组中不能存储二进制的文件,而buffer就是专门用来存储二进制数据的

  • 使用buffer不需要引入模块,直接使用即可

    var str = "hello"
    // 将一个字符串保存到buffer中
    var buf = Buffer.from(str)
    console.log(buf)
    console.log(buf.length,str.length)  // 占用内存的大小,字符串的长度
  • 在buffer中存储的都是二进制数据,但是在显示的时候都是以16进制的形式显示的,buffer中每一个元素的范围是从00到ff(0255) 0000000011111111 8位(bit) = 1字节(byte),buffer中的一个元素占用内存的一个字节 ps:一个汉字3字节,一个英文字母1字节

  • buffer的大小一旦确定,则不能修改,Buffer实际上是对底层内存的直接操作

    • Buffer.from(str)将一个字符串转为buffer
    • Buffer.alloc(size)创建一个指定大小的buffer
    • Buffer.allocUnsafe(size)创建一个指定的大小的buffer,但是可能包含敏感数据
    • buf.toString()将buffer里的数据转为字符串
    // 创建一个指定大小的buffer
    var buf = new Buffer(10) // 10个字节的buffer
    // buffer构造函数都是不推荐使用的
    var buf2 = Buffer.alloc(10)   // 全都是00
    buf2[0] = 88
    buf2[1] = 255
    buf2[2] = 0xaa
    buf2[3] = 556
    // 1000101100截取后八位
    buf2[10] = 15  // 没改变
    // 只要数字在控制台或页面中输出一定是10进制
    console.log(buf2[2])
    console.log(buf2[2].toString(16))  // 可以这样转为16进制显示
    
    var buf3 = Buffer.allocUnsafe(10)  // 创建一个指定大小的buffer,但是buffer中可能含有敏感数据,不全为00

https://nodejs.cn

八、文件系统(File System)

1、简介

  • 文件系统简单来说就是通过Nodejs来操作系统中的文件

  • 在Node中,与文件系统的交互是非常重要的,服务器的本质就是将本地的文件发送给远程的客户端

  • Node通过fs模块来和文件系统进行交互

  • fs模块提供了一些标准文件访问API来打开、读取、写入文件,以及与其交互

  • 要使用文件系统,需要先引入fs模块,fs是核心模块,直接引入不需要下载;要使用fs模块,首先需要对其进行加载const fs = require("fs")

2、同步和异步调用

  • fs模块中所有的操作都有两种形式可供选择,同步(fs.xxx)和异步(fs.xxxSync)

  • 同步文件系统会阻塞程序的执行,也就是除非操作完毕,否则不会向下执行代码

  • 异步文件系统不会阻塞程序的执行,而是在操作完成时,通过回调函数将结果返回

3、同步、异步文件写入

  • 同步文件的写入

    • 操作步骤

      1. 打开文件

        • fs.openSync(path, flags[, mode])

        • path 要打开文件的路径

        • flags 打开文件要做的操作的类型:r只读的、w可写的

        • mode 设置文件的操作权限,一般不传

        • 返回值: 返回一个文件的描述符,可以通过该描述符来对文件进行各种操作

          var fs = require("fs");
          var fd = fs.openSync("hello.txt", "w");
          // console.log(fd)
      2. 向文件中写入内容

        • fs.writeSync(fd, string[, position[, encoding]])

        • fd 文件的描述符,需要传递要写入的文件的描述符

        • string 要写入的内容

        • position 写入的起始位置

        • encoding 写入的编码,默认utf-8

          fs.writeSync(fd, "这是写入的内容")
      3. 保存并关闭文件

        • fs.closeSync(fd)
        • fd 要关闭的文件的描述符
  • 异步文件写入

    1. fs.open(path, flags[, mode], callback)

      • 用来打开一个文件

      • 异步方法没有返回值,有返回值的都是同步方法。异步调用的方法,结果都是通过回调函数参数返回的。

      • callback 回调函数两个参数(arguements):

        • err 错误对象,如果没有错误则为null

        • fd 文件的描述符

      var fs = require("fs")
      var f
      fs.open("hello.txt","w",function(err, fd){
          // console.log('回调函数中的代码')  // callback中的代码会在读取完毕之后执行
          if(!err){
              f = fd
          }else{
              console.log(err)
          }
      })
      console.log("open下的代码")    // 能比上面的更早执行
  1. fs.write(fd, string[, position[, encoding]], callback)

    • 用来异步写入一个文件
    1. fs.close(fd, callback)

      • 用来关闭文件

        var fs = require("fs")
        fs.open("hello.txt","w",function(err, fd){
           if(!err){
               fs.write(fd, "这是异步写入的内容",function(err)          {
                   if(!err){
                       console.log('写入成功')
                   }
                   fs.close(fd, function(err){
                       if(!err){
                           console.log('文件已关闭')
                       }
                   })
               })
           }else{
               console.log(err)
           }
        })

4、简单文件写入

  • fs.writeFile(file, data[, options], callback)

    • file 要操作的文件的路径

    • data 要写入的数据

    • options 选项,可以对写入进行一些设置,是一个对象{encoding, mode, flag}

      • encoding: ‘utf8’
      • mode: ‘0o666’
      • flag: ‘w’ 一般用r(只读)、w(可写)、a(追加)
    • callback 当写入完成以后执行的函数

      var fs = require('fs')
      // 路径也可以C:/Users/Shinlo/Desktop/hello.txt
      fs.writeFile("C:\\Users\\Shinlon\\Desktop\\hello.txt", "这是通过writeFile写入的内容", {flag: "a"}, function(err){
          if(!err){
              console.log('写入成功')
          }else{
              console.log(err)
          }
      })

      1588844894085

  • fs.writeFileSync(file, data[, options])

    • 同步简单写入

5、流式文件写入

  • 同步、异步、简单文件的写入都不适合大文件的写入,性能较差,容易导致内存溢出

  • 流式文件写入

    • fs.createWriteStream(path[, options])

      • 可以用来创建一个可写流
      • path 文件路径
      • options 配置的参数
      var fs = require("fs")
      var ws = fs.createWriteStream("hello.txt")
      // 可以通过监听流的open和close事件来监听流的打开和关闭
      // ws.on("open", function{  // on绑定一个长期有效的事件
      ws.once("open", function{   // once绑定一次性的事件,在触发一次之后事件自动失效
          console.log("流打开了")
      })
      ws.once("close", function{
          console.log("流关闭了")
      })
      // 通过ws向文件中输出内容
      ws.write("通过可写流写入文件的内容1")
      ws.write("通过可写流写入文件的内容2")
      ws.write("通过可写流写入文件的内容3")
      // 只要流还存在就可以接着写入
      // 关闭流
      // ws.close()    // 这个在传入的方向断开流,文件没到管子里
      ws.end()        // 在传出的这一方断开流,数据已经在管子里了

6、文件的读取

  • 同步文件读取

  • 异步文件读取

  • 简单文件读取

    • fs.readFile(path[, options], callback)

    • fs.readFileSync(path[, options])

      • path 要读取的文件的路径
      • options 读取的选项
      • callback 回调函数,通过回调函数将读取到的内容返回
        • err 错误对象
        • data 读取到的数据,会返回一个Buffer
      var fs = require("fs")
      var path="C:/Users/Shinlon/a.mp3"
      fs.readFile("hello.txt", function(err, data){
          if(!err){
              console.log(data)  // buffer通用性更高
              // console.log(data.toString())文本可以,其他不行
              fswriteFile("hello.mp3", data, function(err){
                  if(!err){
                      console.log("文件写入成功")
                  }
              })
          }
      })
  • 流式文件读取

    • 流式文件读取也适用于一些比较大的文件,可以分多次将文件读取到内存中
    • fs.createReadStream(path[, options])
    var fs = require("fs")
    // 创建一个可读流
    var rs = fs.createReadStream("an.jpg")
    // 监听流的开启和关闭
    rs.once("open", function(){
        console.log("可读流打开了")
    })
    rs.once("close", function(){
        console.log("可读流关闭了")
    })
    // 读取一个可读流中的数据,必须要为可读流绑定一个data事件,data事件绑定完毕,它会自动开始读取数据
    rs.on("data", function(data){
        console.log(data) // 参数就是数据 data.length最大65536字节
    })

可读流、可写流复制一个大文件

var fs = require("fs")
var rs = fs.createReadStream("an.jpg")
var ws = fs.createWriteStream("an.jpg")
rs.once("open", function(){
    console.log("可读流打开了")
})
rs.once("close", function(){
    console.log("可读流关闭了")
    // 数据读取完毕,关闭可写流
    ws.end()
})
ws.once("open", function(){
    console.log("可写流打开了")
})
ws.once("close", function(){
    console.log("可写流关闭了")
})
rs.on("data", function(data){
    ws.write(data)
})

简单的方式

var fs = require("fs")
var rs = fs.createReadStream("an.jpg")
var ws = fs.createWriteStream("an.jpg")
rs.once("open", function(){
    console.log("可读流打开了")
})
rs.once("close", function(){
    console.log("可读流关闭了")
})
ws.once("open", function(){
    console.log("可写流打开了")
})
ws.once("close", function(){
    console.log("可写流关闭了")
})
// pipe()可以将可读流中的内容直接输出到可写流中
rs.pipe(ws)

7、fs的其他方法

  • 验证路径是否存在

    • fs.exists(path, callback)
    • fs.exitsSync(path)
    var fs = require("fs")
    var isExists = fs.exitsSync("a.mp3")
    // console.log(isExists)
  • 获取文件信息

    • fs.stat(path, callback)获取文件的状态,会返回一个对象,这个对象中保存了当前对象状态的相关信息
    • fs.statSync(path)
    fs.stat("a.mp3", function(err, stat){
        console.log(stat)
    })
    • stat参数的一些属性、方法
      • size 大小
      • ……
  • 删除文件

    • fs.unlink(path, callback)
    • fs.unlinkSync(path)
    fs.unlinkSync("hello.txt")
  • 列出文件

    • fs.readdir(path[, options], callback)读取一个目录的目录结构
    • fs.readdirSync(path[, options])
      • files是一个字符串数组,每一个元素就是一个文件夹或文件的名字
    fs.readdir(".", function(err, files){
        if(!err){
            console.log(files)
        }
    })
  • 截断文件

    • fs.truncate(path, len, callback) 将文件修改为指定的大小
    • fs.truncateSync(path, len)
    fs.truncateSync("hello.txt", 3)
  • 建立目录

    • fs.mkdir(path[,mode], callback)
    • fs.mkdirSync(path[, mode])
    fs.mkdirSync("hello")
  • 删除目录

    • fs.rmdir(path, callback)
    • fs.rmdirSync(path)
    fs.rmdirSync("hello")
  • 重命名文件和目录

    • fs.rename(oldPath, newPath, callback)
    • fs.renameSync(oldPath, newPath)
      • oldPath 旧的路径
      • newPath 新的路径
      • callback 回调函数
    fs.rename("a.mp3", "new.mp3", function(err){
        if(!err){
            console.log("succece")
        }
    })
  • 监视文件更改写入

    • fs.watchFile(filename[, options], listener)
      • filename 要监视的文件的名字
      • options 配置选项
      • listener 回调函数,当文件发生变化时,回调函数会执行
        • curr 当前文件的状态
        • prev 修改前文件的状态
        • 这两个对象都是stats对象
    fs.watchFile("hello.txt", function{
        console.log(prev.size)
        console.log(curr.size)
    })
    • 时间间隔,配置选项中
    fs.watchFile("hello.txt", {interval: 1000}, function{
        console.log(prev.size)
        console.log(curr.size)
    })
MongoDB学习笔记

MongoDB学习笔记

MongoDB

一、数据库简介

  • 数据库是按照数据结构来组织、存储和管理数据的仓库
  • 我们的程序都是在内存中运行的,一旦程序运行结束或者计算机断电,程序运行的数据都会丢失
  • 所以我们就需要将一些程序运行的数据持久化到硬盘之中,以确保数据的安全性
  • 说白了,数据库就是存储数据的仓库
  • 数据库分类
    • 关系型数据库(RDBMS)
      • MySQL、Oracle、DB2、SQL Server……
      • 关系数据库中都是表
    • 非关系型数据库(No SQL Not Only SQL)
      • MongoDB、Redis……
      • 键值对数据库
      • 文档数据库MongoDB

二、MongoDB简介

  • MongoDB是为快速开发互联网Web应用而设计的数据库系统

  • MongoDB的设计目标是极简、灵活、作为Web应用栈的一部分

  • MongoDB的数据模型是面向文档的,所谓文档是一种类似于JSON的结构,简单理解MongoDB这个数据库中存的是各种各样的JSON(BSON)

  • 三个概念

    • 数据库(database):数据库是一个仓库,在仓库中可以存放集合
    • 集合(collection):集合类似于数组,在集合中可以存放文档
    • 文档(document):文档数据库中的最小单位,我们存储和操作的内容都是文档
  • 下载MongoDB

    • 下载地址:https://www.mongodb.org/dl/win32/
    • MongoDB的版本偶数版本为稳定版,奇数版本为开发版
    • MongoDB对于32位系统支持不佳,所以3.2版本以后没有再对32位系统的支持
  • 安装MongoDB

    • 安装

    • 配置环境变量 D:\Program Files\MongoDB\Server\4.2\bin

    • 在C盘根目录下创建data/db文件夹

    • 打开cmd命令行窗口

      • 输入mongod启动MongoDB服务器
      • 32位的初次启动需要输入mongod --storageEngine=mmapv1
    • 再打开一个cmd窗口

      • 输入mongo连接mongodb,出现>
    • 其他配置命令mongod --dbpath "D:\Program Files\MongoDB\Server\4.2\data\db --port 123"

    • 数据库

      • 服务器用来保存数据,mongod启动

      • 客户端用来操作服务器,对数据进行增删改查的操作,mongo来启动

      • 需要打开两个窗口,我们可以将MongoDB设置为系统服务,可以自动在后台启动,不需要每次都手动启动

        • C://data/创建db、log目录

        • 在D:\Program Files\MongoDB\Server\4.2下添加一个配置文件mongod.cfg,配置文件中添加内容

          systemLog:
              destination: file
              path: c:\data\log\mongod.log
          storage:
              dbPath: c:\data\db
        • 以管理员身份打开命令行窗口,输入命令sc.exe create MongoDB binPath="\"D:\Program Files\MongoDB\Server\4.2\bin\mongod.exe\" --service --config=\"D:\Program Files\MongoDB\Server\4.2\mongod.cfg\"" DisplayName= "MongoDB" start= "auto"

        • 启动MongoDB服务

        • 如果启动失败,则输入sc delete MongoDB删除服务之后,重新开始配置

三、MongoDB的基本操作

在MongoDB中,数据库和集合都不需要我们手动创建,当我们创建文档时,如果文档所在的集合或数据库不存在则会自动创建数据库和集合

MongoDB

1、基本命令

  • 显示当前的所有数据库
    • show dbs
    • show databases
  • 进入到指定的数据库中
    • use 数据库名
  • db表示的是当前所处的数据库
    • db
  • 显示数据库中所有的集合
    • show collections

2、数据库的CRUD操作

安装图形化工具:MongodbManagerFreeStudio-3T

  • 向数据库中插入文档

    • db.<collection>.insert(doc) 向集合中插入一个或多个文档。当我们向集合中插入文档时,如果没有给文档指定_id属性,则数据库会自动为文档添加_id,该属性用来作为文档的唯一标识,也可以自己指定,如果我们指定了,数据库就不会添加了,如果自己指定_id也必须确保它的唯一性
    // 向test数据库中的stus集合中插入一个新的学生对象{name:"swk",age:18,gender:"man"}
    use test
    db.stus.insert({name:"swk",age:18,gender:"man"})
    db.stus.insert([
        {name:"shs",age:38,gender:"man"},
        {name:"bgj",age:18,gender:"female"},
        {name:"zbj",age:28,gender:"man"}
    ])
    ObjectId()  // 根据时间戳、机器码
    db.stus.insert({_id:"ts",name:"ts",age:18,gender:"man"})   // 建议不要自己指定
    • db.<collection>.insertOne(doc) 插入一个文档对象
    • db.<collection>.insertMany(doc) 插入多个文档对象
    • 这两个和上面的差不多,就是写开了更容易理解

    注:MongoDB中的文档的属性值也可以是一个文档,当一个文档的属性值是文档时,我们称这为内嵌文档。

    MongoDB支持直接通过内嵌文档的属性进行查询,如果要查询内嵌文档,可以通过.的形式来匹配。属性名必须用引号括起来

  • 查询

    • db.<collectin>.find() 查询当前集合中的所有的文档
      • find()用来查询集合中所有符合条件的文档。在开发时,绝对不会执行不带条件的查询
      • find()可以接受一个对象作为条件参数
        • {}表示查询集合中所有的文档
        • {字段名:值}查询属性是指定值的文档
          • 值还可以添加一个选项,多个条件之间使用,连接
          • $eq 等于
          • $gt $gte 大于 大于等于
          • $in
          • $lt $lte
          • $ne
          • $nin
          • $or
      • find()返回的是一个数组
      • db.stus.find({}).count()返回对象个数,一般用这个;db.stus.find({}).length()
      • db.stus.find().limit(10) 设置显示数据的上限,这里表示只显示前十条数据
      • db.numbers.find().skip(10).limit(10) skip()用于跳过指定数量的数据,这里会显示第11~20条数据。MongoDB会自动调整skip和limit的位置,因此顺序随便
    • db.<collectin>.findOne() 查询当前集合中符合条件的第一个文档
      • findOne()返回的是一个文档对象
    db.stus.find();
    db.stus.find({});
    db.stus.find({}).count();
    db.stus.find({}).length();
    db.stus.find({_id:"ts"});
    db.stus.find({age: 28,name:"zbj"});
    db.stus.find({age: 28})[1];
    db.stus.find({age:{$gt:20}})
  • 修改

    • db.<collectin>.update(查询条件,新对象[,配置选项])
      • update()默认情况下回使用新对象来替换旧的对象
      • 如果需要修改指定的属性,而不是替换,需要使用“修改操作符”来完成修改
        • $set 可以用来修改文档中的指定属性
        • $push用于向数据中添加一个新的元素
        • $addT0Set向数据中添加一个新元素,如果数组中已经存在该元素,则不会添加
        • $unset 可以用来删除文档的指定属性
      • update()默认只会修改一个
    • db.<collectin>.updateMany(查询条件,新对象) 同时修改多个符合条件的文档
    • db.<collectin>.updateOne(查询条件,新对象) 修改一个符合条件的文档
    • db.<collectin>.replace(查询条件,新对象) 替换一个文档
    • 这三个也是将第一个分开了
    db.stus.upadate({name:'shs'},{age:29}) // 替换
    db.stus.upadate({name:'shs'},{$set{age:29, address:"lsh"}) // 修改,默认值修改一个
    db.stus.upadate({name:'shs'},{$set{address:"lsh"})
    db.stus.upadate({name:'shs'},{$unset{age:29}) // 删除
    db.stus.upadate({name:'shs'},{$set{age:29, address:"lsh"}) // 添加配置项也可以修改多个
  • 删除

    • db.<collectin>.remove()
      • remove()可以根据条件来删除文档,传递的条件的方式和find()一样
      • 删除符合条件的所有的文档,默认会删除多个
      • 如果remove()第二个参数传递一个true,则只会删除一个
      • 如果只传递一个空对象,则会删除所有的
    • db.<collectin>.deleteOne()
    • db.<collectin>.deleteMany()
    • db.collection.drop() 删除集合
    • db.dropDatabase()删除数据库
    • 一般数据库中的数据都不会删除,所以删除的方法很少调用,一般在数据中添加一个字段,来表示数据是否被删除
    db.stus.remove()
    
    db.stus.remove({})   // 清空集合(性能略差)
    db.stus.drop()     // 删除集合
    db.database()       // 删除数据库
    // 一般删除一个文档都是用这种方式
    db.stus.inert([
        {
            name: "ts",
            isDel: 0
        },
        {
            name: "zbj",
            isDel: 0
        },
        {
            name:"shs",
            isDel: 0
        }
    ])
    db.stus.updateOne({name:"ts"},{$et:{isDel:1}})
    db.stus.find({isDel:0})

四、文档之间的关系

  • 一对一(One to One)

    • 夫妻(一个丈夫对应一个妻子)
    • 在MongoDB中,可以通过内嵌文档的形式来体现出一对一的关系
    db.wifeAndHusband.insert([
        {
            name:"hr",
            husband:{
                name:"gj"
            }
        },{
            name:"pjl"
            husband:{
                name:"wdl"
            }
        }
    ])
  • 一对多(One to Many)/多对一(Many to One)

    • 父母——孩子、用户——订单、文章——评论
    • 也可以通过内嵌文档来映射一对多的关系,属性变为数组
    db.users.insert([
        {
            username:"swk"
        },{
            username:"zbj"
        }
    ])
    db.order.insert({
        list:["pg","xj","dyl"],
        user_id:ObjectId("swk_Id")
    })
    var user_idd = db.users.findOne({username:"swk"})._id
    db.order.find({user_id:user_idd})
  • 多对多(Many to Many)

    • 分类——商品、老师——学生
    • 学生属于多个老师、老师教多个学生,学生中有一个老师的id,id多个用数组
    db.teachers.insert([
        {
            name: 'hqg',
        },
        {
            name: 'hys',
        },
        {
            name: "gxr"
        }
    ])
    db.stus.insert([
        {
            name:"gj",
            tech_ids:[
                ObjectID('hqgID'),
                ObjectID("hysID")
            ]
        },
        {
            name:"swk",
            tech_ids: [
                ObjectID('hqgID'),
                ObjectID("hysID"),
                ObjectID("gxr")
            ]
        }
    ])
    db.teachers.find()
    db.stus.find()

五、sort和投影

  • db.emp.find() 查询文档时,默认情况是按照_id的值进行排列(升序)
  • db.emp.find({}).sort({sal:1}) sort()可以用来指定文档的排序的规则,sort()需要传递一个队形来指定排序的规则
    • 1 表示升序
    • -1 表示降序
    • db.emp.find({}).sort(sal:1, empno:-1) 先按照sal升序排列,如果sal相同,按照empno降序排列
  • 注:limit、skip、sort书写顺序没有要求
  • db.emp.find({}, {ename:1})在查询时,可以在第二个参数的位置来设置查询结果的投影 db.emp.find({}, {ename: 1, _id: 0, sal: 1})

六、MongoOse简介

1、简介

  • 之前我们都是通过shell来完成对数据库的各种操作的,在开发中大部分的时候我们都需要通过程序来完成对数据库的操作
  • Mongoose就是一个让我们可以通过Node来操作MongoDB的模块
  • Mongoose是一个对象文档模型(ODM)库,它对Node原生的MongoDB模块进行了进一步的优化封装,并提供了很多的功能
  • 在大多数情况下,它被用来把结构化的模式应用到一个MongoDB集合,并提供了验证和类型转换等好处

2、Mongoose好处

  • 可以为文档创建一个模式结构(Schema) ——约束
  • 可以对模型中的对象/文档进行验证
  • 数据可以通过类型转换转为对象模型
  • 可以使用中间件来应用业务逻辑挂钩
  • 比Node原生的MongoDB驱动更容易

3、新的对象

  • Schema(模式对象)
    • 定义约束了数据库中的文档结构
  • Model
    • Model对象作为集合中所有文档的表示,相当于MongoDB数据库中的集合
  • Document
    • Document表示集合中的具体文档,相当于集合中的一个具体的文档

七、连接MongoDB数据库

  1. 下载安装Mongoose npm i mongoose --save
  2. 在项目中引入Mongoose var mongoose = require("mongoose")
  3. 连接数据库 mongoose.connect('mongodb://数据库IP地址:端口号/数据库名');
    • 端口号如果是默认端口号(27017),则可以省略不写
  4. [可选] 监听MongoDB数据库的连接状态
    • 在Mongoose对象中,有一个属性叫做connection,该对象表示的就是数据库连接通过监视该对象的状态,可以来监听数据库的连接与断开
      • mongoose.connection.once("open",function(){}) 数据库连接成功的事件
      • mongoose.connection.once("close",function(){}) 数据库断开的事件
  5. 断开数据库连接(一般不需要调用) mongoose.disconnect()
    • MongoDB数据库,一般情况下只需要连接一次,连接一次以后,除非项目停止、服务器关闭,否接连接一般不会断开
// 例子
var mongoose = require("mongoose")
mongoose.connect("mongodb://127.0.0.1/mongoose_test")
mongoose.connection.once("open",function(){
    console.log("数据库已连接")
})
mongoose.connection.once("close",function(){
    console.log("数据库已断开")
})
mongoose.disconnect()

八、Schema、Model和Document

有了Model,就可以对数据库进行增删改查的操作了

  • Model.crate(doc(s), [callback])
    • 用来创建一个文档并添加到数据库中
    • doc(s) 可以是一个文档对象,也可以是一个文档对象的数组
    • callback 当操作完成之后调用的回调函数
  • 查询
    • Model.find(conditions, [projection], [options], [callback]) 查询所有符合条件的文档 总会返回一个数组
    • Model.findById(id, [projection], options), [callback] 根据文档的id属性查询文档
    • Model.findOne([conditions], [projection], [options], [callback]) 查询符合条件的第一个文档 总会返回一个具体的对象
      • conditions 查询的条件
      • projection 投影 需要获取到的字段 两种方式
        • {} 对象,要的字段为1,不要的为0
        • "" 字段名、不要的设为-
      • options 查询选项(skip limit)
      • callback 回调函数,查询结果会通过回调函数返回,毁掉函数必须传,如果不传回调函数,压根不会查询
    • 通过find()查询的结果,返回的对象,就是Document,文档对象
  • 修改
    • Model.update(conditions, doc, [options], [callback])
    • Model.updateMany(conditions, doc, [options], [callback])
    • Model.updateOne(conditions, doc, [options], [callback])
      • 用来修改一个或多个文档
      • conditions 查询条件
      • doc 修改后的对象
      • options 配置参数
      • callback 回调函数
    • Model.replaceOne(conditions, doc, [options], [callback]) 替换
  • 删除(不用这个)
    • Model.remove(conditions, [callback])
    • Model.deleteOne(conditions, [callback])
    • Model.deleteMany(conditions, [callback])
  • Model.count(conditions, [callback])
    • 统计文档的数量

Document和集合中的文档一一对应,Document是Model的实例,通过Model查询到结果都是Document

Document的方法

  • Model#save([options], [options.safe], [options.validateBeforeSave], [fn])
  • update(update, [options], [callback])
    • 修改对象
  • remove([callback])
    • 删除对象(拒绝用这个)
  • get(name)
    • 获取文档中的指定属性值
  • set(name, value)
    • 设置文档的指定的属性值
  • id
    • 获取文档的_id属性值
  • toJSON()
    • 转换为一个JSON对象
  • toObject()
    • 将Document对象转换为普通的js对象
    • 转换为普通的js对象以后,所有的Document对象的方法或属性都不能使用了
  • 其他 equals(doc) isNew isInit(path)
var mongoose = require("mongoose")
mongoose.connect("mongodb://127.0.0.1/mongoose_test", {useMongoClient:true})
// Schema对数据进行约束
// 将mongoose.Schema赋值给一个变量
var Schema = mongoose.Schema
// 创建Schema(模式)对象
var stuSchema = new Schema({
    name: String,
    age: Number,
    gender: {
        type: String,
        default: "male"
    },
    address: String
})

// Model代表的是数据库中的集合,通过Model才能对数据库进行操作
// 通过Schema来创建Model
// mogoose.model("要映射的集合名", 刚创建的Schema对象) Mongoose会自动将集合名变为复数
var StuModel = mogoose.model("student", stuSchema)
// 向数据库中出入一个文档 model.create(文档, 回调函数)
stuModel.create({
    name: 'swk',
    age: 18,
    gender: 'male',
    address: "hgs"
}, function(err){
    if(!err){
        console.log("插入成功")
    }
})
stuModel.create([{
    name: 'shs',
    age: 28,
    gender: 'male',
    address: "lsh"
},{
    name: "bgj",
    age: 17,
    gender: "female",
    address: "bgd"
}], function(err){
    if(!err){
        console.log("插入成功")
    }
})

// 查询
StuModel.find({name:"shs"},function(err, docs){
    if(!err){
        console.log(docs)
        console.log(docs[0].name)
    }
})
/* StuModel.find({name:"shs"},{name:1, _id:0},function(err, docs){
    if(!err){
        console.log(docs)
    }
}) */
StuModel.find({name:"shs"},"name -_id",function(err, docs){
    if(!err){
        console.log(docs)
    }
})
StuModel.find({name:"shs"},"name -_id",{skip:3, limit:1},function(err, docs){
    if(!err){
        console.log(docs)
    }
})
StuModel.findOne({}, function(err, doc){
    if(!err){
        console.log(doc.name)
    }
})
StuModel.findById("...",function(err,doc){
    if(!err){
        console.log(doc)
    }
})

// Document对象是Model的实例
/* StuModel.findById("...",function(err,doc){
    if(!err){
        console.log(doc instantceof StuModel)
    }
})
 */

StuModel.updateOne({name: 'ts'}, {$set:{age:20}},function(err){
    if(!err){
        console.log("修改成功")
    }
})

StuModel.remove({name:"bgj"}, function(err){
    if(!err){
        console.log("删除成功")
    }
})

StuModel.count({}, function(err, count){
    if(!err){
        console.log(count)
    }
})



// 创建一个Document
var stu = new StuModel({
    name: "blm",
    age: 14,
    gender: "male",
    address: "bbt"
})
stu.save(function(err){
    if(!err){
        console.log("保存成功")
    }
})
StuModel.findOne({}, function(err, doc){
    if(!err){
        /* doc.update({$set:{age: 28}}, function(err){
            if(!err){
                console.log("修改成功")
            }
        }) */
        doc.age = 28
        save()

        /* doc.remove(function(err){
            if(!err){
                console.log("删除成功")
            }
        }) */
        // console.log(doc.get("age"))
        console.log(doc.age)
        // doc.set("name", "zhu")
        doc.name = "zhu"
        // console.log(doc.id)
        console.log(doc._id)
        console.log(doc.toJSON())
        doc = doc.toObject()
        delete doc.address
        console.log(doc)
    }
})

九、Mongoose的模块化

定义一个模块,用来连接MongoDB数据库

// conn_mongo.js
var mongoose = require("mongoose")
mongoose.connect("mongodb://127.0.0.1/mongoose_test")

定义一个student的模型,几个模型就可以创建几个js文件

// student.js
var mongoose = require("mongoose")
var Schema = mongoose.Schema
var stuSchema = new Schema({
    name: String,
    age: Number,
    gender: {
        type: String,
        default: "male"
    },
    address: String
})
var StuModel = mongoose.model("student", stuSchema)
// exports.model = StuModel
module.exports = StuModel

其他地方引入

require("./conn_mongo")
// var Student = require("./models/student").model
var Student = require("./models/student")
Student.find({}, function(err, docs){
    if(!err){
        console.log(docs)
    }
})

练习

//1.进入my_test数据库
use my_test

//2.向数据库的user集合中插入一个文档  
db.users.insert({
    username:"sunwukong"
});

//3.查询user集合中的文档
db.users.find();

//4.向数据库的user集合中插入一个文档   
db.users.insert({
    username:"zhubajie"
});

//5.查询数据库user集合中的文档
db.users.find();

//6.统计数据库user集合中的文档数量
db.users.find().count();

//7.查询数据库user集合中username为sunwukong的文档
db.users.find({username:"sunwukong"});

//8.向数据库user集合中的username为sunwukong的文档,添加一个address属性,属性值为huaguoshan
db.users.update({username:"sunwukong"},{$set:{address:"huaguoshan"}});


//9.使用{username:"tangseng"} 替换 username 为 zhubajie的文档
db.users.replaceOne({username:"zhubajie"},{username:"tangseng"});    

//10.删除username为sunwukong的文档的address属性
db.users.update({username:"sunwukong"},{$unset:{address:1}});


//11.向username为sunwukong的文档中,添加一个hobby:{cities:["beijing","shanghai","shenzhen"] , movies:["sanguo","hero"]}
//MongoDB的文档的属性值也可以是一个文档,当一个文档的属性值是一个文档时,我们称这个文档叫做 内嵌文档
db.users.update({username:"sunwukong"},{$set:{hobby:{cities:["beijing","shanghai","shenzhen"] , movies:["sanguo","hero"]}}});
db.users.find();

//12.向username为tangseng的文档中,添加一个hobby:{movies:["A Chinese Odyssey","King of comedy"]}
db.users.update({username:"tangseng"},{$set:{hobby:{movies:["A Chinese Odyssey","King of comedy"]}}})

//13.查询喜欢电影hero的文档
//MongoDB支持直接通过内嵌文档的属性进行查询,如果要查询内嵌文档则可以通过.的形式来匹配
//如果要通过内嵌文档来对文档进行查询,此时属性名必须使用引号 
db.users.find({'hobby.movies':"hero"});

//14.向tangseng中添加一个新的电影Interstellar
//$push 用于向数组中添加一个新的元素
//$addToSet 向数组中添加一个新元素 , 如果数组中已经存在了该元素,则不会添加
db.users.update({username:"tangseng"},{$push:{"hobby.movies":"Interstellar"}});
db.users.update({username:"tangseng"},{$addToSet:{"hobby.movies":"Interstellar"}});
db.users.find();

//15.删除喜欢beijing的用户
db.users.remove({"hobby.cities":"beijing"});

//16.删除user集合
db.users.remove({});
db.users.drop();

show dbs;

//17.向numbers中插入20000条数据 7.2s
for(var i=1 ; i<=20000 ; i++){
    db.numbers.insert({num:i});
}

db.numbers.find()

db.numbers.remove({});


//0.4s
var arr = [];

for(var i=1 ; i<=20000 ; i++){
    arr.push({num:i});
}

db.numbers.insert(arr);


//18.查询numbers中num为500的文档
db.numbers.find({num:500})

//19.查询numbers中num大于5000的文档
db.numbers.find({num:{$gt:500}});
db.numbers.find({num:{$eq:500}});

//20.查询numbers中num小于30的文档
db.numbers.find({num:{$lt:30}});

//21.查询numbers中num大于40小于50的文档
db.numbers.find({num:{$gt:40 , $lt:50}});

//22.查询numbers中num大于19996的文档
db.numbers.find({num:{$gt:19996}});

//23.查看numbers集合中的前10条数据
db.numbers.find({num:{$lte:10}});

//limit()设置显示数据的上限
db.numbers.find().limit(10);
//在开发时,我们绝对不会执行不带条件的查询
db.numbers.find();

//24.查看numbers集合中的第11条到20条数据
/*
    分页 每页显示10条
        1-10     0
        11-20    10
        21-30    20
        。。。

        skip((页码-1) * 每页显示的条数).limit(每页显示的条数);

    skip()用于跳过指定数量的数据    

    MongoDB会自动调整skip和limit的位置
*/
db.numbers.find().skip(10).limit(10);

//25.查看numbers集合中的第21条到30条数据
db.numbers.find().skip(20).limit(10);

db.numbers.find().limit(10).skip(10);




//26.将dept和emp集合导入到数据库中
db.dept.find()
db.emp.find()

//27.查询工资小于2000的员工
db.emp.find({sal:{$lt:2000}});

//28.查询工资在1000-2000之间的员工
db.emp.find({sal:{$lt:2000 , $gt:1000}});

//29.查询工资小于1000或大于2500的员工
db.emp.find({$or:[{sal:{$lt:1000}} , {sal:{$gt:2500}}]});

//30.查询财务部的所有员工
//(depno)
var depno = db.dept.findOne({dname:"财务部"}).deptno;
db.emp.find({depno:depno});

//31.查询销售部的所有员工
var depno = db.dept.findOne({dname:"销售部"}).deptno;
db.emp.find({depno:depno});

//32.查询所有mgr为7698的所有员工
db.emp.find({mgr:7698})

//33.为所有薪资低于1000的员工增加工资400元
db.emp.updateMany({sal:{$lte:1000}} , {$inc:{sal:400}});
db.emp.find()
AngularJs学习笔记

AngularJs学习笔记

1、Angular介绍

  • 官网: https://angularjs.org
  • Angular是Google开源的前端JS结构化框架
  • AngularJS特性和优点
    • 双向数据绑定
    • 声明式依赖注入
    • 解耦应用逻辑,数据模型和视图
      • 耦合度:两者之间关系密切度
      • 降低耦合度
    • 完善的页面指令
    • 定制表单验证
    • Ajax封装
  • 与jQuery比较
    • jQuery
      • JS函数库
      • 封装简化DOM操作
    • Angular
      • JS结构化框架
      • 主体不再是DOM,而是页面中的动态数据
  • AngularJS能做什么项目
    • 构建单页面(SPA)Web应用或Web APP应用
      • 单页面应用(SPA)Simple Page Application特点:
        • 将所有的活动局限于一个页面
        • 当页面中有部分数据发生了变化不会去刷新整个页面,而是局部刷新
        • 利用的Ajax技术、路由
    • 应用
  • 版本学习
    • 1.0 JavaScript
    • 2.0 3.0 4.0 TypeScript

2、第一个Angular程序

使用jQuery实现

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>jQuery实现</title>
</head>
<body>
  <input type="text">
  <p>您输入的内容是:<span></span></p>
  <script src="jquery.js"></script>
  <script>
    $(function(){  //document.ready 文档(页面结构)加载完毕 window.onload:整个页面加载完毕,包括图片等资源
      $('input').keyup(function(){
        var value = this.value   // $(this).val()
        $('span').html(value)
      })
    })
  </script>
</body>
</html>

使用AngularJS实现

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>AngularJS实现</title>
</head>
<body ng-app>
  <input type="text" ng-model="username">
  <p>您输入的内容是:<span>{{username}}</span></p>
  <script src="https://cdn.bootcdn.net/ajax/libs/angular.js/1.2.29/angular.js"></script>
</body>
</html>

Chrome插件:

ng-inspector for AngularJS

  • ng-app(指令):告诉angular核心,它管理当前标签所包含的整个区域,并且会自动创建$rootScope根作用域对象(通常放在body标签)
  • ng-model:将当前输入框的值与谁关联(属性名:属性值),并作为当前作用域对象($rootScope)的属性
{{}}(表达式):显示数据,从当前作用域对象的指定属性名上取
  • 表达式:通常有一个返回值,可以放在任何需要值的地方,比如函数调用的参数、一个变量名、一个运算等

  • 语句:通常表示一个完整的执行单位,一段完整的js可执行代码,有的语句也可以用表达式来执行,称为表达式语句

  • 区别:语句用分号结尾,有些语句我们没有加分号,例如console.log虽然没加分号,但也是语句,因为js引擎会自动在解析的时候加上分号

  • 特例:if语句,就不用加分号,也可以是完整的语句

二、四个重要概念

1、双向数据绑定

  • 数据绑定:数据从一个地方A转移(传递)到另一个地方B,而且这个操作由框架来完成

  • 双向数据绑定:数据可以从View(视图层)流向Model(模型),也可以从Model流向View

    • 视图(View):也就是我们的页面(主要是Angular指令和表达式)

    • 模型(Model):作用域对象(当前为$rootScope),它可以包含一些属性或方法

    • 当改变View中的数据,Model对象的对应属性也会随之改变:ng-model指令 数据从View到Model

    • 当Model域对象的属性发生改变时,页面对应数据随之更新:{{}}表达式 数据从Model到View

    • ng-model是双向数据绑定,而{{}}是单向数据绑定

      (View——页面 Model——内存)

  • ng-init:用来初始化当前作用域变量 (View–>Model–>View)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>AngularJS实现</title>
</head>
<body ng-app>    <!-- ng-app=""除了接管区域,还会自动生成根作用域($rootScope) -->
  <!--
  <input type="text" ng-model="name">   先传递到Model,再从Model到三个View中的相应位置
  <p>姓名1:{{name}}</p>
  <input type="text" ng-model="name">
  <p>姓名2:{{name}}</p>
  -->
  <script src="https://cdn.bootcdn.net/ajax/libs/angular.js/1.2.29/angular.js"></script>
</body>
</html>

显示结果

测试

过程

<body ng-app>

ng-app=””除了接管区域,还会自动生成根作用域($rootScope)

<input type="text" ng-model="name">

过程1

<p>姓名1:{{name}}</p>

过程2

  • 初始化数据
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>AngularJS实现</title>
</head>
<body ng-app ng-init="name='Tom'"> <!-- ng-init初始化数据 -->
  <input type="text" ng-model="name">
  <p>姓名1:{{name}}</p>
  <input type="text" ng-model="name">
  <p>姓名2:{{name}}</p>
  <script src="https://cdn.bootcdn.net/ajax/libs/angular.js/1.2.29/angular.js"></script>
</body>
</html>

过程3

2、依赖注入

  • 依赖对象:完成某个特定的功能需要某个对象才能实现,这个对象就是依赖对象。
  • 依赖注入:依赖的对象以形参的形式被注入进来使用,这种方式就是依赖注入。
  • Angular的$scope对象就是依赖对象,并且是依赖注入的形式进行使用。
  • 回调函数的event的就是依赖对象
  • 回调函数有形参就是依赖注入

补:

开发的两种方式:

  • 命令式
    • 更加注重的是执行的过程
    • 更像考试的解答题
  • 声明式
    • 更加注重的是执行的结果
    • 声明式是对命令的局部包装
    • 更像选择题或填空题

eg:数组中每一项加10

var arr = [1,2,3,4,5]
var newArr1 = []
// 命令式
for(var i = 0; i < arr.length; i++){
    var value = arr[i] + 10
    newArr1.push(value)
}
console.log(newArr1)
// 声明式
var newArr2 = arr.map(function(item, index){
    return item + 10
})
console.log(newArr2)

三、三个重要对象

1、作用域与控制器

  • 作用域对象
    • 一个JS实例对象,ng-app指令默认会创建一个根作用域对象($rootScope)
    • 它的属性和方法与页面中的指令或表达式是关联的
  • 控制器
    • 用来控制AngularJS应用数据的实例对象
    • ng-controller:指定控制器构造函数,Angular会自动new此函数创建控制器对象
    • 同时Angular还有创建一个新的域对象$scope,它是$rootScope的子对象
    • 在控制器函数中声明$scope形参,Angular会自动将$scope传入
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>AngularJS实现</title>
</head>
<body ng-app ng-init="age=12">
  <div ng-controller="MyController">
    <input type="text" placeholder="姓" ng-model="firstName">
    <input type="text" placeholder="名" ng-model="lastName">
    <p>姓名1:{{firstName+'-'+lastName}}</p>
    <p>姓名2:{{getName()}}</p>
    {{age}}
  </div>
  <div>
    {{firstName}}   <!-- 不能显示 -->
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/angular.js/1.2.29/angular.js"></script>
  <script>
    function MyController($scope){ // 形参必须是$scope
      // console.log($scope)
      // console.log(this instanceof MyController)
      $scope.firstName = 'lu'
      $scope.lastName = 'wang'
      $scope.getName = function(){
        return $scope.firstName + ' ' + $scope.lastName
        // return this.firstName + ' ' + this.lastName
      }
    }
  </script>
</body>
</html>

作用域

2、模块和控制器

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>AngularJS实现</title>
</head>
<body ng-app="myApp">   <!--指向模块对象的名字-->
  <div ng-controller="MyController">
    <input type="text" ng-model='empName'>
    <p>员工名字1:{{empName}}</p>
  </div>
  <div ng-controller="MyController2">
    <input type="text" ng-model='empName'>
    <p>员工名字2:{{empName}}</p>
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/angular.js/1.5.5/angular.js"></script>
  <script>
    // console.log(angular)
    /* // 创建模块对象
    var myModule = angular.module("myApp", [])
    // 生成作用域对象
    myModule.controller('MyController', function($scope){
      $scope.empName = 'Tom'
    })
    myModule.controller('MyController2', function($scope){
      $scope.empName = 'Jack'
    }) */
    // 优化 链式调用
    /* <angular.module("myApp", [])
          .controller('MyController', function($scope){  // 返回值是模块对象
            $scope.empName = 'Tom'
          })
          .controller('MyController2', function($scope){ // 隐式声明依赖注入
            $scope.empName = 'Jack'
          }) */
      // 但是,由于js代码压缩之后形参会用其他字母abcd代替,会造成angular解析不了,解决方案:
      <angular.module("myApp", [])
          .controller('MyController', ['$scope', function($scope){ // 显式声明依赖注入
            $scope.empName = 'Tom'
          }])
          .controller('MyController2', ['$scope', function($scope){
            $scope.empName = 'Jack'
          }])
  </script>
</body>
</html>

四、两个页面语法

1、表达式

  • 使用Angular表达式:
    • 语法:
    • 作用:显示表达式的结果数据
    • 注意:表达式中引用的变量必须是当前域对象有的属性(包括其原型属性)
  • 操作的数据
    • 基本类型数据:Number/String/Boolean
    • undefined, Infinity,NaN,null解析为空串””,不显示任何效果
    • 对象的属性或方法
    • 数组

2、常用指令

  • Angular指令
    • Angular为HTML页面扩展的:自定义标签属性或标签
    • 与Angular的作用域对象(scope)交互,扩展页面的动态表现力
  • 常用指令
    • ng-app:
    • ng-model
    • ng-init
    • ng-click
    • ng-controller
    • ng-bind
    • ng-repeat
    • ng-show
    • ng-hide
    • ng-class
    • ng-style
    • ng-mouseenter
    • ng-mouseleave

练习

1、我的笔记

功能:在文本输入区域写文本,下方显示可输入剩余字符量;保存能够将文本保存,这样点击读取的时候文本能接着在文本输入区域显示,点删除删除

2、我的备忘录

ES和模块化学习笔记

ES和模块化学习笔记

一、理解ES

  1. 全称: ECMAScript

    • 它是一种由ECMA组织(前身为欧洲计算机制造商协会)制定和发布的脚本语言规范
    • 我们学习的JavaScript就是ECMA的实现,但属于ECMAScript和JavaScript平时表达同一个意思
  2. JS包含三个部分:

    • ECMAScript(js基础、核心)

    • 扩展–>浏览器端

      • BOM(浏览器对象模型)
      • DOM(文档对象模型)
    • 扩展–>服务器端

      • Node.js
  3. ES的几个重要版本

    • ES5:09年发布
    • ES6(ES2015):15年发布,也称为ECMA2015——重点
    • ES7(ES2016):16年发布,也称为ECMA2016(变化不大)

二、ES5

1、严格模式

  • 理解

    • 运行模式: 正常(混杂)模式与严格模式
    • 这种模式使得JavaScript在更严格的语法条件下运行
  • 目的/作用:

    • 使得Javascript在更严格的条件下运行
    • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为
    • 消除代码运行的一些不安全之处,保证代码运行的安全
  • 使用

    • 在全局或函数的第一条语句定义为:use strict
    • 如果浏览器不支持,只解析为一条简单的语句,没有任何副作用
  • 语法和行为改变

    • 声明定义变量必须用var
    • 禁止自定义的函数中的this关键字指向全局对象(window)
    • 创建eval作用域, 更安全
    • 对象不能有重名的属性
    <script>
      'use strict'
      var username = 'luwang'
      // name = 'luwang' 在严格模式下不用var声明变量会报错
      console.log(username)
    
      function Person(name, age){
        this.name = name
        this.age = age
      }
      new Person('luwang', 23)
      // Person('luwang', 23) //没有new会报错
    
      var str = 'web'
      eval('var str = "HTML"; alert(str)') // HTML
      alert(str) // web 即开启严格模式之后不会污染全局作用域
    
      var obj = {
        username: 'luwang',
        username: 'luwang'  // 定义重名了
      }
    </script>

2、JSON对象

  • 作用: 用于在json对象/数组与js对象/数组相互转换
  • JSON.stringify(obj/arr) js对象(数组)转换为json对象(数组)
  • JSON.parse(json) json对象(数组)转换为js对象(数组)
    <script>
    var obj = {username: 'wallleap'}
    obj = JSON.stringify(obj)
    console.log(typeof obj)
    obj = JSON.parse(obj)
    console.log(typeof obj)
    </script>

3、Object扩展

ES5给Object扩展了一些静态方法,常用的两个:

  • Object.create(prototype[, descriptors]): 创建一个新的对象

    • 作用:以指定对象为原型创建新的对象

    • 为新的对象指定新的属性, 并对属性进行描述

      • value: 指定值
      • writable : 标识当前属性值是否是可修改的, 默认为false
      • configurable:标识当前属性是否可以被删除,默认为false
      • enumerable:标识当前属性是否能用for in枚举,默认为false
      <script>
        var obj = {username: 'luwang', age:23}
        var obj1 = {}
        obj1 = Object.create(obj, {  // obj的属性为obj1的原型
          sex: {
            value: '男',
            writable: true,  // 默认false
            configurable: true,
            enumerable: true
          }
        })
        console.log(obj1.sex)
        obj1.sex = 'nan'
        console.log(obj1.sex)
        delete obj1.sex
        console.log(obj1)
        for(var i in obj1){
          console.log(i)
        }
      </script>
  • Object.defineProperties(object, descriptors): 为指定对象定义扩展多个属性

    • get方法 : 用来得到当前属性值的回调函数
    • set方法 : 用来监视当前属性值变化的回调函数
    <script>
      var obj = {username: 'luwang', age:23}
      var obj1 = {}
      var obj2 = {firstName: 'lu', lastName: 'wang'}
      Object.defineProperties(obj2, {
        fullName: { // 此方法在原型中
          get: function(){ // 获取扩展属性的值
            console.log('get方法被调用')
            return this.firstName + ' ' + this.lastName
          },
          set: function(data){ // 监听扩展属性,当扩展属性发生变化的时候会自动调用,自动调用后会讲变化的值作为实参注入到set函数
            console.log('set方法被调用,', data)
            var names = data.split(' ') // 根据空格拆分为数组
            this.firstName = names[0]
            this.lastName = names[1]
          }
        }
      })
      console.log(obj2.fullName) // get会自动调用 
      obj2.fullName = 'lu wang'  
      console.log(obj2.fullName)
    </script>

    console.log(obj2) fullName

    惰性求值:点击才给值(什么时候要什么时候给),会再次调用get方法

    惰性求值

    • 什么时候调用:
      • get方法:获取扩展属性值的时候get方法自动调用
      • set方法:监听
    • 存储器属性:setter,getter一个用来存值,一个用来取值

对象本身也有两个方法

  • get propertyName(){}
  • set propertyName(){}
<script>
  var obj = {
    firstName: 'lu', 
    lastName: 'wang',
    get fullName(){
      return this.firstName + ' ' + this.lastName
    },
    set fullName(data){
      var names = data.split(' ')
      this.firstName = names[0]
      this.lastName = names[1]
    }
  }
  console.log(obj)
  obj.fullName = 'lu wang'
  console.log(obj.fullName)
</script>

4、Array扩展

  • Array.prototype.indexOf(value) : 得到值在数组中的第一个下标

  • Array.prototype.lastIndexOf(value) : 得到值在数组中的最后一个下标

  • Array.prototype.forEach(function(item, index){}) : 遍历数组

  • Array.prototype.map(function(item, index){}) : 遍历数组返回一个新的数组

  • Array.prototype.filter(function(item, index){}) : 遍历过滤出一个子数组,返回条件为true的值

    var arr = [2,4,5,1,6,7,4,3,9]
    console.log(arr.indexOf(4))
    console.log(arr.lastIndexOf(4))
    arr.forEach(function(item, index){
        console.log(item, index)
    })
    var arr1 = arr.map(function(item, index){
        return item + 10
    })
    console.log(arr, arr1)
    arr.filter(fuction(item, index){
        return item > 3
    })

5、Function扩展

this,强制绑定使用call和bind

eg:

var obj = {username: 'luwang'}
function foo(){
    console.log(this)   
}
foo() // this-->Window 全局
// call和apply不传参的时候是一样的
foo.call(obj) // this-->{username: 'luwang'} obj对象
foo.apply(obj) // this-->{username: 'luwang'} obj对象
// bind的特点: 绑定完this不会立即调用当前的函数,而是将函数返回
// var bar = foo.bind(obj)
// bar()
foo.bind(obj)()


// 传入参数的形式
var obj1 = {age: 23}
function fun(data){
    console.log(this, data)
}
fun(22) // Window  22
// call直接从第二个参数开始,依次传入
fun.call(obj1, 21) // {age: 23} 21
// 第二参数必须是数组,传入放在数组里
fun.apply(obj1, [20]) // {age: 23} 20

// bind传参的方式通call一样
fun.bind(obj1, 18)()
  • Function.prototype.bind(obj)

    • 将函数内的this绑定为obj, 并将函数返回
  • 面试题: 区别bind()与call()和apply()?

    • fn.bind(obj) : 指定函数中的this, 并返回函数(不会立即调用),一般用在回调函数绑定其他对象的this

      var obj = {username: 'luwang'}
      setTimeout(function(){
          console.log(this) // Window
      }, 1000)
      setTimeout(function(){
          console.log(this) // Window
      }.bind(obj), 1000)
    • fn.call(obj): 指定函数中的this,并调用函数

    • fn.apply(obj)

6、Date扩展

  • Date.now() : 得到当前时间值

三、ES6

1、2个新的关键字

  • 块作用域:ES5中没有(只有全局和函数作用域),ES6有
  1. let

    • 作用:与var相似,用于声明一个变量

    • 特点

      • 在块作用域内有效
      • 不能重复声明
      • 不会预处理,不存在变量提升
    • 应用

      • 循环遍历加监听

        <br/><button>按钮1</button><br/><br/>
        <button>按钮2</button><br/><br/>
        <button>按钮3</button>
        <script>
            var btns = document.getElementsByTagName('button')
            for(var i = 0; i < btns.length; i++){
                var btn = btns[i]
                btn.onclick = function(){
                    alert(i)
                }
            }
            /*
             * 一直会显示3
             * 点击事件对应的是回调函数,回调函数又称勾子函数,回调函数会被放到事件队列中,等主线程上的代码执行完毕之后再通过钩子一样的形式,勾出来执行
             * 以前的方式是通过闭包,立即执行函数(自己的作用域)
            */
            for(var i = 0; i < btns.length; i++){
              var btn = btns[i]
              ;(function(i){  // 声明的形参
                btn.onclick = function(){
                alert(i)
              }
              })(i)   // 传的实参
            }
            /*
            * 闭包利用的是函数作用域的特点
            * 因此可以直接使用let
            */
            for(let i = 0; i < btns.length; i++){ // let,在块作用域内有效
              var btn = btns[i]
              btn.onclick = function(){
                alert(i)
              }
            }
        </script>
      • 使用let代替var是趋势

  2. const

    • 作用:定义一个常量

    • 特点

      • 不能修改
      • 其他特点同let
    • 应用

      • 保存不用改变的数据
      const PI = 3.1415926

2、变量(对象)的解构赋值

  • 理解:从对象或数组中提取数据,并赋值给多个变量

  • 将包含多个数据的对象(数组)一次赋值给多个变量

  • 数据源: 对象/数组

  • 目标: {a, b}/[a, b]

  • 对象的解构赋值:let {n, a} = {n:'tom', a:12} 把对象中的值赋值出来(根据属性名key)

  • 数组的解构赋值:let[a, b] = [1, 'luwang'] (根据下标)

  • 用途:给多个形参赋值

    let obj = {
        username: 'luwang',
        age: 23
    }
    // let username = obj.username
    // let age = obj.age
    // console.log(username, age)
    // let {username, age} = obj // 对象,因此需要以对象的形式来接收 只需要一个就写一个,不需要按顺序
    // console.log(username, age)
    let {age} = obj
    console.log(age)
    
    let arr = [1, 3, 5,'abc', true]
    // let [a, b, c, d, e] = arr
    // console.log(a, b, c, d, e)
    // let [a, b] = arr
    // console.log(a, b)
    let [,,a, b] = arr
    console.log(a, b)
    
    function foo({username, age}){ // {username, age} = obj
        console.log(username, age)
    }
    foo(obj)

3、各种数据类型的扩展

  • 字符串

    • 模板字符串
      • 作用: 简化字符串的拼接
      • 模板字符串必须用``,波浪线那个
      • 变化的部分使用${xxx}定义
    let obj = {username: 'luwang', age: 23}
    /*
    * 之前的写法:简单拼串
    * 缺点:可能会拼错,效率低。比如,url携带10个参数,动态拼起来
    */
    let str = 'My name is ' + obj.username + ', age is '+ obj.age
    console.log(str)
    /*
    * ES6提供的模板字符串
    */
    str = `My name is ${obj.username} age is ${obj.age}`
    • includes(str) : 判断是否包含指定的字符串
    • startsWith(str): 判断是否以指定字符串开头
    • endsWith(str) : 判断是否以指定字符串结尾
    • repeat(count): 重复指定次数
    let str = 'asdfghjkklqwrtyuiopzxcvbnm123467890'
    console.log(str.includes('t')) // true
    console.log(str.includes('abc')) // false
    console.log(str.startsWith('a')) // true
    console.log(str.endsWith('0')) // true
    console.log(str.repeat(2)) // asdfghjkklqwrtyuiopzxcvbnm123467890asdfghjkklqwrtyuiopzxcvbnm123467890
  • 数值扩展

    • 二进制与八进制表示法:二进制用0b,八进制用0o
    • Number.isFinite(i):判断是否是有限大的数字
    • Number.isNaN(i):判断是否是NaN
    • Number.isInteger(i):判断是否是整数
    • Number.parseInt(str):将字符串转换为对应的数值
    • Math.trunc(i):直接去除小数部分
    console.log(0b1010)
    console.log(0o12)
    console.log(Number.isFinite(Infinity))
    console.log(Number.isNaN(NaN))
    console.log(Number.isInteger(123.1))
    console.log(Number.isInteger(123.0))
    console.log(Number.parseInt('123abc123')) // 123
    console.log(Number.parseInt('a123abc123')) // NN
    console.log(Math.trunc(123.123)) // 123
  • 对象

    • 简化的对象写法

      • 省略同名的属性值
      • 省略方法的function
      let name = 'Tom';
      let age = 12;
      /* 正常情况 */
      let obj = {
          name: name,
          age: age,
          getName: function(){
              retrun this.name
          }
      }
      console.log(obj)
      /* key和value相同,可以省略 */
      let person = {
          name,  // 同名的属性可以不写
          age,
          setName (name) { // 可以省略函数的function
              this.name = name
          }
      }
    • Object.assign(target, source1, source2..): 将源对象的属性复制到目标对象上

    let obj = {}
    let obj1 = {username:'a', age: 20}
    let obj2 = {sex: '男'}
    // Object.assign(obj, obj1)
    // console.log(obj) // {username: "a", age: 20}
    Object.assign(obj, obj1, obj2)
    console.log(obj) // {username: "a", age: 20, sex: "男"}
    • Object.is(v1, v2) : 判断2个数据是否完全相等
    console.log(0 == -0) // true
    console.log(NaN == NaN) //false
    console.log(Object.is(0, -0)) // false
    console.log(Object.is(NaN, NaN)) // true
    • proto属性 : 隐式原型属性.ES6中能直接操作__proto__属性
    let obj = {}
    let obj1 = {salary: 5000000}
    obj.__proto__ = obj1
    console.log(obj)
    console.log(obj.salary)
  • 数组

    • Array.from(v) : 将伪数组对象或可遍历对象转换为真数组
    • Array.of(v1, v2, v3) : 将一系列值转换成数组
    • find(function(value, index, arr){return true}) : 找出第一个满足条件返回true的元素
    • findIndex(function(value, index, arr){return true}): 找出第一个满足条件返回true的元素下标
    <button>測試1</button><br>
    <button>測試2</button><br>
    <button>測試3</button>
    <script>
      let btns = document.getElementsByTagName('button')
      // 偽數組 不能使用forEach(數組的方法)
      Array.from(btns).forEach(function(item, index){
        console.log(item)
      })
    
      let arr = Array.of(1, 4, 'abc', true)
      console.log(arr)
    
      let arr2 = [2,3,4,2,5,7,3,6]
      console.log(arr2.find(function(item, index){
        return item > 4
      }))
      console.log(arr2.findIndex(function(item, index){
        return item > 4
      }))
    </script>
  • 函数

    • 箭头函数
      • 用来定义匿名函数
      • 基本语法:
        • 没有参数: () => console.log(‘xxxx’) 箭头前的()不能省略
        • 一个参数: i => i+2 可以省略
        • 大于一个参数: (i,j) => i+j ()不能省略
        • 函数体不用大括号: 默认返回结果
        • 函数体如果有多个语句, 需要用{}包围
      • 使用场景: 多用来定义回调函数
      • 特点:
        • 简洁
        • 箭头函数没有自己的this,箭头函数的this不是调用的时候决定的,而是在定义的时候所处的对象就是它的this
        • 扩展理解:箭头函数的this看外层是否有函数
          • 箭头外层有函数,this是外层函数的this
          • 箭头外层无函数,this是window
    let fun = function(){console.log('fun')}
    fun()
    // 1、没有形参
    let fun1 = () => console.log('fun1')
    fun1()
    
    // 2、只有一个形参
    let fun2 = (a) => console.log(a)
    // 可省略() let fun2 = a => console.log(a)
    fun2('aaa')
    
    // 3、两个及两个以上的形参
    let fun3 = (x,y) => console.log(x, y)
    fun3(1, 2)
    
    // I、函数体只有一条语句或表达式,{}可以省略-->会自动返回语句执行的结果或表达式的结果
    let foo = (x, y) => x + y
    // let foo = (x, y) => {return x + y}
    console.log(foo(1, 3))
    
    // II、函数体不止一条语句或者表达式, {}不可以省略
    let foo2 = (x, y) => {
        console.log(x, y)
        return x + y
    }
    console.log(foo2(3, 5))
    
    // 箭头函数的this
    <br/><button id="btn1">按钮1</button><br/><br/>
    <button id="btn2">按钮2</button><br/><br/>
    <button id="btn3">按钮3</button>
    <script>
        let btn1 = document.getElementById('btn1')
        let btn2 = document.getElementById('btn2')
        let btn3 = document.getElementById('btn3')
        btn1.onclick = function(){
          console.log(this) // <button id="btn1">按钮1</button>
        }
        btn2.onclick = () => {
          console.log(this)  // Window
        }
        let obj = {
          name: '箭头函数',
          getName: function(){
            btn3.onclick = () => {
              console.log(this) // {name: "箭头函数", getName: ƒ}
            }
          }
        }
        obj.getName()
        let obj1 = {
          name: '箭头函数',
          getName: () => {
            btn3.onclick = () => {
              console.log(this) // Window
            }
          }
        }
        obj.getName()
    </script>
  • 3点运算符/点点点运算符

    • rest(可变)参数
      • 通过形参左侧的…来表达, 取代arguments的使用
      • 比arguments灵活,只能是最后部分形参参数
        • arguments是伪数组,有length,但是没有数组的一般方法,不能使用forEach遍历
        • callee是arguments的一个属性,等于函数本身,递归的时候可以写为:arguments.callee()
    // arguments
    function foo(a, b){
      console.log(arguments)
      // arguments.callee() 调用自身,相当于foo(参数)
      /* arguments.forEach(function(item, index){ // 会报错,伪数组并没有数组的一般方法
          console.log(item, index)
      }) */
    }
    foo(2,5)

    参数

    // 点点点运算符
    function foo(...value){
      console.log(arguments)
      console.log(value) // 就是一个正常的数组
      value.forEach(function(item, index){
        console.log(item, index)
      })
    }
    foo(2,5)
    
    function foo(a, ...value){// ...value只能放在最后面
      console.log(arguments)
      // arguments.callee()
      console.log(value)    // 使用的时候不用加...
      value.forEach(function(item, index){
        console.log(item, index)
      })
    }
    foo(2, 3, 5, 7) // 最前面的就是a,value就不包括它了
  • 第二种用法——扩展运算符

    • 可以分解出数组或对象中的数据
    let arr = [1, 6]
    let arr1 = [2, 3, 4, 5]
    arr = [1, ...arr1, 6]
    console.log(arr)    // (6) [1, 2, 3, 4, 5, 6]  数组
    console.log(...arr) // 1 2 3 4 5 6  每项值
  • 形参的默认值

    • 定义形参时指定其默认的值
    • 当不传入参数的时候默认使用形参里的默认值
    // 定义一个点的坐标的构造函数
    function Point(x, y){
      this.x = x
      this.y = y
    }
    let point = new Point(50, 20)
    console.log(point) // Point {x: 50, y: 20}
    // 忘记传参
    let point1 = new Point()
    console.log(point1) // Point {x: undefined, y: undefined}
    
    /* 
    * 因此会有需求,在忘记传参的时候使用默认值
    * 在形参的位置赋默认值
    */
    function Point(x = 0, y = 0){
      this.x = x
      this.y = y
    }
    let point = new Point(50, 20)
    console.log(point) // Point {x: 50, y: 20}
    // 忘记传参,使用默认值
    let point1 = new Point()
    console.log(point1) // Point {x: 0, y: 0}

4、深度克隆

  • 拷贝数据:

    • 基本数据类型
      • 拷贝后会生成一份新的数据
      • 修改拷贝以后的数据不会影响原数据
    • 引用数据类型
      • 拷贝后不会生成新的数据,而是拷贝的引用
      • 修改拷贝以后的数据会影响原来的数据
    // 基本數據類型
    let str = 'abcd'
    let str2 = str
    console.log(str, str2)
    str2 = ''
    console.log(str, str2)
    
    // 引用數據類型
    let obj = {username: 'kobe', age:39}
    let obj1 = obj
    console.log(obj, obj1)
    obj1.username = 'wade'
    console.log(obj, obj1)
    let arr = [1,4,{username:'kobe',age:39}]
    let arr2 = arr
    arr2[0] = 'abcd'
    console.log(arr, arr2)
  • 拷贝数据的方法

    • 直接赋值给一个变量 // 浅拷贝(浅克隆)——能影响
    • Object.assign() // 浅拷贝
    let obj = {username: 'kobe'}
    let obj2 = Object.assign(obj)
    console.log(obj, obj2)
    obj2.username = 'wade'
    console.log(obj, obj2)
    • Array.prototype.concat() //浅拷贝
    let arr = [1, 4, {username: 'kobe'}]
    let testArr = ['ce', 'shi']
    // let arr2 = arr.concat(testArr)
    let arr2 = arr.concat()
    console.log(arr, arr2)
    arr2[1] = 'a'
    console.log(arr, arr2)
    arr2[2].username = 'wade'
    console.log(arr, arr2)
    • Array.prototype.slice() // 浅拷贝
    let arr = [1, 4, {username: 'kobe'}]
    let arr2 = arr.slice()
    arr2[2].username = 'wade'
    console.log(arr, arr2)
    • JSON.parse(JSON.stringify()) // 深拷贝(深度克隆)——修改不影响引用类型的原数据。
      • 拷贝的数据里不能有函数(处理不了),先是将数据转为了JSON格式,字符串对应js中的只有对象和数组,没有函数
    let arr = [1, 4, {username: 'kobe'}]
    let arr2 = JSON.stringify(arr)
    arr2 = JSON.parse(arr2)
    arr2[2].username = 'wade'
    console.log(arr, arr2)
  • 浅拷贝

    • 特点:拷贝的是引用,修改拷贝以后的数据会影响原来的数据,使得原数据不安全
  • 深拷贝

    • 特点:拷贝的时候生成新数据,修改拷贝以后的数据不会影响原数据
  • 这两个都是针对对象/数组来说的

  • 思考:如何实现深度拷贝?

    • 拷贝的数据里有对象/数组,即使有对象/数组可以继续遍历对象、数组,拿到里边的每一项值,直到拿到的是基本数据类型,然后再去复制(拷贝的数据里不能有对象/数组)

    • 知识点储备

      • 如何判断数据类型
        1. typeof返回的数据类型: String、Number、boolean、undefined、Object、Function
        2. Object.prototype.toString.call(obj)—>Object.prototype.toString.call(data).slice(8, -1)
      let result = 'abc' // [object String]
      result = null // [object Null]
      result = [1, 'a'] // test-demo.html:17 [object Array]
      // console.log(Object.prototype.toString.call(result))
      // console.log(typeof Object.prototype.toString.call(result)) // 返回的是string
      // 因此可以用下面这种方式显示数据类型
      console.log(Object.prototype.toString.call(result).slice(8, -1))
      
      • for in 循环

        • 用于循环对象,枚举出来的是属性名
        let obj = {username: 'kobe', age: 39}
        for(let i in obj){
          console.log(i) // username age
        }
        • 循环数组时,枚举的是下标
        let arr = [1,3,'abc']
        for(let i in arr){
          console.log(i) // 0 1 2
        }
    • 实现深度克隆

    // 定义检测数据类型的功能函数
    function checkType(target){
      return Object.prototype.toString.call(target).slice(8, -1)
    }
    console.log(checkType([1,2,'a']))
    // 实现深度克隆-->对象/数组
    function clone(target){
      // 1.判断拷贝的数据类型
      let result, targetType = checkType(target)
      if(targetType === 'Object'){
        // 2.初始化数据,对象/数组/其他类型仍不改变
        result = {}
      }else if(targetType === 'Array'){
        result = []
      }else{
        return target
      }
      // 3.遍历目标数据
      for(let i in target){
        // 遍历数据结构的每一项值
        let value = target[i] // key、下标都可以用[]
        // 判断目标结构里的每一值是否存在数组/对象
        if(checkType(value) === 'Object' || checkType(value) === 'Array'){// 对象、数组中还嵌套了对象数组
          // 继续遍历获取到的value值
          result[i] = clone(value)
        }else{ // 获取到的value值是基本数据类型或函数
          result[i] = value
        }
      }
      return result
    }
    let arr = [1,2,{username: 'kobe'}]
    let arr2 = clone(arr)
    console.log(arr, arr2)
    arr2[2].username = 'abdc'
    console.log(arr, arr2)
    
    let obj = {username: 'luwang', age: 23}
    let obj2 = clone(obj)
    console.log(obj, obj2)
    obj2.username = 'LUWANG'
    console.log(obj, obj2)

7、class类

  • 通过class定义类,实现类的继承

    • 回顾:原型、构造函数、构造函数+原型——继承

      function Person(name, age){
        this.name = name
        this.age = age
      }
      let person = new Person('kobe', 39)
      console.log(person)

      继承

  • 在类中通过 constructor() 定义构造方法(相当于构造函数)

    // 定義一個人物的類
    class Person{
     // 类的构造方法
      constructor(name, age){
        this.name = name
        this.age = age
      }
      // 类的一般方法
      showMe(){
        console.log(this.name)
      }
    }
    let person = new Person('kobe', 39)
    console.log(person)
    person.showName()

    继承

  • 一般方法: xxx () {}

  • 用extends来定义子类(实现累的继承)

  • 用super()来调用父类的构造方法

  • 子类方法自定义: 将从父类中继承来的方法重新实现一遍(重写从父类继承的一般方法)

    // 定義一個人物的類
    class Person{
      constructor(name, age){
        this.name = name
        this.age = age
      }
      showMe(){
        console.log('調用父類的方法')
        console.log(this.name, this.age)
      }
    }
    let person = new Person('kobe', 39)
    person.showMe()
    
    // 子類
    class StarPerson extends Person{
      constructor(name, age, salary){
        super(name, age) // 調用父類的構造方法
        this.salary = salary
      }
      showMe(){
        console.log('子類重寫的方法')
        console.log(this.name, this.age, this.salary)
      }
    }
    let p1 = new StarPerson('wade', 36, 10000000)
    console.log(p1)
    p1.showMe()
  • js中没有方法重载(方法名相同, 但参数不同)的语法

4、set/Map容器结构

  • 容器: 能保存多个数据的对象, 同时必须具备操作内部数据的方法

  • 任意对象都可以作为容器使用, 但有的对象不太适合作为容器使用(如函数)

  • Set的特点: 保存多个value, value是不重复 ====>数组元素去重

  • Map的特点: 保存多个key–value, key是不重复, value是可以重复的

  • API

    • Set容器:无序不可重复的多个value的集合体
      • Set()
      • Set(arr) //arr是一维数组
      • add(value)
      • delete(value)
      • clear()
      • has(value)
      • size
      • Map容器:无序的key、不重复的多个key-value的集合体
    • Map()
    • Map(arr)//arr是二维数组
    • set(key, value)
    • get(key)
    • delete(key)
    • clear()
    • has(key)
    • size
    // let set = new Set()
    let set = new Set([1,2,4,5,2,3,6]) // 重复的会去除
    console.log(set)
    set.add(7)
    console.log(set.size, set)
    console.log(set.has(8))
    console.log(set.has(7))
    set.delete(7)
    console.log(set.size, set)
    set.clear()
    console.log(set.size, set)
    
    // let map = new Map()
    let map = new Map([['username', 'aaa'], ['age', 35], ['sex', 'female']]) // 二维数组,且只能有两值(一个是key,一个是value)
    map.set('other', 'shuoming')
    console.log(map.size, map)
    map.delete('other')
    console.log(map)
    console.log(map.has('username'))
    map.clear()
    console.log(map)

5、for–of循环

  • 可以遍历任何容器(Set、Map)
let set = new Set([1, 2, 4, 3, 4, 5]) 
for(let i of set){
  console.log(i)
}

// 可以用Set给数组去重
let arr = [1,2,4,5,5,6,2]
let arr1 = arr
arr = [] // 保留数组类型
let set = new Set(arr1)
for(let i of set){
  arr.push(i)
}
console.log(arr)
  • 数组
  • 对象
  • 伪/类对象
  • 字符串
  • 可迭代的对象

6、Promise对象

  • 理解:

    • Promise对象代表了某个将要发生的事件(通常是一个异步操作)
    • ES6的Promise是一个构造函数,用来生成promise实例
    • 解决回调地狱(回调函数的层层嵌套, 编码是不断向右扩展, 阅读性很差);有了promise对象,可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数(回调地狱)

    • 能以同步编码的方式实现异步调用

    • 在es6之前原生的js中是没这种实现的, 一些第三方框架(jQuery)实现了promise

    • promise对象的3个状态:

      • pending:初始化状态
      • fullfilled:成功状态
      • rejected:失败状态
    • 应用:

      • 使用promise实现超时处理
      • 使用promise封装处理Ajax请求
      let request = new XMLHttpRequest()
      request.responseType = 'json'
      request.open("GET", url)
      request.send()
  • ES6中定义实现API(使用Promise基本步骤):

    // 1. 创建promise对象
    let promise = new Promise((resolve, reject) => { 
          // 初始化promise状态为pending
      // 执行异步操作 
      if(异步操作成功) { // 调用成功的回调
        resolve(result);     // 修改promise状态为fullfilled
      } else { // 调用失败的回调
        reject(errorMsg);   // 修改promise的状态为rejected
      } 
    }) 
    // 2. 调用promise对象的then()
    promise.then(function(
      result => console.log(result), 
      errorMsg => alert(errorMsg)
    ))

    例子:

    // 1、创建promise对象
    let promise = new Promise((resolve, reject) => {
      // 初始化promise状态  pending: 初始化
      console.log('11111111')
      // 执行异步操作,通常是发送Ajax请求,开启定时器
      setTimeout(() => {
        console.log('3333333')
        // 根据异步任务的返回结果去修改promise的状态
        // 异步任务执行成功
        // resolve('哈哈,') // 修改promise的状态为 fullfilled:成功
        // 异步任务执行失败
        reject('555, ') // 修改promise的状态为 rejsected: 失败
      }, 2000)
    })
    console.log('222222222')
    // 2. 调用promise对象的then()
    promise
      .then((data) => { // 成功的回调
        console.log(data, '成功了~~~')
      }, (error) => { // 失败的回调
        console.log(error, '失败了……')
    })

    实例:新闻、新闻的评论:只发新闻的内容;在接着根据新闻的id拿取这个新闻下的评论

    1、打开ES5_6_7中code目录中的es_server

    2、输入命令node bin/www

    3、浏览器中访问 http://localhost:3000/news ,能够获取数据

    // 定义获取新闻的功能函数
    function getNews(url){
      let promise = new Promise((resolve, reject) => {
        // 状态:初始化
        // 执行异步任务
        let xmlHttp = new XMLHttpRequest()
        // 绑定监听readyState
        /*xmlHttp.onreadystatechange = function(){
          if(xmlHttp.readyState === 4 && xmlHttp.status == 200){
            // 请求成功
            console.log(xmlHttp.responseText)
            // 修改状态
            resolve(xmlHttp.responseText) // 修改promise的状态为成功
          }else{
            // 请求失败
            reject('暂时没有新闻内容')
          }
        } --> 逻辑有问题*/
        xmlHttp.onreadystatechange = function(){
          if(xmlHttp.readyState === 4){
            if(xmlHttp.status == 200){
              // 请求成功
              // console.log(xmlHttp.responseText)
              // 修改状态
              resolve(xmlHttp.responseText) // 修改promise的状态为成功
            }else{
              // 请求失败
              reject('暂时没有新闻内容')
            }
          }
        }
    
        // open 设置请求得方式以及url
        xmlHttp.open('GET', url)
        // 发送
        xmlHttp.send()
      })
      return promise
    }
    getNews('http://localhost:3000/news?id=2')
      .then((data) => {
        console.log(data)
        // 发送请求获取评论内容准备url
        let commentsUrl = JSON.parse(data).commentsUrl
        let url = 'http://localhost:3000' + commentsUrl
        // 发送请求
        return getNews(url)
      },(error) => {
        console.log(error)
      })
      .then((data) => {
        console.log(data)
      }, () => {
    
    })

8、Symbol属性

  • 前言:ES5中对象的属性名都是字符串,容易造成重名,污染环境
  • 概念:ES6中添加了一种原始数据类型symbol(已有的数据类型:String、Number、boolean、null、undefined、对象)
  • 特点
    • Symbol属性对应的值是唯一的,解决命名冲突问题
    • Symbol值不能其他数据进行计算,包括与字符串拼串
    • for in、for of遍历时不会遍历symbol属性
  • 使用
    • 调用Symbol函数得到symbol值
    • 传参标识
    • 内置Symbol值
      • 除了定义自己使用的Symbol值以外,ES6还提供了11个内置的Symbol值(查看官方文档)
      • 对象的Symbol.iterator属性,指向该对象的默认遍历器方法
// 创建symbol属性值
let symbol = Symbol()
console.log(symbol)  // Symbol()
let obj = {username:'kobe', age:39}
// 可以添加symbol属性——但是得用另一种方式
obj.gender = '男'
obj[symbol] = 'hello'
console.log(obj)  // {username: "kobe", age: 39, gender: "男", Symbol(): "hello"}

//let symbol2 = Symbol()
//let symbol3 = Symbol()
// 并不相同,值是唯一的
//console.log(symbol2, symbol3, symbol2 == symbol3)  // Symbol() Symbol() false

// 可以传参,这样就能很明显看出不同了
let symbol2 = Symbol('one')
let symbol3 = Symbol('two')
console.log(symbol2, symbol3, symbol2 == symbol3)  // Symbol(one) Symbol(two) false

// 可以用来定义常量
const Person_key = Symbol('person_key')
console.log(Person_key)  // Symbol(person_key)






// 等同于在指定的数据结构上部署了Iterator接口
// 当使用for of去遍历某一个数据结构的时候,首先去找Symbol.itearator,找到了就去遍历,没有找到就不能遍历
let targetData = {
  [Symbol.iterator]: function(){
    let nextIndex = 0
    return{
      next: function(){
        return nextIndex < this.length ? {value: this[nextIndex++], done: false} : {value: undefined, done: true}
      }
    }
  }
}
// 使用三点运算符、解构赋值,默认会去调用Iterator接口
let arr2 = [1,6]
let arr3 = [2,3,4,5]
arr2 = [1,...arr3,6]
console.log(arr2)
let [a,b] = arr2
console.log(a,b)

9、Iterator遍历器

  • 概念:iterator是一种接口机制,为各种不同的数据结构提供统一的访问机制

  • 作用:

    • 为各种数据结构,提供一个统一的、简便的访问接口
    • 使得数据机构的成员能够按照某种次序排列
    • ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费
  • 工作原理

    • 创建一个指针对象(遍历器对象),指向数据结构的起始位置
    • 第一次调用next方法,指针自动指向数据结构的第一个成员
    • 接下来不断调用next方法,指针会一直往后移动,直到指向最后一个成员
    • 没调用next方法返回的是一个包含value和done的对象{value: 当前成员的值, done: 布尔值}
      • value表示当前成员的值,done对应的布尔值表示当前的数据的结构是否遍历结束
      • 当遍历结束的时候返回的value值是undefined,done值为false
  • 原生具备Iterator接口的数据(可用for…of遍历)

  • 扩展理解

    • 当数据结构上部署了Symbol.iterator接口,该数据就是可以用for of遍历
    • 当使用for of去遍历目标数据的时候,该数据会自动去找Symbol.iterator属性(Symbol.iterator属性指向对象的默认遍历器方法)
      • Array
      • arguments
      • set容器
      • map容器
      • String
      • ……
    // 模拟指针对象(遍历器对象)
    function myIterator(arr){// Iterator接口
    let nextIndex = 0 // 记录指针的位置
      return{
        next: function(){// 遍历器对象
          return nextIndex < arr.length ? {value: arr[nextIndex++], done: false} : {value: undefined, done: true}
        }
      }
    }
    // 准备一个数据
    let arr =[1,4,65,'abc']
    
    let iteratorObj = myIterator(arr)
    console.log(iteratorObj.next()) // {value: 1, done: false}
    console.log(iteratorObj.next()) // {value: 4, done: false}
    console.log(iteratorObj.next()) // {value: 65, done: false}
    console.log(iteratorObj.next()) // {value: "abc", done: false}
    console.log(iteratorObj.next()) // {value: undefined, done: true}
    
    // 将iterator接口部署到指定的数据类型上,可以使用for of去循环遍历
    // 数组、字符串、argument、set容器、map容器
    for(let i of arr){
      console.log(i)
    }// 1 4 65 abc
    
    let str = 'abcdefg'
    for(let i of str){
      console.log(i)
    }// a b c d e f g
    
    function fun(){
      for(let i of arguments){
        console.log(i)
      }
    }
    fun(1,4,5,'abc') // 1 4 5 abc
    
    // let obj = {username:'kobe', age: 39}
    // for(let i of obj){
    //   console.log(i)
    // }// Uncaught TypeError: obj is not iterable 不可迭代

10、Generator函数

  • 概念

    • ES6提供的解决异步编程的方案之一
    • Generator函数是一个状态机,内部封装了不同状态的数据
    • 用来生成遍历器对象
    • 可暂停函数(惰性求值),yield可暂停,next方法可启动。每次返回的是yield后的表达式结果
  • 特点

    • function 与函数名之间有一个星号

    • 内部用yield表达式来定义不同的状态

      例如:

      function* generatorExample(){
          let result = yield 'hello'  // 状态值为hello
          yield 'generator'  // 状态值为generator
      }
    • generator函数返回的是指针对象,而不会执行函数内部逻辑

      function* generatorExample(){
          console.log('开始执行')
          let result = yield 'hello'  // 状态值为hello
          yield 'generator'  // 状态值为generator
      }
      generatorExample() // 调用并不会执行函数内部逻辑
    • 调用next方法函数,内部逻辑开始执行,遇到yield表达式停止,返回{value: yield后的表达式结果/return后的返回结果(如果没写,返回undefined),done: boolean值(后面还有返回false,没有返回true)}

      function* generatorExample(){
          console.log('开始执行')
          let result = yield 'hello'  // 状态值为hello,会执行,停止 测试yield console.log('会执行')
          console.log('下次调用next执行')
          yield 'generator'  // 状态值为generator
          console.log('下次调用next执行')
          return '返回的结果'
      }
      let MG = generatorExample() // 返回的是指针对象
      console.log(MG.next()) // 执行,遇到yield停止
      console.log(MG.next('可以拿到这个值')) // 再次调用next,往下执行,可以传参
      console.log(MG.next()) // 再次调用next,往下执行,返回true
    • 再次调用next方法会从上一次停止时的yield处开始,直到最后

    • yield语句返回结果通常为undefined,当调用next方法时传参内容会作为启动时yield语句的返回值

    • 补充

      • 对象的Symbol.iterator属性,指向遍历器对象
      let obj = {username:'kobe', age: 39}
      obj[Symbol.iterator] = function* myTest(){
          yield 1
          yield 2
          yield 3
      }
      for(let i of obj){
        console.log(i)
      }
      • 案例
        • 需求
          • 发送ajax请求获取新闻内容
          • 新闻内容获取成功后再次发送请求,获取对应的新闻评论内容
          • 新闻内容获取失败则不需要再次发送请求
        • 启动服务器——进入es_server目录,cmd输入命令node bin/www
      // 要比使用Promise更好
      function getNews(url){
        $.get(url, function(data){ // 前面引入了jQuery
          console.log(data)
          let url = 'http://localhost:3000' + data.commentsUrl
          SX.next(url) // 放在这里也可以往下移,并且这里参数传输更方便
        })
      }
      function* sendXml(){
        let url = yield getNews('http://localhost:3000/news?id=3') // 如果这里出错,后面评论也不会再执行了
        yield getNews(url)
      }
      // 获取遍历器对象
      let SX = sendXml()
      SX.next()

四、模块化

1、理解

  • 什么是模块?

    • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
    • 块的内部数据/实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信
  • 一个模块的组成

    • 数据—>内部的属性
    • 操作数据的行为—>内部的函数
  • 模块化

    • 编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目
  • 模块化的进化过程

    • 全局function模式 :

      • 编码: 全局变量/函数
      • 问题: 污染全局命名空间, 容易引起命名冲突/数据不安全

全局变量

  • namespace模式 :

    • 编码: 将数据/行为封装到对象中
    • 解决: 命名冲突(减少了全局变量)
    • 问题: 数据不安全(外部可以直接修改模块内部的数据)

Namespace

  • IIFE模式/增强

    • IIFE : 立即调用函数表达式—>匿名函数自调用

    • 编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口

IIFE

  • 引入依赖: 通过函数形参来引入依赖模块

    (function(window, module2){
      var data = 'atguigu.com'
      function foo() {
         module2.xxx()
         console.log('foo()'+data)
      }
      function bar() {
         console.log('bar()'+data)
      }
    
      window.module = {foo}
    })(window, module2)

    IIFE增强

  • 模块化好处

    模块化好处

    • 避免命名冲突(减少命名空间污染)
    • 更好的分离, 按需加载
    • 更高复用性
    • 高可维护性
  • 页面引入加载script

    引入js

    • 问题
      • 请求过多
      • 依赖模糊
      • 难以维护
  • 模块化规范

    • CommonJS

      • Node.js : 服务器端

      • Browserify : 浏览器端 也称为js的打包工具

      • 基本语法:

        • 定义暴露模块 : exports

          exports.xxx = value
          module.exports = value

          引入模块 : require

          var module = require('模块名/模块相对路径')
      • 每个文件都可以当作一个模块

      • 引入模块发生在什么时候?

        • Node : 运行时, 动态同步引入
        • Browserify : 在运行前对模块进行编译/转译/打包的处理(已经将依赖的模块包含进来了),
          运行的是打包生成的js, 运行时不存在需要再从远程引入依赖模块
    • *AMD *: 浏览器端,模块的加载时异步的

      • require.js

      • 基本语法

        • 定义暴露模块: define([依赖模块名], function(){return 模块对象})

        • 引入模块: require([‘模块1’, ‘模块2’, ‘模块3’], function(m1, m2){//使用模块对象})

        • 配置:

          require.config({
            //基本路径
            baseUrl : 'js/',
            //标识名称与路径的映射
            paths : {
              '模块1' : 'modules/模块1',
              '模块2' : 'modules/模块2',
              'angular' : 'libs/angular',
              'angular-messages' : 'libs/angular-messages'
            },
            //非AMD的模块
            shim : {
              'angular' : {
                  exports : 'angular'
              },
              'angular-messages' : {
                  exports : 'angular-messages',
                  deps : ['angular']
              }
            }
          })
    • CMD(通用模块定义) : 浏览器端,模块加载是异步的;模块使用时才会加载执行

      • sea.js

      • 基本语法

        • 定义暴露模块:

          define(function(require, module, exports){
            通过require引入依赖模块
            通过module/exports来暴露模块
            exports.xxx = value
          })
        • 使用模块seajs.use([‘模块1’, ‘模块2’])

    • ES6

      • ES6内置了模块化的实现,依赖模块需要编译打包处理

      • 基本语法

        • 定义暴露模块 : export

          • 暴露一个对象:

            export default 对象
          • 暴露多个:

            export var xxx = value1
            export let yyy = value2
            
            var xxx = value1
            let yyy = value2
            export {xxx, yyy}
        • 引入使用模块 : import

          • default模块:

            import xxx  from '模块路径/模块名'
          • 其它模块

            import {xxx, yyy} from '模块路径/模块名'
            import * as module1 from '模块路径/模块名'
      • 问题: 所有浏览器还不能直接识别ES6模块化的语法

      • 解决:

        • 使用Babel将ES6—>ES5(使用了CommonJS) —-浏览器还不能直接支行
        • 使用Browserify—>打包处理—-浏览器可以运行

2、CommonJS基于服务器端(Node.js)

  • 下载安装Node.js

  • 创建目录结构

    • Modules

      • module1.js
      • module2.js
      • module3.js
    • app.js

    • package.json(命令创建,npm init,再输入包名)

      {
          "name": "conmmonjs_node",
          "version": "1.0.0"
      }
  • 下载第三方模块npm install uniq --save

    • package.json

      {
          "name": "conmmonjs_node",
          "version": "1.0.0",
          "dependencies":{
              "uniq": "^1.0.1"
          }
      }
  • 模块化编码

    • module1.js

      // module.exports = value 暴露一个对象
      module.exports = {
          msg: 'module1',
          foo(){
              console.log(this.msg)
          }
      }
    • module2.js

      // 暴露一个函数 module.exports = function(){}
      module.exports = fuction(){
          console.log("module2")
      }
    • module3.js

      // exports.xxx = value 分别暴露
      exports.foo = function(){
          console.log('foo() module3')
      }
      exports.bar = function(){
          console.log('bar() module3')
      }
      
      exports.arr = [2, 4, 5, 2, 3, 5, 1]
    • app.js

      // 将其他的模块汇集到主模块
      let uniq = require('uniq')
      
      let module1 = require('./modules/module1')
      let module2 = require('./modules/module2')
      let module3 = require('./modules/module3')
      
      module1.foo()
      
      module2()
      
      module3.foo()
      module3.bar()
      
      let result = uniq(module3.arr)
      console.log(result)
  • 通过node运行app.js——node app.js

3、CommonJS基于浏览器端应用(Browserify)

  • 创建项目结构

    • js

      • dist //打包生成文件的目录
      • src //源码所在的目录
        • module1.js
        • module2.js
        • module3.js
        • app.js //应用主源文件
    • index.html

    • package.json

      {
          "name": "browserify-test",
          "version": "1.0.0"
      }
  • 下载browserify

    • 全局:npm install browserify -g

    • 局部:npm install browserify --save-dev

      {
          "name": "browserify-test",
          "version": "1.0.0",
          "devDependencies":{
              "browserify": "^14.5.0"
          }
      }
  • 下载第三方模块npm install uniq --save

    • package.json

      {
          "name": "browserify-test",
          "version": "1.0.0",
          "devDependencies":{
              "browserify": "^14.5.0"
          },
          "dependencies":{
              "uniq": "^1.0.1"
          }
      }
  • 定义模块代码

    • module1.js

      module.exports = {
        foo() {
          console.log('moudle1 foo()')
        }
      }
    • module2.js

      module.exports = function () {
        console.log('module2()')
      }
    • module3.js

      exports.foo = function () {
        console.log('module3 foo()')
      }
      
      exports.bar = function () {
        console.log('module3 bar()')
      }
    • app.js

      //引用模块
      let module1 = require('./module1')
      let module2 = require('./module2')
      let module3 = require('./module3')
      
      let uniq = require('uniq')
      
      //使用模块
      module1.foo()
      module2()
      module3.foo()
      module3.bar()
      
      console.log(uniq([1, 3, 1, 4, 3]))
  • 打包处理js:browserify js/src/app.js -o js/dist/bundle.js

  • 页面使用引入

    • index.html

      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>broserify测试</title>
      </head>
      <body>
      <script src="js/dist/bundle.js"></script>
      </body>
      </html>

4、AMD规范

(1)NoAMD

  • 项目结构

    • js
      • alerter.js
      • dataService.js
    • app.js
    • test1.html
  • 文件内容

    • dataService.js

      // 定义一个没有依赖的模块
      (function(win){ // 形参可以和实参的名字相同,这里方便区分
          let name = 'dataservice.js'
          function getName(){
              return name
          }
          win.dataService = {getName}
      })(window) // 实参
    • alerter.js

      // 定义一个有依赖的模块
      (function(window, dataService){
          let msg = 'alerter.js'
          function showMsg(){
              alert(msg + ',' + dataService.getName())
          }
          window.alerter = {showMsg}
      })(window, dataService) // 依赖dataService
    • app.js

      (function (alerter){
          alerter.showMsg()
      })(alerter)
    • test1.html

      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Modular Demo: 未使用AMD(require.js)</title>
      </head>
      <body>
      <script src="./js/dataService.js"></script> 
      <script src="./js/alerter.js"></script> 
      <script src="./app.js"></script> // 下面的分别依赖上面的模块
      </body>
      </html>

(2)AMD(require.js)

  1. 下载require.js, 并引入

  2. 创建项目结构

  • js
    • libs
      • require.js
    • modules
      • alerter.js
      • dataService.js
    • main.js
  • index.html
  1. 定义require.js的模块代码

    • dataService.js

      // 定义没有依赖的模块
      define(function () {
       let msg = 'dataService.js'
      
       function getMsg() {
         return msg.toUpperCase()
       }
       // 暴露模块
       return {getMsg}
      })
    • alerter.js

      // 定义有依赖的模块
      define(['dataService', 'jquery'], function (dataService, $) {
       let msg = 'alerter.js'
      
       function showMsg() {
         $('body').css('background', 'gray')
         alert(dataService.getMsg() + ', ' + msg)
       }
      
       return {showMsg}
      })
  2. 应用主(入口)js:main.js

    (function () {
        //配置
        require.config({
            //基本路径 出发点在根目录下
            baseUrl: "js/",
            //模块标识名与模块路径映射
            paths: {
                "alerter": "modules/alerter",
                "dataService": "modules/dataService",
            }
        })
        //引入使用模块
        require( ['alerter'], function(alerter) {
            alerter.showMsg()
        })
    })()
  3. 页面使用模块:index.html

<script data-main="js/main" src="js/libs/require.js"></script>

  1. 使用第三方基于require.js的框架(jquery)

    • 将jquery的库文件导入到项目:

      • js/libs/jquery-1.10.1.js
    • 在main.js中配置jquery路径

      paths: {
         'jquery': 'libs/jquery-1.10.1'  // jQuery定义的返回AMD模块名为小写
      }
    • 在alerter.js中使用jquery

      define(['dataService', 'jquery'], function (dataService, $) {
         var name = 'xfzhang'
         function showMsg() {
             $('body').css({background : 'red'})
             alert(name + ' '+dataService.getMsg())
         }
         return {showMsg}
      })

  1. 使用第三方不基于require.js的框架(angular/angular-messages)

    • 将angular.js和angular-messages.js导入项目

      • js/libs/angular.js
      • js/libs/angular-messages.js
    • 在main.js中配置

      (function () {
       require.config({
         //基本路径
         baseUrl: "js/",
         //模块标识名与模块路径映射
         paths: {
           //第三方库
           'jquery' : 'libs/jquery-1.10.1',
           'angular' : 'libs/angular',
           'angular-messages' : 'libs/angular-messages',
           //自定义模块
           "alerter": "modules/alerter",
           "dataService": "modules/dataService"
         },
         /*
          配置不兼容AMD的模块
          exports : 指定导出的模块名
          deps  : 指定所有依赖的模块的数组
          */
         shim: {
           'angular' : {
             exports : 'angular'
           },
           'angular-messages' : {
             exports : 'angular-messages',
             deps : ['angular']
           }
         }
       })
       //引入使用模块
       require( ['alerter', 'angular', 'angular-messages'], function(alerter, angular) {
         alerter.showMsg()
         angular.module('myApp', ['ngMessages'])
         angular.bootstrap(document,["myApp"])
       })
      })()
    • 页面:

      <form name="myForm">
       用户名: <input type="text" name="username" ng-model="username" ng-required="true">
       <div style="color: red;" ng-show="myForm.username.$dirty&&myForm.username.$invalid">用户名是必须的</div>
      </form>

5、CMD规范

  1. 下载sea.js, 并引入

  2. 创建项目结构

    |-js
     |-libs
       |-sea.js
     |-modules
       |-module1.js
       |-module2.js
       |-module3.js
       |-module4.js
       |-main.js
    |-index.html
  3. 定义sea.js的模块代码

    • module1.js

      // 没有依赖的模块
      define(function (require, exports, module) {
       //内部变量数据
       var data = 'atguigu.com'
       //内部函数
       function show() {
         console.log('module1 show() ' + data)
       }
      
       //向外暴露
       exports.show = show
      })
    • module2.js

      define(function (require, exports, module) {
       let msg = 'module2'
       function bar(){
           console.log(msg)
       }
         module.exports = bar
       /* module.exports = {
         msg: 'I Will Back'
       } */
      })
    • module3.js

      define(function(require, exports, module){
         let data = 'module3'
         function fun(){
             console.log(data)
         }
         exports.module3 = {fun}
      })
      /* define(function (require, exports, module) {
       const API_KEY = 'abc123'
       exports.API_KEY = API_KEY
      }) */
    • module4.js

      define(function(require, exports, module){
         let msg = 'module4'
         // 同步引入
         let module2 = require('./module2')
         module2()
         // 异步引入
         require.async('./module3', function(module3){
             module3.module3.fun()
         })
         function fun2(){
             console.log(msg)
         }
         exports.fun2 = fun2
      })
      /*define(function (require, exports, module) {
       //引入依赖模块(同步)
       var module2 = require('./module2')
      
       function show() {
         console.log('module4 show() ' + module2.msg)
       }
      
       exports.show = show
       //引入依赖模块(异步)
       require.async('./module3', function (m3) {
         console.log('异步引入依赖模块3  ' + m3.API_KEY)
       })
      })*/
    • main.js : 主(入口)模块

      define(function (require) {
       let module1 = require('./module1')
       console.log(module1.foo())
       let module4 = require('./module4')
       module4.fun2()
      })
      /*define(function (require) {
       var m1 = require('./module1')
       var m4 = require('./module4')
       m1.show()
       m4.show()
      })*/
  4. index.html:

    <!--
    使用seajs:
     1. 引入sea.js库
     2. 如何定义导出模块 :
       define()
       exports
       module.exports
     3. 如何依赖模块:
       require()
     4. 如何使用模块:
       seajs.use()
    -->
    <script type="text/javascript" src="js/libs/sea.js"></script>
    <script type="text/javascript">
     seajs.use('./js/modules/main')
    </script>

6、ES6-Babel-Browserify使用教程

  1. 定义package.json文件

    {
     "name" : "es6-babel-browserify",
     "version" : "1.0.0"
    }
  2. 安装babel-cli, babel-preset-es2015和browserify // cli——command line interface

    • npm install babel-cli browserify -g
  • npm install babel-preset-es2015 --save-dev
  • preset 预设(将es6转换成es5的所有插件打包)
  1. 定义.babelrc文件(babel run control)

    {
     "presets": ["es2015"]
    }
  2. 编码

    • js/src/module1.js

      // 暴露模块 分别暴露
      export function foo() {
       console.log('module1 foo()');
      }
      export let bar = function () {
       console.log('module1 bar()');
      }
      export const DATA_ARR = [1, 3, 5, 1]
    • js/src/module2.js

      // 统一暴露——常规暴露
      let data = 'module2 data'
      
      function fun1() {
       console.log('module2 fun1() ' + data);
      }
      
      function fun2() {
       console.log('module2 fun2() ' + data);
      }
      
      export {fun1, fun2}
    • js/src/module3.js

      // 默认暴露:可以暴露任意数据类型,暴露什么数据类型接收到的就是什么数据
      // export default value
      export default() =>{
         console.log('我是默认暴露的箭头函数')
      } // 只能暴露一个default,因此暴露对象
      /* export default {
       name: 'Tom',
       setName: function (name) {
         this.name = name
       }
      } */
    • js/src/app.js

      // 引入其他的模块
      // 语法: import xxx from '路径'
      import {foo, bar} from './module1'
      import {DATA_ARR} from './module1'
      import {fun1, fun2} from './module2'
      
      import module3 from './module3'
      /* import person from './module3' */
      
      // 安装 npm install jquery@1
      import $ from 'jquery'
      
      $('body').css('background', 'red')
      
      foo()
      bar()
      console.log(DATA_ARR);
      fun1()
      fun2()
      
      module3()
      /* person.setName('JACK')
      console.log(person.name); */
  3. 编译

    • 使用Babel将ES6编译为ES5代码(但包含CommonJS语法) : babel js/src -d js/lib
    • 使用Browserify编译js : browserify js/lib/app.js -o js/lib/bundle.js
  4. 页面中引入测试

    <script type="text/javascript" src="js/lib/bundle.js"></script>
  5. 引入第三方模块(jQuery)
    1). 下载jQuery模块:

    • npm install jquery@1 --save // @后的是版本,1代表1.x.x中的最新版本

    2). 在app.js中引入并使用

    import $ from 'jquery'
    $('body').css('background', 'red')

五、ES7

1、async函数(源自ES2017)

  • 概念:真正意义上去解决异步回调的问题,同步流程表达异步操作

  • 本质:Generator的语法

  • 语法:

    async function foo(){
        await 异步操作;
        await 异步操作;
    }
  • 特点:

    • 不需要像Generator去调用next方法,遇到await等待,当前的异步操作完成就往下执行
    • 返回的总是Promise对象,可以用then方法进行下一步操作
    • async取代Generator函数的*,await取代Generator的yield
    • 语义上更为明确,使用简单,暂时没有任何副作用
    // async基本使用
    async function foo(){
      return new Promise(resolve => {
        // setTimeout(function(){
        //   resolve()
        // }, 2000)
        // 可以写成下方这种
        setTimeout(resolve, 2000)
      })
    }
    async function test(){
      console.log('开始执行', new Date().toTimeString())
      await foo()
      console.log('执行完毕', new Date().toTimeString())
    }
    test()
    
    

// async里await返回值
function test2(){
return ‘xxx’
}
async function asyncPrint(){
/* let result = await test2()
console.log(result) // 普通函数没有返回值
/
/

let result = await Promise.resolve()
console.log(result) // Promise对象成功状态返回undefined
*/
let result = await Promise.resolve(‘promise’)
console.log(result) // Promise对象成功状态传参返回参数 promise
result = await Promise.reject(‘失败了……’)
console.log(result) // 失败状态,返回出错,且能将参数返回 Uncaught (in promise) 失败了……

}
asyncPrint()


  仍是获取新闻内容案例

  ```javascript
  // async比generator又更简单
  async function getNews(url){
    return new Promise((resolve, reject) => {
      $.ajax({ // 前面已经引入jQuery
        method: 'GET',
        url,  // 这是ES6中简写
        /* success: function(data){
          resolve()
        },
        error: function(error){
          reject() 
        }*/
        // 简写
        success: data => resolve(data),
        error: error => reject(error)

      })
    })
  }
  async function sendXml(){
    let result = await getNews('http://localhost:3000/news?id=7')
    console.log(result) // {id: "7", title: "news title1...", content: "news content1...", commentsUrl: "/comments?newsId=7"}
    result = await getNews('http://localhost:3000' + result.commentsUrl)
    console.log(result)
  }
  sendXml()

改进一下,由于这种写法error并不会显示错误信息

  <script src="./jquery-3.1.0.min.js"></script>
  <script>
    // async比generator又更简单
    async function getNews(url){
      return new Promise((resolve, reject) => {
        $.ajax({
          method: 'GET',
          url,  // 这是ES6中简写
          /* success: function(data){
            resolve()
          },
          error: function(error){
            reject() 
          }*/
          // 简写
          success: data => resolve(data),
          // error: error => reject(error)
          error: error => resolve(false) // 不用reject,而是返回false
        })
      })
    }
    async function sendXml(){
      let result = await getNews('http://localhost:30010/news?id=7')
      console.log(result) // {id: "7", title: "news title1...", content: "news content1...", commentsUrl: "/comments?newsId=7"}
      if(!result){ // 出错就弹窗
        alert('暂时没有新闻……')
      }
      result = await getNews('http://localhost:3000' + result.commentsUrl)
      console.log(result)
    }
    sendXml()
  </script>

2、指数运算符(幂): **

console.log(3 ** 3)

3、Array.prototype.includes(value): 判断数组中是否包含指定value

let arr = [1,2,3,'abc']
console.log(arr.includes('a'))
  • 区别方法的2种称谓
    • 静态(工具)方法
      • Fun.xxx = function(){}
    • 实例方法
      • 所有实例对象 : Fun.prototype.xxx = function(){} //xxx针对Fun的所有实例对象
      • 某个实例对象 : fun.xxx = function(){} //xxx只是针对fun对象
jQuery学习笔记

jQuery学习笔记

  • 初识jQuery
  • jQuery的两把利器
  • 使用jQuery核心函数
  • 使用jQuery对象
  • jQuery对象
  • 练习

一、初识jQuery

1、what

  • http://jquery.com/
  • 一个优秀的JS函数库
  • 使用了jQuery的网站超过90%中大型WEB项目开发首选
  • Write Less, Do More!!!

2、why

  • HTML元素选取(选择器)
  • HTML元素操作
  • CSS操作
  • HTML事件处理
  • JS动画效果
  • 链式调用
  • 读写合一
  • 浏览器兼容
  • 易扩展插件
  • ajax封装
    ……

封装简化DOM操作(CRUD) / Ajax

强大选择器:方便快速查找DOM元素

隐式遍历(迭代):一次操作多个元素

读写合一:读数据/写数据用的是一个函数

事件处理、链式调用、DOM操作(CUD)、样式操作……

3、how

image-20200815145126124

(1) 引入库:本地引入、CDN远程引入,测试版、生产版(压缩版)

(2) 使用库:函数($/jQuery)、对象($xxx)

<!--
需求: 点击"确定"按钮, 提示输入的值
-->
用户名: <input type="text" id="username">
<button id="btn1">确定(原生版)</button>
<button id="btn2">确定(jQuery版)</button>
<!--使用原生DOM-->
<script type="text/javascript">
  window.onload = function () {
    var btn1 = document.getElementById('btn1')
    btn1.onclick = function () {
      var username = document.getElementById('username').value
      alert(username)
    }
  }
</script>
<!--使用jQuery实现-->
  <!--本地引入-->
<script type="text/javascript" src="js/jquery-1.12.3.js"></script>
  <!--远程引入-->
<!--<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.js"></script>-->
<script type="text/javascript">
  //绑定文档加载完成的监听
  jQuery(function () {
    var $btn2 = $('#btn2')
    $btn2.click(function () { // 给btn2绑定点击监听
      var username = $('#username').val()
      alert(username)
    })
  })
  /*
  1. 使用jQuery核心函数: $/jQuery
  2. 使用jQuery核心对象: 执行$()返回的对象
    */
  //新的注释
</script>

二、jQuery的2把利器

  • jQuery函数: $/jQuery
    • jQuery向外暴露的就是jQuery函数, 可以直接使用
    • 当成一般函数使用: $(param)
      • param是function: 相当于window.onload = function(文档加载完成的监听)
      • param是选择器字符串: 查找所有匹配的DOM元素, 返回包含所有DOM元素的jQuery对象
      • param是DOM元素: 将DOM元素对象包装为jQuery对象返回 $(this)
      • param是标签字符串: 创建标签DOM元素对象并包装为jQuery对象返回
    • 当成对象使用: $.xxx
      • each(obj/arr, function(key, value){})
      • trim(str)
  • jQuery对象
    • 包含所有匹配的n个DOM元素的伪数组对象
    • 执行$()返回的就是jQuery对象
    • 基本行为:
      • length/size(): 得到dom元素的个数
      • [index]: 得到指定下标对应的dom元素
      • each(function(index, domEle){}): 遍历所有dom元素
      • index(): 得到当前dom元素在所有兄弟中的下标

1、jQuery核心函数

  • 简称: jQuery函数($/jQuery)

  • jQuery库向外直接暴露的就是$/jQuery

  • 引入jQuery库后, 直接使用$即可

    • 当函数用: $(xxx)
    • 当对象用: $.xxx()
<script type="text/javascript" src="js/jquery-1.12.3.js"></script>
<script type="text/javascript">
  //1.  jQuery函数: 直接可用
  console.log($, typeof $) // f(){}
  console.log(jQuery===$) // true
  /*
  (function (window) {
    var jQuery = function () {
      return new xxx()
    }
    window.$ = window.jQuery = jQuery
  })(window)
  */
</script>

理解

  • $jQuery
  • jQuery定义了这个全局的函数供我们调用
  • 它既可作为一般函数调用, 且传递的参数类型不同/格式不同功能就完全不同
  • 也可作为对象调用其定义好的方法, 此时$就是一个工具对象

作为函数调用

  • 参数为函数 $(fun)
  • 参数为选择器(selector)字符串 $("#div1")
  • 参数为DOM对象 $(div1Ele)
  • 参数为html标签字符串$("<div>")

作为对象使用

  • 发送ajax请求的方法
    $.ajax()
    $.get()
    $.post()
    ……
  • 其它工具方法
    $.each()
    $.trim()
    $.parseJSON()
    ……
<div>
  <button id="btn">测试</button>
  <br/>
  <input type="text" name="msg1"/><br/>
  <input type="text" name="msg2"/><br/>
</div>
<!--
1. 作为一般函数调用: $(param)
    1). 参数为函数 : 当DOM加载完成后,执行此回调函数
    2). 参数为选择器字符串: 查找所有匹配的标签, 并将它们封装成jQuery对象
    3). 参数为DOM对象: 将dom对象封装成jQuery对象
    4). 参数为html标签字符串 (用得少): 创建标签对象并封装成jQuery对象
2. 作为对象使用: $.xxx()
    1). $.each() : 隐式遍历数组
    2). $.trim() : 去除两端的空格
-->
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  /*
   需求1. 点击按钮: 显示按钮的文本, 显示一个新的输入框
   需求2. 遍历输出数组中所有元素值
   需求3. 去掉"  my atguigu  "两端的空格
   */
  /*需求1. 点击按钮: 显示按钮的文本, 显示一个新的输入框*/
  //1.1). 参数为函数 : 当DOM加载完成后,执行此回调函数
  $(function () { // 绑定文档加载完成的监听
    // 1.2). 参数为选择器字符串: 查找所有匹配的标签, 并将它们封装成jQuery对象
    $('#btn').click(function () { // 绑定点击事件监听
      // this是什么? 发生事件的dom元素(<button>)
      // alert(this.innerHTML)
      // 1.3). 参数为DOM对象: 将dom对象封装成jQuery对象
      alert($(this).html())
      // 1.4). 参数为html标签字符串 (用得少): 创建标签对象并封装成jQuery对象
      $('<input type="text" name="msg3"/><br/>').appendTo('div')
    })
  })
  /*需求2. 遍历输出数组中所有元素值*/
  var arr = [2, 4, 7]
  // 1). $.each() : 隐式遍历数组
  $.each(arr, function (index, item) {
    console.log(index, item)
  })
  // 2). $.trim() : 去除两端的空格
  var str = ' my atguigu  '
  // console.log('---'+str.trim()+'---')
  console.log('---'+$.trim(str)+'---')
</script>

2、jQuery核心对象

  • 简称: jQuery对象

  • 得到jQuery对象: 执行jQuery函数返回的就是jQuery对象

  • 使用jQuery对象: $obj.xxx()

<script type="text/javascript" src="js/jquery-1.12.3.js"></script>
<script type="text/javascript">
  //2. jQuery对象: 执行jQuery函数得到它
  console.log($() instanceof Object) // true
</script>

image-20200815155509580

理解

  • 即执行jQuery核心函数返回的对象
  • jQuery对象内部包含的是dom元素对象的伪数组(可能只有一个元素)
  • jQuery对象拥有很多有用的属性和方法, 让程序员能方便的操作dom

属性/方法

  • 基本行为
    size()/length
    [index]/get(index)
    each()
    index()

  • 属性

        操作内部标签的属性或值
  • CSS

        操作标签的样式
  • 文档

        对标签进行增删改操作
  • 筛选

        根据指定的规则过滤内部的标签
  • 事件

        处理事件监听相关
  • 效果

        实现一些动画效果
<button>测试一</button>
<button>测试二</button>
<button id="btn3">测试三</button>
<button>测试四</button>
<!--
1. jQuery对象是一个包含所有匹配的任意多个dom元素的伪数组对象
2. 基本行为
  * size()/length: 包含的DOM元素个数
  * [index]/get(index): 得到对应位置的DOM元素
  * each(): 遍历包含的所有DOM元素
  * index(): 得到在所在兄弟元素中的下标
-->
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  /*
   需求:
   需求1. 统计一共有多少个按钮
   需求2. 取出第2个button的文本
   需求3. 输出所有button标签的文本
   需求4. 输出'测试三'按钮是所有按钮中的第几个
   */
  //需求1. 统计一共有多少个按钮
  var $buttons = $('button')
  /*size()/length: 包含的DOM元素个数*/
  console.log($buttons.size(), $buttons.length)
  //需求2. 取出第2个button的文本
  /*[index]/get(index): 得到对应位置的DOM元素*/
  console.log($buttons[1].innerHTML, $buttons.get(1).innerHTML)
  //需求3. 输出所有button标签的文本
  /*each(): 遍历包含的所有DOM元素*/
  /*$buttons.each(function (index, domEle) {
    console.log(index, domEle.innerHTML, this)
  })*/
  $buttons.each(function () {
    console.log(this.innerHTML)
  })
  //需求4. 输出'测试三'按钮是所有按钮中的第几个
  /*index(): 得到在所在兄弟元素中的下标*/
  console.log($('#btn3').index())  //2
  /*
  1. 伪数组
    * Object对象
    * length属性
    * 数值下标属性
    * 没有数组特别的方法: forEach(), push(), pop(), splice()
   */
  console.log($buttons instanceof Array) // false
  // 自定义一个伪数组
  var weiArr = {}
  weiArr.length = 0
  weiArr[0] = 'atguigu'
  weiArr.length = 1
  weiArr[1] = 123
  weiArr.length = 2
  for (var i = 0; i < weiArr.length; i++) {
    var obj = weiArr[i]
    console.log(i, obj)
  }
  console.log(weiArr.forEach, $buttons.forEach) //undefined, undefined
</script>

三、使用jQuery核心函数

1、选择器

(1) 说明

  • 选择器本身只是一个有特定语法规则的字符串, 没有实质用处
  • 它的基本语法规则使用的就是CSS的选择器语法, 并对基进行了扩展
  • 只有调用$(), 并将选择器作为参数传入才能起作用
  • $(selector)作用 : 根据选择器规则在整个文档中查找所有匹配的标签的数组, 并封装成jQuery对象返回(用来查找特定页面元素)

(2) 分类

  • 基本选择器(最基本最常用的选择器)

    • #id:id选择器
    • element:元素选择器
    • .class:属性选择器
    • *:任意标签
    • selector1,selector2,selectorN:取多个选择器的并集(组合选择器)
    • selector1selector2selectorN:取多个选择器的交集(相交选择器)
    <div id="div1" class="box">div1(class="box")</div>
    <div id="div2" class="box">div2(class="box")</div>
    <div id="div3">div3</div>
    <span class="box">span(class="box")</span>
    <br>
    <ul>
      <li>AAAAA</li>
      <li title="hello">BBBBB(title="hello")</li>
      <li class="box">CCCCC(class="box")</li>
      <li title="hello">DDDDDD(title="hello")</li>
    </ul>
    <script src="js/jquery-1.10.1.js" type="text/javascript"></script>
    <script type="text/javascript">
      /*
       需求:
       1. 选择id为div1的元素
       2. 选择所有的div元素
       3. 选择所有class属性为box的元素
       4. 选择所有的div和span元素
       5. 选择所有class属性为box的div元素
       */
      //1. 选择id为div1的元素
      // $('#div1').css('background', 'red')
      //2. 选择所有的div元素
      // $('div').css('background', 'red')
      //3. 选择所有class属性为box的元素
      //$('.box').css('background', 'red')
      //4. 选择所有的div和span元素
      // $('div,span').css('background', 'red')
      //5. 选择所有class属性为box的div元素
      //$('div.box').css('background', 'red')
      //$('*').css('background', 'red')
    </script>
  • 层次选择器(查找子元素, 后代元素, 兄弟元素的选择器)

    • ancestor descendant:在给定的祖先元素下的后代元素中匹配元素
    • parent > child:在给定的父元素下的子元素中匹配元素
    • prev + next:匹配所有紧接在prev元素后的next元素
    • prev ~ siblings:匹配prev元素之后的所有siblings元素
    <ul>
      <li>AAAAA</li>
      <li class="box">CCCCC</li>
      <li title="hello"><span>BBBBB</span></li>
      <li title="hello"><span class="box">DDDD</span></li>
      <span>EEEEE</span>
    </ul>
    <script src="js/jquery-1.10.1.js" type="text/javascript"></script>
    <script type="text/javascript">
      /*
       需求:
       1. 选中ul下所有的的span
       2. 选中ul下所有的子元素span
       3. 选中class为box的下一个li
       4. 选中ul下的class为box的元素后面的所有兄弟元素
       */
      //1. 选中ul下所有的的span
      // $('ul span').css('background', 'yellow')
      //2. 选中ul下所有的子元素span
      // $('ul>span').css('background', 'yellow')
      //3. 选中class为box的下一个li
      // $('.box+li').css('background', 'yellow')
      //4. 选中ul下的class为box的元素后面的所有兄弟元素
      $('ul .box~*').css('background', 'yellow')
    </script>
  • 过滤选择器(在原有选择器匹配的元素中进一步进行过滤的选择器)

    • 基本
      • :first
      • :last
      • :eq(index)
      • :lt
      • :gt
      • :odd
      • :even
      • :not(selector)
      • header
      • animated
      • focus
    • 内容
      • :contains(text)
      • empty
      • has(selector)
      • parent
    • 可见性
      • :hidden
      • :visible
    • 属性
      • [attrbute]
      • [attrName=value]
      • [attribute!=value]
      • [attribute^=value]
      • [attribute$=value]
      • [attribute*=value]
      • [attrSel1][attrSel2][attrSelN]
    <div id="div1" class="box">class为box的div1</div>
    <div id="div2" class="box">class为box的div2</div>
    <div id="div3">div3</div>
    <span class="box">class为box的span</span>
    <br/>
    <ul>
      <li>AAAAA</li>
      <li title="hello">BBBBB</li>
      <li class="box">CCCCC</li>
      <li title="hello">DDDDDD</li>
      <li title="two">BBBBB</li>
      <li style="display:none">我本来是隐藏的</li>
    </ul>
    <script src="js/jquery-1.10.1.js" type="text/javascript"></script>
    <script type="text/javascript">
    
      /*
       需求:
       1. 选择第一个div
       2. 选择最后一个class为box的元素
       3. 选择所有class属性不为box的div
       4. 选择第二个和第三个li元素
       5. 选择内容为BBBBB的li
       6. 选择隐藏的li
       7. 选择有title属性的li元素
       8. 选择所有属性title为hello的li元素
       */
      //1. 选择第一个div
      // $('div:first').css('background', 'red')
      //2. 选择最后一个class为box的元素
      //$('.box:last').css('background', 'red')
      //3. 选择所有class属性不为box的div
      // $('div:not(.box)').css('background', 'red')  //没有class属性也可以
      //4. 选择第二个和第三个li元素
      // $('li:gt(0):lt(2)').css('background', 'red') // 多个过滤选择器不是同时执行, 而是依次
      //$('li:lt(3):gt(0)').css('background', 'red')
      //5. 选择内容为BBBBB的li
      // $('li:contains("BBBBB")').css('background', 'red')
      //6. 选择隐藏的li
      // console.log($('li:hidden').length, $('li:hidden')[0])
      //7. 选择有title属性的li元素
      // $('li[title]').css('background', 'red')
      //8. 选择所有属性title为hello的li元素
      $('li[title="hello"]').css('background', 'red')
    </script>

练习: 表格隔行变色

image-20200815163732938

<style>
div, span, p {
  width: 140px;
  height: 140px;
  margin: 5px;
  background: #aaa;
  border: #000 1px solid;
  float: left;
  font-size: 17px;
  font-family: Verdana;
}

div.mini {
  width: 55px;
  height: 55px;
  background-color: #aaa;
  font-size: 12px;
}

div.hide {
  display: none;
}

#data {
  width: 600px;
}

#data, td, th {
  border-collapse: collapse;
  border: 1px solid #aaaaaa;
}

th, td {
  height: 28px;
}

#data thead {
  background-color: #333399;
  color: #ffffff;
}

.odd {
  background-color: #ccccff;
}
</style>
<table id="data">
  <thead>
    <tr>
      <th>姓名</th>
      <th>工资</th>
      <th>入职时间</th>
      <th>操作</th>
    </tr>
  </thead>
  <tbody>
  <tr>
    <td>Tom</td>
    <td>$3500</td>
    <td>2010-10-25</td>
    <td><a href="javascript:void(0)">删除</a></td>
  </tr>
  <tr>
    <td>Mary</td>
    <td>$3400</td>
    <td>2010-12-1</td>
    <td><a href="javascript:void(0)">删除</a></td>
  </tr>
  <tr>
    <td>King</td>
    <td>$5900</td>
    <td>2009-08-17</td>
    <td><a href="javascript:void(0)">删除</a></td>
  </tr>
  <tr>
    <td>Scott</td>
    <td>$3800</td>
    <td>2012-11-17</td>
    <td><a href="javascript:void(0)">删除</a></td>
  </tr>
  <tr>
    <td>Smith</td>
    <td>$3100</td>
    <td>2014-01-27</td>
    <td><a href="javascript:void(0)">删除</a></td>
  </tr>
  <tr>
    <td>Allen</td>
    <td>$3700</td>
    <td>2011-12-05</td>
    <td><a href="javascript:void(0)">删除</a></td>
  </tr>
  </tbody>
</table>
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript">
  $('#data>tbody>tr:odd').css('background', '#ccccff')
</script>
  • 表单选择器

    • 表单
      • :input
      • :text
      • :checkbox
      • :radio
    • 表单对象属性
      • :checked: 选中的
    <form>
      用户名: <input type="text"/><br>
      密 码: <input type="password"/><br>
      爱 好:
      <input type="checkbox" checked="checked"/>篮球
      <input type="checkbox"/>足球
      <input type="checkbox" checked="checked"/>羽毛球 <br>
      性 别:
      <input type="radio" name="sex" value='male'/>男
      <input type="radio" name="sex" value='female'/>女<br>
      邮 箱: <input type="text" name="email" disabled="disabled"/><br>
      所在地:
      <select>
        <option value="1">北京</option>
        <option value="2" selected="selected">天津</option>
        <option value="3">河北</option>
      </select><br>
      <input type="submit" value="提交"/>
    </form>
    <script src="js/jquery-1.10.1.js" type="text/javascript"></script>
    <script type="text/javascript">
      /*
       需求:
       1. 选择不可用的文本输入框
       2. 显示选择爱好 的个数
       3. 显示选择的城市名称
       */
      //1. 选择不可用的文本输入框
      // $(':text:disabled').css('background', 'red')
      //2. 显示选择爱好 的个数
      console.log($(':checkbox:checked').length)
      //3. 显示选择的城市名称
      $(':submit').click(function () {
        var city = $('select>option:selected').html() // 选择的option的标签体文本
        city = $('select').val()  // 选择的option的value属性值
        alert(city)
      })
    </script>

2、$工具方法

  1. $.each(): 遍历数组或对象中的数据
  2. $.trim(): 去除字符串两边的空格
  3. $.type(obj): 得到数据的类型
  4. $.isArray(obj): 判断是否是数组
  5. $.isFunction(obj): 判断是否是函数
  6. $.parseJSON(json): 解析json字符串转换为js对象/数组
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  //1. $.each(): 遍历数组或对象中的数据
  var obj = {
    name: 'Tom',
    setName: function (name) {
      this.name = name
    }
  }
  $.each(obj, function (key, value) {
    console.log(key, value)
  })

  //2. $.trim(): 去除字符串两边的空格
  //3. $.type(obj): 得到数据的类型
  console.log($.type($)) // 'function'

  //4. $.isArray(obj): 判断是否是数组
  console.log($.isArray($('body')), $.isArray([])) // false true
  //5. $.isFunction(obj): 判断是否是函数
  console.log($.isFunction($)) // true
  //6. $.parseJSON(json) : 解析json字符串转换为js对象/数组
  var json = '{"name":"Tom", "age":12}'  // json对象: {}
  // json对象===>JS对象
  console.log($.parseJSON(json))
  json = '[{"name":"Tom", "age":12}, {"name":"JACK", "age":13}]' // json数组: []
  // json数组===>JS数组
  console.log($.parseJSON(json))
  /*
  JSON.parse(jsonString)   json字符串--->js对象/数组
  JSON.stringify(jsObj/jsArr)  js对象/数组--->json字符串
  */
</script>

练习:多Tab点击切换

image-20200815163918240

<style>
*{margin:0;padding:0}#tab li{float:left;list-style:none;width:80px;height:40px;line-height:40px;cursor:pointer;text-align:center}#container{position:relative}#content1,#content2,#content3{width:300px;height:100px;padding:30px;position:absolute;top:40px;left:0}#tab1,#content1{background-color:#fc0}#tab2,#content2{background-color:#f0c}#tab3,#content3{background-color:#0cf}
</style>
<h2>多Tab点击切换</h2>
<ul id="tab">
  <li id="tab1" value="1">10元套餐</li>
  <li id="tab2" value="2">30元套餐</li>
  <li id="tab3" value="3">50元包月</li>
</ul>
<div id="container">
  <div id="content1">
    10元套餐详情:<br/>&nbsp;每月套餐内拨打100分钟,超出部分2毛/分钟
  </div>
  <div id="content2" style="display: none">
    30元套餐详情:<br/>&nbsp;每月套餐内拨打300分钟,超出部分1.5毛/分钟
  </div>
  <div id="content3" style="display: none">
    50元包月详情:<br/>&nbsp;每月无限量随心打
  </div>
</div>
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript">
  var $contents = $('#container>div')
  // 给3个li加监听
  /*$('#tab>li').click(function () { // 隐式遍历
    //alert('----')
    // 隐隐藏所有内容div
    $contents.css('display', 'none')
    // 显示对应的内容div
    // 得到当前点击的li在兄弟中下标
    var index = $(this).index()
    // 找到对应的内容div, 并显示
    $contents[index].style.display = 'block'
    // $($contents[index]).css('display', 'block')
  })*/
  // 改进——第二三个div默认none,不需要全部设置隐藏
  var currIndex = 0 //当前显示的内容div的下标
  $('#tab>li').click(function () { // 隐式遍历
    //alert('----')
    // 隐藏当前已经显示的内容div
    $contents[currIndex].style.display = 'none'
    // 显示对应的内容div
      // 得到当前点击的li在兄弟中下标
    var index = $(this).index()
      // 找到对应的内容div, 并显示
    $contents[index].style.display = 'block'
    // 更新下标
    currIndex = index
  })
</script>

3、ajax

  • ajax()

  • get()

  • post

    ……

四、使用jQuery对象

1、属性/文本

操作标签的属性, 标签体文本

操作任意属性

  • attr(name) /attr(name, value): 读写非布尔值的标签属性
  • prop(name) / prop(name, value): 读写布尔值的标签属性
  • removeAttr(name)/removeProp(name): 删除属性

操作class属性

  • addClass(classValue): 添加class
  • removeClass(classValue): 移除指定class

操作HTML代码/文本/值

  • val() / val(value): 读写标签的value
  • html() / html(htmlString): 读写标签体文本
<div id="div1" class="box" title="one">class为box的div1</div>
<div id="div2" class="box" title="two">class为box的div2</div>
<div id="div3">div3</div>
<span class="box">class为box的span</span>
<br/>
<ul>
  <li>AAAAA</li>
  <li title="hello" class="box2">BBBBB</li>
  <li class="box">CCCCC</li>
  <li title="hello">DDDDDD</li>
  <li title="two"><span>BBBBB</span></li>
</ul>

<input type="text" name="username" value="guiguClass"/>
<br>
<input type="checkbox">
<input type="checkbox">
<br>
<button>选中</button>
<button>不选中</button>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  //1. 读取第一个div的title属性
  // console.log($('div:first').attr('title')) // one
  //2. 给所有的div设置name属性(value为atguigu)
  // $('div').attr('name', 'atguigu')
  //3. 移除所有div的title属性
  // $('div').removeAttr('title')
  //4. 给所有的div设置class='guiguClass'
  //$('div').attr('class', 'guiguClass')
  //5. 给所有的div添加class='abc'
  //$('div').addClass('abc')
  //6. 移除所有div的guiguClass的class
  //$('div').removeClass('guiguClass')
  //7. 得到最后一个li的标签体文本
  //console.log($('li:last').html())
  //8. 设置第一个li的标签体为"<h1>mmmmmmmmm</h1>"
  //$('li:first').html('<h1>mmmmmmmmm</h1>')
  //9. 得到输入框中的value值
  //console.log($(':text').val()) // 读取
  //10. 将输入框的值设置为atguigu
  //$(':text').val('atguigu') // 设置      读写合一
  //11. 点击'全选'按钮实现全选
    // attr(): 操作属性值为非布尔值的属性
    // prop(): 专门操作属性值为布尔值的属性
  var $checkboxs = $(':checkbox')
  $('button:first').click(function () {
    $checkboxs.prop('checked', true)
  })
  //12. 点击'全不选'按钮实现全不选
  $('button:last').click(function () {
    $checkboxs.prop('checked', false)
  })
</script>

练习表格隔行变色改进—>odd样式类

image-20200815164643571

2、CSS

(1) style样式(设置CSS样式/读取CSS值)

  • css(styleName): 根据样式名得到对应的值
  • css(styleName, value): 设置一个样式
  • css({多个样式对}): 设置多个样式
<p style="color: blue;">HTML</p>
<p style="color: green;">CSS</p>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  //1. 得到第一个p标签的颜色
  //console.log($('p:first').css('color'))
  //2. 设置所有p标签的文本颜色为red
  //$('p').css('color', 'red')
  //3. 设置第2个p的字体颜色(#ff0011),背景(blue),宽(300px), 高(30px)
  $('p:eq(1)').css({
    color: '#ff0011',
    background: 'blue',
    width: 300,
    height: 30
  })
</script>

(2) 位置坐标(获取/设置标签的位置数据)

  • offset(): 读/写当前元素坐标(原点是页面左上角)
  • position(): 读当前元素坐标(原点是父元素左上角)
  • scrollTop()/scrollLeft(): 读/写元素/页面的滚动条坐标
<style type="text/css">
  * {
    margin: 0px;
  }
  .div1 {
    position: absolute;
    width: 200px;
    height: 200px;
    top: 20px;
    left: 10px;
    background: blue;
  }
  .div2 {
    position: absolute;
    width: 100px;
    height: 100px;
    top: 50px;
    background: red;
  }
  .div3 {
    position: absolute;
    top: 250px;
  }
</style>
<body style="height: 2000px;">
<div class="div1">
  <div class="div2">测试offset</div>
</div>
<div class='div3'>
  <button id="btn1">读取offset和position</button>
  <button id="btn2">设置offset</button>
</div>
<!--
获取/设置标签的位置数据
  * offset(): 相对页面左上角的坐标
  * position(): 相对于父元素左上角的坐标
-->
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  $('#btn1').click(function () {
        // 打印 div1 相对于页面左上角的位置
    var offset = $('.div1').offset()
    console.log(offset.left, offset.top) // 10 20
        // 打印 div2 相对于页面左上角的位置
    offset = $('.div2').offset()
    console.log(offset.left, offset.top) // 10 70
        // 打印 div1 相对于父元素左上角的位置
    var position = $('.div1').position()
    console.log(position.left, position.top) // 10 20
        // 打印 div2 相对于父元素左上角的位置
    position = $('.div2').position()
    console.log(position.left, position.top) // 0 50
  })
  $('#btn2').click(function () {
    // 设置 div2 相对于页面的左上角的位置
    $('.div2').offset({
      left: 50,
      top: 100
    })
  })
</script>
  1. scrollTop(): 读取/设置滚动条的Y坐标

  2. $(document.body).scrollTop()+$(document.documentElement).scrollTop(): 读取页面滚动条的Y坐标(兼容chrome和IE)

  3. $('body,html').scrollTop(60);滚动到指定位置(兼容chrome和IE)

<body style="height: 2000px;">
<div style="border:1px solid black;width:100px;height:150px;overflow:auto">
  This is some text. This is some text. This is some text. This is some text.
  This is some text. This is some text. This is some text. This is some text.
  This is some text. This is some text. This is some text. This is some text.
  This is some text. This is some text. This is some text. This is some text.
  This is some text. This is some text. This is some text. This is some text.
  This is some text. This is some text. This is some text. This is some text.
  his is some text.
</div>
<br>
<br>
<br>
<button id="btn1">得到scrollTop</button>
<button id="btn2">设置scrollTop</button>
<script src="js/jquery-1.10.1.js"></script>
<script>
  //1. 得到div或页面滚动条的坐标
  $('#btn1').click(function () {
    console.log($('div').scrollTop())
    // console.log($('html').scrollTop()+$('body').scrollTop())
    console.log($(document.documentElement).scrollTop()+$(document.body).scrollTop()) // 兼容IE/Chrome
  })
  //2. 让div或页面的滚动条滚动到指定位置
  $('#btn2').click(function () {
    $('div').scrollTop(200)
    $('html,body').scrollTop(300)
  })
</script>
</body>

(3) 尺寸(获取/设置标签的尺寸数据)

image-20200815171020176

  • 内容尺寸

    • height(): height
    • width(): width
  • 内部尺寸

    • innerHeight(): height+padding
    • innerWidth(): width+padding
  • 外部尺寸

    • outerHeight(false/true): height+padding+border 如果是true, 加上margin
    • outerWidth(false/true): width+padding+border 如果是true, 加上margin
div {
  width: 100px;
  height: 150px;
  background: red;
  padding: 10px;
  border: 10px #fbd850 solid;
  margin: 10px;
}
<div>div</div>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script>
  var $div = $('div')
  // 1. 内容尺寸
  console.log($div.width(), $div.height())  // 100 150
  // 2. 内部尺寸
  console.log($div.innerWidth(), $div.innerHeight()) //120 170
  // 3. 外部尺寸
  console.log($div.outerWidth(), $div.outerHeight()) //140 190
  console.log($div.outerWidth(true), $div.outerHeight(true)) //160 210
</script>

3、筛选

(1) 过滤

在jQuery对象中的元素对象数组中过滤出一部分元素来

  1. first()

  2. last()

  3. eq(index|-index)

  4. filter(selector):对当前元素提要求

  5. not(selector):对当前元素提要求, 并取反

  6. has(selector):对子孙元素提要求

<ul>
  <li>AAAAA</li>
  <li title="hello" class="box2">BBBBB</li>
  <li class="box">CCCCC</li>
  <li title="hello">DDDDDD</li>
  <li title="two"><span>BBBBB</span></li>
</ul>
<li>eeeee</li>
<li>EEEEE</li>
<br>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  var $lis = $('ul>li')
  //1. ul下li标签第一个
  // $lis.first().css('background', 'red')
  // $lis[0].style.background = 'red'

  //2. ul下li标签的最后一个
  // $lis.last().css('background', 'red')

  //3. ul下li标签的第二个
  // $lis.eq(1).css('background', 'red')

  //4. ul下li标签中title属性为hello的
  // $lis.filter('[title=hello]').css('background', 'red')

  //5. ul下li标签中title属性不为hello的
  // $lis.not('[title=hello]').css('background', 'red')
  // $lis.filter('[title!=hello]').filter('[title]').css('background', 'red')

  //6. ul下li标签中有span子标签的
  $lis.has('span').css('background', 'red')
</script>

(2) 查找

在已经匹配出的元素集合中根据选择器查找孩子/父母/兄弟标签

  1. children(): 子标签中找

  2. find() : 后代标签中找

  3. parent() : 父标签

  4. prevAll() : 前面所有的兄弟标签

  5. nextAll() : 后面所有的兄弟标签

  6. siblings() : 前后所有的兄弟标签

<div id="div1" class="box" title="one">class为box的div1</div>
<div id="div2" class="box">class为box的div2</div>
<div id="div3">div3</div>
<span class="box">class为box的span</span>
<br/>
<div>
  <ul>
    <span>span文本1</span>
    <li>AAAAA</li>
    <li title="hello" class="box2">BBBBB</li>
    <li class="box" id='cc'>CCCCC</li>
    <li title="hello">DDDDDD</li>
    <li title="two"><span>span文本2</span></li>
    <span>span文本3</span>
  </ul>
  <span>span文本444</span><br>
  <li>eeeee</li>
  <li>EEEEE</li>
  <br>
</div>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  var $ul = $('ul')
  //1. ul标签的第2个span子标签
  //$ul.children('span:eq(1)').css('background', 'red')

  //2. ul标签的第2个span后代标签
  // $ul.find('span:eq(1)').css('background', 'red')

  //3. ul标签的父标签
  // $ul.parent().css('background', 'red')

  //4. id为cc的li标签的前面的所有li标签
  var $li = $('#cc')
  // $li.prevAll('li').css('background', 'red')

  //5. id为cc的li标签的所有兄弟li标签
  $li.siblings('li').css('background', 'red')
</script>

4、文档处理(CUD)

  • 增加
    • append() / appendTo(): 向当前匹配的所有元素内部的最后插入指定内容
    • preppend() / preppendTo(): 向当前匹配的所有元素内部的最前面插入指定内容
    • before(): 将指定内容插入到当前所有匹配元素的前面
    • after(): 将指定内容插入到当前所有匹配元素的后面替换节点
  • 删除
    • remove(): 将自己及内部的孩子都删除
    • empty(): 掏空(自己还在)
  • 更新
    • replaceWith():用指定内容替换所有匹配的标签删除节点
<style type="text/css">
  * {
    margin: 0px;
  }
  .div1 {
    position: absolute;
    width: 200px;
    height: 200px;
    top: 20px;
    left: 10px;
    background: blue;
  }
  .div2 {
    position: absolute;
    width: 100px;
    height: 100px;
    /*top: 50px;*/
    background: red;
  }
  .div3 {
    position: absolute;
    top: 250px;
  }
</style>
<ul id="ul1">
  <li>AAAAA</li>
  <li title="hello">BBBBB</li>
  <li class="box">CCCCC</li>
  <li title="hello">DDDDDD</li>
  <li title="two">EEEEE</li>
  <li>FFFFF</li>
</ul>
<br>
<br>
<ul id="ul2">
  <li>aaa</li>
  <li title="hello">bbb</li>
  <li class="box">ccc</li>
  <li title="hello">ddd</li>
  <li title="two">eee</li>
</ul>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  //1. 向id为ul1的ul下添加一个span(最后)
  var $ul1 = $('#ul1')
  // $ul1.append('<span>append()添加的span</span>')
  $('<span>appendTo()添加的span</span>').appendTo($ul1)

  //2. 向id为ul1的ul下添加一个span(最前)
  // $ul1.prepend('<span>prepend()添加的span</span>')
  $('<span>prependTo()添加的span</span>').prependTo($ul1)

  //3. 在id为ul1的ul下的li(title为hello)的前面添加span
  $ul1.children('li[title=hello]').before('<span>before()添加的span</span>')

  //4. 在id为ul1的ul下的li(title为hello)的后面添加span
  $ul1.children('li[title=hello]').after('<span>after()添加的span</span>')

  //5. 将在id为ul2的ul下的li(title为hello)全部替换为p
  $('#ul2>li[title=hello]').replaceWith('<p>replaceAll()替换的p</p>')
  //6. 移除id为ul2的ul下的所有li
  // $('#ul2').empty()  // <p>也会删除
  $('#ul2>li').remove()
</script>

5、事件

(1) 事件处理

  • 绑定事件

    • eventName(function(){}):绑定对应事件名的监听, 例如:$('#div').click(function(){});

    • on(‘eventName’, function(){}): 通用的绑定事件监听, 例如:$('#div').on('click', function(){})

      • 常用: click, mouseenter/mouseleave mouseover/mouseout focus/blur

      优缺点:

      eventName: 编码方便, 但只能加一个监听, 且有的事件监听不支持

      on: 编码不方便, 可以添加多个监听, 且更通用

  • 解绑事件

    • off(‘eventName’)
<style type="text/css">
  * {
    margin: 0px;
  }
  .out {
    position: absolute;
    width: 200px;
    height: 200px;
    top: 20px;
    left: 10px;
    background: blue;
  }
  .inner {
    position: absolute;
    width: 100px;
    height: 100px;
    top: 50px;
    background: red;
  }
  .divBtn {
    position: absolute;
    top: 250px;
  }
</style>
<body style="height: 2000px;">
<div class="out">
  外部DIV
  <div class="inner">内部div</div>
</div>
<div class='divBtn'>
  <button id="btn1">取消绑定所有事件</button>
  <button id="btn2">取消绑定mouseover事件</button>
  <button id="btn3">测试事件坐标</button>
  <a href="http://www.baidu.com" id="test4">百度一下</a>
</div>
<script src="js/jquery-1.10.1.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
  //1. 给.out绑定点击监听(用两种方法绑定)
  /*$('.out').click(function () {
   console.log('click out')
   })*/
  $('.out').on('click', function () {
    console.log('on click out')
  })

  //2. 给.inner绑定鼠标移入和移出的事件监听(用3种方法绑定)
  /*
   $('.inner')
   .mouseenter(function () { // 进入
    console.log('进入')
   })
   .mouseleave(function () { // 离开
   console.log('离开')
   })
   */
  /*
   $('.inner')
   .on('mouseenter', function () {
   console.log('进入2')
   })
   .on('mouseleave', function () {
   console.log('离开2')
   })
   */
  $('.inner').hover(function () {
    console.log('进入3')
  }, function () {
    console.log('离开3')
  })


  //3. 点击btn1解除.inner上的所有事件监听
  $('#btn1').click(function () {
    $('.inner').off()
  })

  //4. 点击btn2解除.inner上的mouseenter事件
  $('#btn2').click(function () {
    $('.inner').off('mouseenter')
  })

  //5. 点击btn3得到事件坐标
  $('#btn3').click(function (event) { // event事件对象
    console.log(event.offsetX, event.offsetY) // 原点为事件元素的左上角
    console.log(event.clientX, event.clientY) // 原点为窗口的左上角
    console.log(event.pageX, event.pageY) // 原点为页面的左上角
  })

  //6. 点击.inner区域, 外部点击监听不响应
  $('.inner').click(function (event) {
    console.log('click inner')
    //停止事件冒泡
    event.stopPropagation()
  })

  //7. 点击链接, 如果当前时间是偶数不跳转
  $('#test4').click(function (event) {
    if(Date.now()%2===0) {
      event.preventDefault()
    }
  })
</script>

(2) 事件切换

hover(function(){}, function(){}) 同时绑定鼠标移入和移出监听

区别mouseover与mouseenter?

  • mouseover: 在移入子元素时也会触发, 对应mouseout

  • mouseenter: 只在移入当前元素时才触发, 对应mouseleave

​ hover()使用的就是mouseenter()和mouseleave()

区别on(‘eventName’, fun)与eventName(fun)

  • on(‘eventName’, fun): 通用, 但编码麻烦

  • eventName(fun): 编码简单, 但有的事件没有对应的方法

<style type="text/css">
    * {
        margin: 0px;
    }
    .div1 {
        position: absolute;
        width: 200px;
        height: 200px;
        top: 50px;
        left: 10px;
        background: olive;
    }
    .div2 {
        position: absolute;
        width: 100px;
        height: 100px;
        top: 50px;
        background: red;
    }
    .div3 {
        position: absolute;
        width: 200px;
        height: 200px;
        top: 50px;
        left: 230px;
        background: olive;
    }
    .div4 {
        position: absolute;
        width: 100px;
        height: 100px;
        top: 50px;
        background: yellow;
    }
    .divText{
        position: absolute;
        top: 330px;
        left: 10px;
    }
</style>
<div class="divText">
    区分鼠标的事件
</div>
<div class="div1">
    div1.....
    <div class="div2">div2....</div>
</div>
<div class="div3">
    div3.....
    <div class="div4">div4....</div>
</div>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
    $('.div1')
        .mouseover(function () {
            console.log('mouseover 进入')
        })
        .mouseout(function () {
            console.log('mouseout 离开')
        })
    $('.div3')
        .mouseenter(function () {
            console.log('mouseenter 进入')
        })
        .mouseleave(function () {
            console.log('mouseleave 离开')
        })
</script>

(3) 事件委托

  • 理解: 将子元素的事件委托给父辈元素处理
    • 事件监听绑定在父元素上, 但事件发生在子元素上
    •  事件会冒泡到父元素
    • 但最终调用的事件回调函数的是子元素: event.target
  • 好处
    • 新增的元素没有事件监听
    • 减少监听的数量(n==>1)
  • 编码
    • delegate(selector, ‘eventName’, function(event){}) // 回调函数中的this是子元素
    • undelegate(‘eventName’)
<ul>
  <li>11111</li>
  <li>1111111</li>
  <li>111111111</li>
  <li>11111111111</li>
</ul>
<li>22222</li>
<br>
<button id="btn">添加新的li</button>
<br>
<script src="js/jquery-1.10.1.js"></script>
<script>
  /*
   需求:
   1. 点击 li 背景就会变为红色
   2. 点击 btn 就添加一个 li
  */
  $('ul>li').click(function () {
    this.style.background = 'red'
  })

  $('#btn').click(function () {
    $('ul').append('<li>新增的li....</li>')
  })
</script>

引入:绑定事件监听的问题: 新加的元素没有监听

  1. 事件委托(委派/代理):
  • 将多个子元素(li)的事件监听委托给父辈元素(ul)处理

  • 监听回调是加在了父辈元素上

  • 当操作任何一个子元素(li)时, 事件会冒泡到父辈元素(ul)

  • 父辈元素不会直接处理事件, 而是根据event.target得到发生事件的子元素(li), 通过这个子元素调用事件回调函数

  1. 事件委托的2方:
  • 委托方: 业主 li

  • 被委托方: 中介 ul

  1. 使用事件委托的好处
  • 添加新的子元素, 自动有事件响应处理

  • 减少事件监听的数量: n==>1

  1. jQuery的事件委托API
  • 设置事件委托: $(parentSelector).delegate(childrenSelector, eventName, callback)

  • 移除事件委托: $(parentSelector).undelegate(eventName)

<ul>
  <li>1111</li>
  <li>2222</li>
  <li>3333</li>
  <li>4444</li>
</ul>
<li>22222</li>
<br>
<button id="btn1">添加新的li</button>
<button id="btn2">删除ul上的事件委托的监听器</button>
<script src="js/jquery-1.10.1.js"></script>
<script>
  // 设置事件委托
  $('ul').delegate('li', 'click', function () {
    // console.log(this)
    this.style.background = 'red'
  })
  $('#btn1').click(function () {
    $('ul').append('<li>新增的li....</li>')
  })
  $('#btn2').click(function () {
    // 移除事件委托
    $('ul').undelegate('click')
  })
</script>
  • 事件坐标

    • event.offsetX/event.offsetY: 原点是当前元素左上角(相对于事件元素左上角)

    • event.clientX/event.clientY: 原点是窗口左上角(相对于视口的左上角)

    • event.pageX/event.pageY: 原点是页面左上角(相对于页面的左上角)

      image-20200815173852046

  • 事件相关处理

    • 停止事件冒泡: event.stopPropagation()
    • 阻止事件的默认行为: event.preventDefault()

6、动画效果

在一定的时间内, 不断改变元素样式

(1) 滑动动画(不断改变元素的高度来实现的)

  • slideDown():带动画的展开
  • slideUp():带动画的收缩
  • slideToggle():带动画的切换展开/收缩
<style type="text/css">
  * {
    margin: 0px;
  }
  .div1 {
    position: absolute;
    width: 200px;
    height: 200px;
    top: 50px;
    left: 10px;
    background: red;
  }
</style>
<body>
<button id="btn1">慢慢收缩</button>
<button id="btn2">慢慢展开</button>
<button id="btn3">收缩/展开切换</button>
<div class="div1">
</div>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  /*
   需求:
   1. 点击btn1, 向上滑动
   2. 点击btn2, 向下滑动
   3. 点击btn3, 向上/向下切换
   */
  var $div1 = $('.div1')
  // 1. 点击btn1, 向上滑动
  $('#btn1').click(function () {
    $div1.slideUp(3000)
  })
  // 2. 点击btn2, 向下滑动
  $('#btn2').click(function () {
    $div1.slideDown()
  })
  // 3. 点击btn3, 向上/向下切换
  $('#btn3').click(function () {
    $div1.slideToggle()
  })
</script>

(2) 淡入淡出动画(不断改变元素的透明度来实现的)

  • fadeIn():带动画的显示
  • fadeOut():带动画隐藏
  • fadeToggle():带动画切换显示/隐藏
<style type="text/css">
  * {
    margin: 0px;
  }
  .div1 {
    position: absolute;
    width: 200px;
    height: 200px;
    top: 50px;
    left: 10px;
    background: red;
  }
</style>
<body>
<button id="btn1">慢慢淡出</button>
<button id="btn2">慢慢淡入</button>
<button id="btn3">淡出/淡入切换</button>
<div class="div1">
</div>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  /*
   需求:
   1. 点击btn1, 慢慢淡出
     * 无参
     * 有参
       * 字符串参数
       * 数字参数
   2. 点击btn3, 慢慢淡入
   3. 点击btn3, 淡出/淡入切换,动画结束时提示“动画结束了”
   */
  var $div1 = $('.div1')
  $('#btn1').click(function () {
    // $div1.fadeOut()
    // $div1.fadeOut('slow')
    $div1.fadeOut(1000, function () {
      alert('动画完成了!!!')
    })
  })
  $('#btn2').click(function () {
    $div1.fadeIn()
  })
  $('#btn3').click(function () {
    $div1.fadeToggle()
  })
</script>

(3) 显示/隐藏动画(不断改变元素的尺寸和透明度来实现)

  • show():(不)带动画的显示
  • hide():(不)带动画的隐藏
  • toggle():(不)带动画的切换显示/隐藏
<style type="text/css">
  * {
    margin: 0px;
  }
  .div1 {
    position: absolute;
    width: 200px;
    height: 200px;
    top: 50px;
    left: 10px;
    background: red;
    display: none;
  }
</style>
<body>
<button id="btn1">瞬间显示</button>
<button id="btn2">慢慢显示</button>
<button id="btn3">慢慢隐藏</button>
<button id="btn4">显示隐藏切换</button>
<div class="div1">
</div>
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  /*
  需求:
  1. 点击btn1, 立即显示
  2. 点击btn2, 慢慢显示
  3. 点击btn3, 慢慢隐藏
  4. 点击btn4, 切换显示/隐藏
   */
  var $div1 = $('.div1')
  //1. 点击btn1, 立即显示
  $('#btn1').click(function () {
    $div1.show()
  })
  //2. 点击btn2, 慢慢显示
  $('#btn2').click(function () {
    $div1.show(1000)
  })
  //3. 点击btn3, 慢慢隐藏
  $('#btn3').click(function () {
    $div1.hide(1000)
  })
  //4. 点击btn4, 切换显示/隐藏
  $('#btn4').click(function () {
    $div1.toggle(1000)
  })
</script>

(4) 自定义动画

  • animate({结束时的样式}, time, fun):自定义动画效果的动画
  • stop():停止动画
<style type="text/css">
  * {
    margin: 0px;
  }

  .div1 {
    position: absolute;
    width: 100px;
    height: 100px;
    top: 50px;
    left: 300px;
    background: red;
  }
</style>
</head>
<body>
<button id="btn1">逐渐扩大</button>
<button id="btn2">移动到指定位置</button>
<button id="btn3">移动指定距离</button>
<button id="btn4">停止动画</button>

<div class="div1">
  花满田间,月照人影
</div>
<!--
jQuery动画本质 : 在指定时间内不断改变元素样式值来实现的
1. animate(): 自定义动画效果的动画
2. stop(): 停止动画
-->
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript">
  /*
   需求:
    1. 逐渐扩大
      1). 宽/高都扩为200px
      2). 宽先扩为200px, 高后扩为200px
    2. 移动到指定位置
      1).移动到(500, 100)处
      2).移动到(100, 20)处
    3.移动指定的距离
      1). 移动距离为(100, 50)
      2). 移动距离为(-100, -20)
    4. 停止动画
   */
  var $div1 = $('.div1')

  /*
   1. 逐渐扩大
     1). 宽/高都扩为200px
     2). 宽先扩为200px, 高后扩为200px
   */
  $('#btn1').click(function () {
    /*
    $div1.animate({
      width: 200,
      height: 200
    }, 1000)
    */
    $div1
      .animate({
        width: 200
      }, 1000)
      .animate({
        height: 200
      }, 1000)
  })
  /*
   2. 移动到指定位置
     1).移动到(500, 100)处
     2).移动到(100, 20)处
   */
  $('#btn2').click(function () {
    // 1).移动到(500, 100)处
    /*
    $div1.animate({ // 向右下移动
      left: 500,
      top: 100
    }, 1000)
    */

    // 2).移动到(100, 20)处
    $div1.animate({ // 向左上移动
      left: 100,  // 300
      top: 20  // 50
    }, 1000)
  })
  /*
   3.移动指定的距离
     1). 移动距离为(100, 50)
     2). 移动距离为(-100, -20)
   */
  $('#btn3').click(function () {
    // 1). 移动距离为(100, 50)
    /*$div1.animate({
      left: '+=100',
      top: '+=50'
    }, 1000)*/
    // 2). 移动距离为(-100, -20)
    $div1.animate({
      left: '-=100',
      top: '-=20'
    }, 3000)
  })
  $('#btn4').click(function () {
    $div1.stop()
  })
</script>

jQuery对象使用特点

  • 链式调用:调用jQuery对象的任何方法后返回的还是当前jQuery对象
$('.div1')
        .mouseover(function () {
            console.log('mouseover 进入')
        })
        .mouseout(function () {
            console.log('mouseout 离开')
        })
  • 读写合一
    • 读: 内部第一个dom元素
    • 写: 内部所有的dom元素

五、jQuery插件

1、扩展插件

  • 扩展jQuery的工具方法
$.extend({
  xxx: fuction () {} // this是$
})
$.xxx()
  • 扩展jQuery对象的方法
$.fn.extend({
  xxx: function(){}  // this是jQuery对象
})
$obj.xxx()
<style type="text/css">
  * {
    margin: 0px;
  }
  .div1 {
    position: absolute;
    width: 100px;
    height: 100px;
    top: 50px;
    left: 10px;
    background: red;
  }
</style>
<input type="checkbox" name="items" value="足球"/>足球
<input type="checkbox" name="items" value="篮球"/>篮球
<input type="checkbox" name="items" value="羽毛球"/>羽毛球
<input type="checkbox" name="items" value="乒乓球"/>乒乓球
<br/>
<input type="button" id="checkedAllBtn" value="全 选"/>
<input type="button" id="checkedNoBtn" value="全不选"/>
<input type="button" id="reverseCheckedBtn" value="反选"/>
<!--
1. 扩展jQuery的工具方法
  $.extend(object)
2. 扩展jQuery对象的方法
  $.fn.extend(object)
-->
<script src="js/jquery-1.10.1.js" type="text/javascript"></script>
<script type="text/javascript" src="js/my_jQuery-plugin.js"></script>
<script type="text/javascript">
  /*
   需求:
   1. 给 $ 添加4个工具方法:
     * min(a, b) : 返回较小的值
     * max(c, d) : 返回较大的值
     * leftTrim() : 去掉字符串左边的空格
     * rightTrim() : 去掉字符串右边的空格
   2. 给jQuery对象 添加3个功能方法:
     * checkAll() : 全选
     * unCheckAll() : 全不选
     * reverseCheck() : 全反选
   */
  console.log($.min(3, 5), $.max(3, 5))
  var string = '   my atguigu    '
  console.log('-----' + $.leftTrim(string) + '-----')
  console.log('-----' + $.rightTrim(string) + '-----')
  var $items = $(':checkbox[name=items]')
  $('#checkedAllBtn').click(function () {
    $items.checkAll()
  })
  $('#checkedNoBtn').click(function () {
    $items.unCheckAll()
  })
  $('#reverseCheckedBtn').click(function () {
    $items.reverseCheck()
  })
</script>

2、jQuery插件

六、其他

1、多库共存

<style type="text/css">
  * {
    margin: 0px;
  }
  .div1 {
    position: absolute;
    width: 100px;
    height: 100px;
    top: 50px;
    left: 10px;
    background: red;
  }
</style>
</head>
<body>
<!--
问题 : 如果有2个库都有$, 就存在冲突
解决 : jQuery库可以释放$的使用权, 让另一个库可以正常使用, 此时jQuery库只能使用jQuery了
API : jQuery.noConflict()
-->
<script type="text/javascript" src="js/myLib.js"></script>
<script type="text/javascript" src="js/jquery-1.10.1.js"></script>
<script type="text/javascript">
  // 释放$的使用权
  jQuery.noConflict()
  // 调用myLib中的$
  $()
  // 要想使用jQuery的功能, 只能使用jQuery
  jQuery(function () {
    console.log('文档加载完成')
  })
</script>

2、jQuery中的$(function(){})

<h1>测试window.onload与$(document).ready()</h1>
<img id="logo" src="https://gss0.bdstatic.com/5bVWsj_p_tVS5dKfpU_Y_D3/res/r/image/2017-05-19/6fec71d56242b74eb24b4ac80b817eac.png">
<!--
区别: window.onload与 $(document).ready()
  * window.onload
    * 包括页面的图片加载完后才会回调(晚)
    * 只能有一个监听回调
  * $(document).ready()
    * 等同于: $(function(){})
    * 页面加载完就回调(早)
    * 可以有多个监听回调
-->
<script type="text/javascript" src="js/jquery-1.10.1.js"></script>
<script type="text/javascript">
  /*
   需求:
   1. 直接打印img的宽度,观察其值
   2. 在 $(function(){}) 中 打印 img 的宽度
   3. 在 window.onload 中打印宽度
   4. 在 img 加载完成后打印宽度
   */
  // 1. 直接打印img的宽度,观察其值
  console.log('直接', $('#logo').width())
  window.onload = function () {
    console.log('onload', $('#logo').width())
  }
  window.onload = function () {
    console.log('onload2', $('#logo').width())
  }
  $(function () {
    console.log('ready', $('#logo').width())
  })
  $(function () {
    console.log('ready2', $('#logo').width())
  })
  $('#logo').on('load', function () {
    console.log('img load', $(this).width())
  })
  /*$(document).ready(function () {

  })*/
</script>

3、练习

以前用原生js实现过的用jQuery来一遍

(1) 爱好选择器

image-20200815180657634

<form>
  你爱好的运动是?<input type="checkbox" id="checkedAllBox"/>全选/全不选

  <br/>
  <input type="checkbox" name="items" value="足球"/>足球
  <input type="checkbox" name="items" value="篮球"/>篮球
  <input type="checkbox" name="items" value="羽毛球"/>羽毛球
  <input type="checkbox" name="items" value="乒乓球"/>乒乓球
  <br/>
  <input type="button" id="checkedAllBtn" value="全 选"/>
  <input type="button" id="checkedNoBtn" value="全不选"/>
  <input type="button" id="checkedRevBtn" value="反 选"/>
  <input type="button" id="sendBtn" value="提 交"/>
</form>

<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript">
  /*
   功能说明:
   1. 点击'全选': 选中所有爱好
   2. 点击'全不选': 所有爱好都不勾选
   3. 点击'反选': 改变所有爱好的勾选状态
   4. 点击'提交': 提示所有勾选的爱好
   5. 点击'全选/全不选': 选中所有爱好, 或者全不选中
   6. 点击某个爱好时, 必要时更新'全选/全不选'的选中状态
   */
  var $checkedAllBox = $('#checkedAllBox')
  var $items = $(':checkbox[name=items]')

  // 1. 点击'全选': 选中所有爱好
  $('#checkedAllBtn').click(function () {
    $items.prop('checked', true)
    $checkedAllBox.prop('checked', true)
  })

  // 2. 点击'全不选': 所有爱好都不勾选
  $('#checkedNoBtn').click(function () {
    $items.prop('checked', false)
    $checkedAllBox.prop('checked', false)
  })

  // 3. 点击'反选': 改变所有爱好的勾选状态
  $('#checkedRevBtn').click(function () {
    $items.each(function () {
      this.checked = !this.checked
    })
    $checkedAllBox.prop('checked', $items.filter(':not(:checked)').length===0)
  })

  //4. 点击'提交': 提示所有勾选的爱好
  $('#sendBtn').click(function () {
    $items.filter(':checked').each(function () {
      alert(this.value)
    })
  })

  // 5. 点击'全选/全不选': 选中所有爱好, 或者全不选中
  $checkedAllBox.click(function () {
    $items.prop('checked', this.checked)
  })

  // 6. 点击某个爱好时, 必要时更新'全选/全不选'的选中状态
  $items.click(function () {
    $checkedAllBox.prop('checked', $items.filter(':not(:checked)').length===0)
  })
</script>

(2) 增删员工记录

image-20200815180841962

<style>
#total{width:450px;margin-left:auto;margin-right:auto}ul{list-style-type:none}li{border-style:solid;border-width:1px;padding:5px;margin:5px;background-color:#9f9;float:left}.inner{width:400px;border-style:solid;border-width:1px;margin:10px;padding:10px;float:left}#employeeTable{border-spacing:1px;background-color:black;margin:80px auto 10px auto}th,td{background-color:white}#formDiv{width:250px;border-style:solid;border-width:1px;margin:50px auto 10px auto;padding:10px}#formDiv input{width:100%}.word{width:40px}.inp{width:200px}#employeeTable,#employeeTable th,#employeeTable td{border:1px solid;border-spacing:0}
</style>
<table id="employeeTable">
  <tr>
    <th>Name</th>
    <th>Email</th>
    <th>Salary</th>
    <th>&nbsp;</th>
  </tr>
  <tr>
    <td>Tom</td>
    <td>[email protected]</td>
    <td>5000</td>
    <td><a href="deleteEmp?id=001">Delete</a></td>
  </tr>
  <tr>
    <td>Jerry</td>
    <td>[email protected]</td>
    <td>8000</td>
    <td><a href="deleteEmp?id=002">Delete</a></td>
  </tr>
  <tr>
    <td>Bob</td>
    <td>[email protected]</td>
    <td>10000</td>
    <td><a href="deleteEmp?id=003">Delete</a></td>
  </tr>
</table>
<div id="formDiv">
  <h4>添加新员工</h4>
  <table>
    <tr>
      <td class="word">name:</td>
      <td class="inp">
        <input type="text" name="empName" id="empName"/>
      </td>
    </tr>
    <tr>
      <td class="word">email:</td>
      <td class="inp">
        <input type="text" name="email" id="email"/>
      </td>
    </tr>
    <tr>
      <td class="word">salary:</td>
      <td class="inp">
        <input type="text" name="salary" id="salary"/>
      </td>
    </tr>
    <tr>
      <td colspan="2" align="center">
        <button id="addEmpButton" value="abc">
          Submit
        </button>
      </td>
    </tr>
  </table>
</div>

初级版本

<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript">
  /*
  1. 添加
  2. 删除
   */
  $('#addEmpButton').click(function () {
    //1. 收集输入的数据
    var $empName = $('#empName')
    var $email = $('#email')
    var $salary = $('#salary')
    var empName = $empName.val()
    var email = $email.val()
    var salary = $salary.val()

    //2. 生成对应的<tr>标签结构, 并插入#employeeTable的tbody中
    /*
     <tr>
       <td>Bob</td>
       <td>[email protected]</td>
       <td>10000</td>
       <td><a href="deleteEmp?id=003">Delete</a></td>
     </tr>
     */
    var $xxx = $('<tr></tr>')
      .append('<td>'+empName+'</td>') // 拼串
      .append('<td>'+email+'</td>')
      .append('<td>'+salary+'</td>')
      .append('<td><a href="deleteEmp?id="'+Date.now()+'>Delete</a></td>')
      .appendTo('#employeeTable>tbody')
      .find('a')
      .click(clickDelete)

    //3. 清除输入
    $empName.val('')
    $email.val('')
    $salary.val('')
  })

  // 给所有删除链接绑定点击监听
  $('#employeeTable a').click(clickDelete)

  /*
  点击删除的回调函数
   */
  function clickDelete () {
    var $tr = $(this).parent().parent()
    var name = $tr.children(':first').html()
    if(confirm('确定删除'+name+'吗?')) {
      $tr.remove()
    }

    return false
  }

</script>

进阶版本

<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript">
  /*
  1. 添加
  2. 删除
   */
  $('#addEmpButton').click(function () {
    //1. 收集输入的数据
    var $empName = $('#empName')
    var $email = $('#email')
    var $salary = $('#salary')
    var empName = $empName.val()
    var email = $email.val()
    var salary = $salary.val()

    //2. 生成对应的<tr>标签结构, 并插入#employeeTable的tbody中
    /*
     <tr>
       <td>Bob</td>
       <td>[email protected]</td>
       <td>10000</td>
       <td><a href="deleteEmp?id=003">Delete</a></td>
     </tr>
     */
    var $xxx = $('<tr></tr>')
      .append('<td>'+empName+'</td>') // 拼串
      .append('<td>'+email+'</td>')
      .append('<td>'+salary+'</td>')
      .append('<td><a href="deleteEmp?id="'+Date.now()+'>Delete</a></td>')
      .appendTo('#employeeTable>tbody')

    //3. 清除输入
    $empName.val('')
    $email.val('')
    $salary.val('')
  })

  // 通过table实现对所有a的click事件委托
  $('#employeeTable').delegate('a', 'click', clickDelete)

  /*
  点击删除的回调函数
   */
  function clickDelete () {
    var $tr = $(this).parent().parent()
    var name = $tr.children(':first').html()
    if(confirm('确定删除'+name+'吗?')) {
      $tr.remove()
    }
    return false
  }
</script>

(3) 轮播图

<style type="text/css">
/*去除内边距,没有链接下划线*/
* {
  margin: 0;
  padding: 0;
  text-decoration: none;
}
/*让<body有20px的内边距*/
body {
  padding: 20px;
}
/*最外围的div*/
#container {
width: 600px;
height: 400px;
overflow: hidden;
position: relative; /*相对定位*/
margin: 0 auto;
}
/*包含所有图片的<div>*/
#list {
width: 4200px; /*7张图片的宽: 7*600 */
height: 400px;
position: absolute; /*绝对定位*/
z-index: 1;
}
/*所有的图片<img>*/
#list img {
float: left; /*浮在左侧*/
}
/*包含所有圆点按钮的<div>*/
#pointsDiv {
position: absolute;
height: 10px;
width: 100px;
z-index: 2;
bottom: 20px;
left: 250px;
}
/*所有的圆点<span>*/
#pointsDiv span {
cursor: pointer;
float: left;
border: 1px solid #fff;
width: 10px;
height: 10px;
border-radius: 50%;
background: #333;
margin-right: 5px;
}
/*第一个<span>*/
#pointsDiv .on {
background: orangered;
}
/*切换图标<a>*/
.arrow {
  cursor: pointer;
  display: none;
  line-height: 39px;
  text-align: center;
  font-size: 36px;
  font-weight: bold;
  width: 40px;
  height: 40px;
  position: absolute;
  z-index: 2;
  top: 180px;
  background-color: RGBA(0, 0, 0, 0.3);
  color: #fff;
}
/*鼠标移到切换图标上时*/
.arrow:hover {
  background-color: RGBA(0, 0, 0, 0.7);
}
/*鼠标移到整个div区域时*/
#container:hover .arrow {
display: block; /*显示*/
}
/*上一个切换图标的左外边距*/
#prev {
left: 20px;
}
/*下一个切换图标的右外边距*/
#next {
right: 20px;
}
</style>
<div id="container">
  <div id="list" style="left: -600px;">
    <img src="img/5.jpg" alt="5"/>
    <img src="img/1.jpg" alt="1"/>
    <img src="img/2.jpg" alt="2"/>
    <img src="img/3.jpg" alt="3"/>
    <img src="img/4.jpg" alt="4"/>
    <img src="img/5.jpg" alt="5"/>
    <img src="img/1.jpg" alt="1"/>
  </div>
  <div id="pointsDiv">
    <span index="1" class="on"></span>
    <span index="2"></span>
    <span index="3"></span>
    <span index="4"></span>
    <span index="5"></span>
  </div>
  <a href="javascript:;" id="prev" class="arrow">&lt;</a>
  <a href="javascript:;" id="next" class="arrow">&gt;</a>
</div>
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script>
  /*
 功能说明:
 1. 点击向右(左)的图标, 平滑切换到下(上)一页
 2. 无限循环切换: 第一页的上一页为最后页, 最后一页的下一页是第一页
 3. 每隔3s自动滑动到下一页
 4. 当鼠标进入图片区域时, 自动切换停止, 当鼠标离开后,又开始自动切换
 5. 切换页面时, 下面的圆点也同步更新
 6. 点击圆点图标切换到对应的页

 bug: 快速点击时, 翻页不正常
 */
$(function () {

  var $container = $('#container')
  var $list = $('#list')
  var $points = $('#pointsDiv>span')
  var $prev = $('#prev')
  var $next = $('#next')
  var PAGE_WIDTH = 600 //一页的宽度
  var TIME = 400 // 翻页的持续时间
  var ITEM_TIME = 20 // 单元移动的间隔时间
  var imgCount = $points.length
  var index = 0 //当前下标
  var moving = false // 标识是否正在翻页(默认没有)


  // 1. 点击向右(左)的图标, 平滑切换到下(上)一页
  $next.click(function () {
    // 平滑翻到下一页
    nextPage(true)
  })
  $prev.click(function () {
    // 平滑翻到上一页
    nextPage(false)
  })

  // 3. 每隔3s自动滑动到下一页
  var intervalId = setInterval(function () {
    nextPage(true)
  }, 1000)

  // 4. 当鼠标进入图片区域时, 自动切换停止, 当鼠标离开后,又开始自动切换
  $container.hover(function () {
    // 清除定时器
    clearInterval(intervalId)
  }, function () {
    intervalId = setInterval(function () {
      nextPage(true)
    }, 1000)
  })

  // 6. 点击圆点图标切换到对应的页
  $points.click(function () {
    // 目标页的下标
    var targetIndex = $(this).index()
    // 只有当点击的不是当前页的圆点时才翻页
    if(targetIndex!=index) {
      nextPage(targetIndex)
    }
  })

  /**
   * 平滑翻页
   * @param next
   * true: 下一页
   * false: 上一页
   * 数值: 指定下标页
   */
  function nextPage (next) {
    /*
      总的时间: TIME=400
      单元移动的间隔时间: ITEM_TIME = 20
      总的偏移量: offset
      单元移动的偏移量: itemOffset = offset/(TIME/ITEM_TIME)

      启动循环定时器不断更新$list的left, 到达目标处停止停止定时器
     */
    //如果正在翻页, 直接结束
    if(moving) { //已经正在翻页中
      return
    }
    moving = true // 标识正在翻页
    // 总的偏移量: offset
    var offset = 0
    // 计算offset
    if(typeof next==='boolean') {
      offset = next ? -PAGE_WIDTH : PAGE_WIDTH
    } else {
      offset = -(next-index)* PAGE_WIDTH
    }
    // 计算单元移动的偏移量: itemOffset
    var itemOffset = offset/(TIME/ITEM_TIME)
    // 得到当前的left值
    var currLeft = $list.position().left
    // 计算出目标处的left值
    var targetLeft = currLeft + offset
    // 启动循环定时器不断更新$list的left, 到达目标处停止停止定时器
    var intervalId = setInterval(function () {
      // 计算出最新的currLeft
      currLeft += itemOffset
      if(currLeft===targetLeft) { // 到达目标位置
        // 清除定时器
        clearInterval(intervalId)
        // 标识翻页停止
        moving = false
        // 如果到达了最右边的图片(1.jpg), 跳转到最左边的第2张图片(1.jpg)
        if(currLeft===-(imgCount+1) * PAGE_WIDTH) {
          currLeft = -PAGE_WIDTH
        } else if(currLeft===0){
          // 如果到达了最左边的图片(5.jpg), 跳转到最右边的第2张图片(5.jpg)
          currLeft = -imgCount * PAGE_WIDTH
        }
      }
      // 设置left
      $list.css('left', currLeft)
    }, ITEM_TIME)

    // 更新圆点
    updatePoints(next)
  }
  /**
   * 更新圆点
   * @param next
   */
  function updatePoints (next) {

    // 计算出目标圆点的下标targetIndex
    var targetIndex = 0
    if(typeof next === 'boolean') {
      if(next) {
        targetIndex = index + 1   // [0, imgCount-1]
        if(targetIndex===imgCount) {// 此时看到的是1.jpg-->第1个圆点
          targetIndex = 0
        }
      } else {
        targetIndex = index - 1
        if(targetIndex===-1) { // 此时看到的是5.jpg-->第5个圆点
          targetIndex = imgCount-1
        }
      }
    } else {
      targetIndex = next
    }
    // 将当前index的<span>的class移除
    // $points.eq(index).removeClass('on')
    $points[index].className = ''
    // 给目标圆点添加class='on'
    // $points.eq(targetIndex).addClass('on')
    $points[targetIndex].className = 'on'

    // 将index更新为targetIndex
    index = targetIndex
  }
})
</script>

jQuery文档的结构图

jQuery文档结构图

JavaScript高级学习笔记

JavaScript高级学习笔记

  • 基础总结

  • 函数高级

  • 面向对象高级

  • 线程机制与事件机制

一、基础总结

1、数据类型

分类

  • 基本(值)类型

    • String:任意字符串
    • Number:任意的数字
    • boolean:true/false
    • undefined:undefined
    • null:null
  • 对象(引用)类型

    • Object:任意对象
    • Function:一种特殊的对象(可以执行)
    • Array:一种特殊的对象(数值下标,内部数据是有序的)
var obj = {
  name: 'TOM',
  age: 12
}
fuction test(){
  var a = 3
}
var arr = [3, 'abc']
arr[1]

判断

  • typeof

    • 可以判断:undefined/ 数值 /字符串/布尔值/function
    • 不能判断:null与Object Object与array
  • instanceof:判断对象的具体类型

  • ===可以判断:undefined/null/function

  • isNaN 判断NaN ……

//1. 基本
// typeof返回数据类型的字符串表达
var a
// undefined
console.log(a, typeof a, typeof a==='undefined',a===undefined )  // undefined 'undefined' true true
console.log(undefined==='undefined') // false
// Number Sting Boolean
a = 4
console.log(typeof a==='number') // true
a = "abc123"
console.log(isNaN(a)) // true
a = 'string iiii'
console.log(typeof a==='string') // true
a = true
console.log(typeof a==='boolean') // true
// null
a = null
console.log(typeof a, a===null) // 'object' true

//2. 对象
var b1 = {
    b2: [1, 'abc', console.log],
    b3: function () {
        console.log('b3') // b3
        return function () {
            return 'funb1'
        }
    }
}
// Object Array Function
console.log(b1 instanceof Object, b1 instanceof Array) // true  false
console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true
console.log(typeof b1.b2) // 'object'
// Function
console.log(typeof b1.b3==='function') // true
console.log(typeof b1.b2[2]==='function') // true

// 细节问题
b1.b2[2](4) // 4
console.log(b1.b3()()) // funb1
/*
b1.b3() ---> b3当前的function
b1.b3()() ---> return的function
*/

实例

实例:实例对象

类型:类型对象

function Person(name, age){ // 构造函数 类型
    this.name = name
  this.age = age
} 
var p = new Person('Tom', 12) // 根据类型创建的实例对象

其他问题

  1. undefined与null的区别?

    • undefined代表定义未赋值
    • nulll定义并赋值了, 只是值为null
    var a
    console.log(a)  // undefined
    a = null
    console.log(a) // null
  1. 什么时候给变量赋值为null呢?

    • 初始赋值, 表明将要赋值为对象
    • 结束前, 让对象成为垃圾对象(被垃圾回收器回收)
    //起始
    var b = null  // 初始赋值为null, 表明将要赋值为对象
    //确定对象就赋值
    b = ['atguigu', 12]
    //最后
    b = null // 让b指向的对象成为垃圾对象(被垃圾回收器回收)
    // b = 2
  1. 严格区别变量类型与数据类型?

    • 数据的类型

      • 基本类型
      • 对象类型
    • 变量的类型(变量内存值的类型)

      • 基本类型: 保存就是基本类型的数据
      • 引用类型: 保存的是地址值
      var c = function () {
      
      }
      console.log(typeof c) // 'function'

2、数据、变量与内存

  1. 什么是数据?
  • 存储在内存中代表特定信息的’东东’, 本质上是0101…

  • 数据的特点: 可传递, 可运算

  • 一切皆数据

  • 内存中所有操作的目标: 数据

    • 算术运算
    • 逻辑运算
    • 赋值
    • 运行函数
  1. 什么是内存?
  • 内存条通电后产生的可储存数据的空间(临时的)

  • 内存产生和死亡: 内存条(电路版)—>通电—>产生内存空间—>存储数据—>处理数据—>断电—>内存空间和数据都消失

  • 一块小内存的2个数据

    • 内部存储的数据
    • 地址值
  • 内存分类

    • 栈: 全局变量/局部变量
    • 堆: 对象
  1. 什么是变量?
  • 可变化的量, 由变量名和变量值组成
  • 每个变量都对应的一块小内存, 变量名用来查找对应的内存, 变量值就是内存中保存的数据
  1. 内存,数据, 变量三者之间的关系
  • 内存用来存储数据的空间

  • 变量是内存的标识

var age = 18
console.log(age)

var obj = {name: 'Tom'}
console.log(obj.name)

function fn () {
  var obj = {name: 'Tom'}
}

var a = 3
var b = a + 2
  1. 关于赋值与内存的问题

问题: var a = xxx, a内存中到底保存的是什么?

  • xxx是基本数据, 保存的就是这个数据

  • xxx是对象, 保存的是对象的地址值

  • xxx是一个变量, 保存的xxx的内存内容(可能是基本数据, 也可能是地址值)

var a = 3
a = function () {

}
var b = 'abc'
a = b
b = {}
a = b

image-20200815100153040

  1. 关于引用变量赋值问题
  • 2个引用变量指向同一个对象, 通过一个变量修改对象内部数据, 另一个变量看到的是修改之后的数据

  • 2个引用变量指向同一个对象, 让其中一个引用变量指向另一个对象, 另一引用变量依然指向前一个对象

var obj1 = {name: 'Tom'}
var obj2 = obj1
obj2.age = 12
console.log(obj1.age)  // 12
function fn (obj) {
  obj.name = 'A'
}
fn(obj1)
console.log(obj2.name) //A

var a = {age: 12}
var b = a
a = {name: 'BOB', age: 13}
b.age = 14
console.log(b.age, a.name, a.age) // 14 Bob 13
function fn2 (obj) {
  obj = {age: 15}
}
fn2(a)
console.log(a.age) // 13

image-20200815101834022

image-20200815102622137

  1. 关于数据传递问题

问题: 在js调用函数时传递变量参数时, 是值传递还是引用传递

  • 理解1: 都是值(基本/地址值)传递

  • 理解2: 可能是值传递, 也可能是引用传递(地址值)

var a = 3
function fn (a) {
  a = a +1
}
fn(a)
console.log(a)  // 3

function fn2 (obj) {
  console.log(obj.name)
}
var obj = {name: 'Tom'}
fn2(obj)  // Tom
  1. JS引擎如何管理内存

问题: JS引擎如何管理内存?

  • 内存生命周期
    • 分配小内存空间, 得到它的使用权
    • 存储数据, 可以反复进行操作
    • 释放小内存空间
  • 释放内存
    • 局部变量: 函数执行完自动释放
    • 对象: 成为垃圾对象==>垃圾回收器回收
var a = 3
var obj = {}
obj = undefined

function fn () {
  var b = {}
}

fn() // b是自动释放, b所指向的对象是在后面的某个时刻由垃圾回收器回收

3、对象

  1. 什么是对象?
  • 多个数据的封装体

  • 用来保存多个数据的容器

  • 一个对象代表现实中的一个事物

  1. 为什么要用对象?
  • 统一管理多个数据
  1. 对象的组成
  • 属性: 属性名(字符串)和属性值(任意)组成

  • 方法: 一种特别的属性(属性值是函数)

  1. 如何访问对象内部数据?
  • .属性名: 编码简单, 有时不能用

  • ['属性名']: 编码麻烦, 能通用

var p = {
  name: 'Tom',
  age: 12,
  setName: function (name) {
    this.name = name
  },
  setAge: function (age) {
    this.age = age
  }
}

p.setName('Bob')
p['setAge'](23)
console.log(p.name, p['age'])

问题: 什么时候必须使用[‘属性名’]的方式?

  1. 属性名包含特殊字符: -空格

  2. 属性名不确定

var p = {}
//1. 给p对象添加一个属性: content type: text/json
// p.content-type = 'text/json' //不能用
p['content-type'] = 'text/json'
console.log(p['content-type'])

//2. 属性名不确定
var propName = 'myAge'
var value = 18
// p.propName = value //不能用
p[propName] = value
console.log(p[propName])

4、函数

  1. 什么是函数?
  • 实现特定功能的n条语句的封装体
  • 只有函数是可以执行的, 其它类型的数据不能执行
  1. 为什么要用函数?
  • 提高代码复用
  • 便于阅读交流
  1. 如何定义函数?
  • 函数声明
  • 表达式
  1. 如何调用(执行)函数?
  • test(): 直接调用
  • obj.test(): 通过对象调用
  • new test(): new调用
  • test.call/apply(obj): 临时让test成为obj的方法进行调用
/*
编写程序实现以下功能需求:
  1. 根据年龄输出对应的信息
  2. 如果小于18, 输出: 未成年, 再等等!
  3. 如果大于60, 输出: 算了吧!
  4. 其它, 输出: 刚好!
*/
function showInfo (age) {
  if(age<18) {
    console.log('未成年, 再等等!')
  } else if(age>60) {
    console.log('算了吧!')
  } else {
    console.log('刚好!')
  }
}

showInfo(17)
showInfo(20)
showInfo(65)

function fn1 () { //函数声明
  console.log('fn1()')
}
var fn2 = function () { //表达式
  console.log('fn2()')
}

fn1()
fn2()

var obj = {}
function test2 () {
  this.xxx = 'atguigu'
}
// obj.test2()  不能直接, 根本就没有
test2.call(obj) // obj.test2()   // 可以让一个函数成为指定任意对象的方法进行调用
console.log(obj.xxx)

5、回调函数

  1. 什么函数才是回调函数?
    1). 你定义的
    2). 你没有调
    3). 但最终它执行了(在某个时刻或某个条件下)

  2. 常见的回调函数?

    • dom事件回调函数 ==>发生事件的dom元素

    • 定时器回调函数 ===>window

    • ajax请求回调函数(后面讲)

    • 生命周期回调函数(后面讲)

<button id="btn">测试点击事件</button>
<script type="text/javascript">
  document.getElementById('btn').onclick = function () { // dom事件回调函数
    alert(this.innerHTML)
  }
  //定时器
    // 超时定时器
    // 循环定时器
  setTimeout(function () { // 定时器回调函数
    alert('到点了'+this)
  }, 2000)
  /*var a = 3
  alert(window.a)
  window.b = 4
  alert(b)*/
</script>

6、IIFE

  1. 理解
    • 全称: Immediately-Invoked Function Expression(立即执行函数)
  2. 作用
    • 隐藏实现
    • 不会污染外部(全局)命名空间
    • 用它来编码js模块
(function () { //匿名函数自调用
  var a = 3
  console.log(a + 3)
})()
var a = 4
console.log(a)
;(function () {
  var a = 1
  function test () {
    console.log(++a)
  }
  window.$ = function () { // 向外暴露一个全局函数
    return {
      test: test
    }
  }
})()
$().test() // 1.

7、函数中的this

  1. this是什么?
    • 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
    • 所有函数内部都有一个变量this
    • 它的值是调用函数的当前对象
  2. 如何确定this的值?
    • test(): window
    • p.test(): p
    • new test(): 新创建的对象
    • p.call(obj): obj
function Person(color) {
  console.log(this)
  this.color = color;
  this.getColor = function () {
    console.log(this)
    return this.color;
  };
  this.setColor = function (color) {
    console.log(this)
    this.color = color;
  };
}

Person("red"); //this是谁? window

var p = new Person("yello"); //this是谁? p

p.getColor(); //this是谁? p

var obj = {};
p.setColor.call(obj, "black"); //this是谁? obj

var test = p.setColor;
test(); //this是谁? window

function fun1() {
  function fun2() {
    console.log(this);
  }

  fun2(); //this是谁? window
}
fun1();

二、函数高级

超级重点,两大神兽:==原型和闭包==

1、原型和原型链

(1) 原型

  1. 函数的prototype属性(图)
    • 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
    • 原型对象中有一个属性constructor, 它指向函数对象

image-20200815113735029

  1. 给原型对象添加属性(一般都是方法)
    • 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
console.log(Date.prototype, typeof Date.prototype)
function Fun () {//alt + shift +r(重命名rename)

}
console.log(Fun.prototype)  // 默认指向一个Object空对象(没有我们的属性)

// 原型对象中有一个属性constructor, 它指向函数对象
console.log(Date.prototype.constructor===Date)
console.log(Fun.prototype.constructor===Fun)

//给原型对象添加属性(一般是方法) ===>实例对象可以访问
Fun.prototype.test = function () {
  console.log('test()')
}
var fun = new Fun()
fun.test()

(2) 显式原型与隐式原型

  1. 每个函数function都有一个prototype,即显式原型(属性)
  2. 每个实例对象都有一个proto,可称为隐式原型(属性)
  3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  4. 内存结构(图)
  5. 总结:
    • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
    • 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
    • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
//定义构造函数
function Fn() {   // 内部语句: this.prototype = {}

}
// 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
console.log(Fn.prototype)
// 2. 每个实例对象都有一个__proto__,可称为隐式原型
//创建实例对象
var fn = new Fn()  // 内部语句: this.__proto__ = Fn.prototype
console.log(fn.__proto__)
// 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype===fn.__proto__) // true
//给原型添加方法
Fn.prototype.test = function () {
  console.log('test()')
}
//通过实例调用原型的方法
fn.test()

image-20200815115016550

(3) 原型链

  1. 原型链(图解)
    • 访问一个对象的属性时,
      • 先在自身属性中查找,找到返回
      • 如果没有, 再沿着__proto__这条链向上查找, 找到返回
      • 如果最终没找到, 返回undefined
    • 别名: 隐式原型链
    • 作用: 查找对象的属性(方法)

image-20200815115135158

  1. 构造函数/原型/实体对象的关系(图解)
var o1 = new Object();
var o2 = {};

image-20200815115413214

  1. 构造函数/原型/实体对象的关系2(图解)
function Foo(){  }
// var Foo = new Function()
// Function = new Function()
// 所有函数的__proto__都是一样的

image-20200815115505581

// console.log(Object)
//console.log(Object.prototype)
console.log(Object.prototype.__proto__)
function Fn() {
  this.test1 = function () {
    console.log('test1()')
  }
}
console.log(Fn.prototype)
Fn.prototype.test2 = function () {
  console.log('test2()')
}

var fn = new Fn()

fn.test1()
fn.test2()
console.log(fn.toString())
console.log(fn.test3)
// fn.test3()

/*
1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
  */
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true
/*
2. 所有函数都是Function的实例(包含Function)
*/
console.log(Function.__proto__===Function.prototype)
/*
3. Object的原型对象是原型链尽头
  */
console.log(Object.prototype.__proto__) // null
  1. 原型继承
  • 构造函数的实例对象自动拥有构造函数原型对象的属性(方法)
  • 利用的就是原型链
  1. 原型属性问题
  • 读取对象的属性值时: 会自动到原型链中查找
  • 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
  • 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
function Fn() {

}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a, fn1)

var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a, fn2.a, fn2)

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.setName = function (name) {
  this.name = name
}
var p1 = new Person('Tom', 12)
p1.setName('Bob')
console.log(p1)

var p2 = new Person('Jack', 12)
p2.setName('Cat')
console.log(p2)
console.log(p1.__proto__===p2.__proto__) // true

(4) 探索instanceof

  1. instanceof是如何判断的?
    • 表达式: A instanceof B
    • 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
  2. Function是通过new自己产生的实例

案例1

function Foo() {  }
var f1 = new Foo()
console.log(f1 instanceof Foo) // true
console.log(f1 instanceof Object) // true

image-20200815120816214

案例2

console.log(Object instanceof Function) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true

function Foo() {}
console.log(Object instanceof  Foo) // false

image-20200815120913828

(5) 面试题

/*
测试题1
  */
function A () {

}
A.prototype.n = 1

var b = new A()

A.prototype = {
  n: 2,
  m: 3
}

var c = new A()
console.log(b.n, b.m, c.n, c.m) // 1 undefined 2 3

image-20200815122319966

/*
  测试题2
  */
function F (){}
Object.prototype.a = function(){
  console.log('a()')
}
Function.prototype.b = function(){
  console.log('b()')
}

var f = new F()
f.a() // a()
// f.b() // Uncaught TypeError: f.b is not a function
F.a() // a()
F.b() // b()
console.log(f) // F {}
console.log(Object.prototype) // {a: ƒ, constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, …}
console.log(Function.prototype) // ƒ () { [native code] }

2、执行上下文与执行上下文栈

(1) 变量提升与函数提升

  1. 变量声明提升
    • 通过var定义(声明)的变量, 在定义语句之前就可以访问到
    • 值: undefined
  2. 函数声明提升
    • 通过function声明的函数, 在之前就可以直接调用
    • 值: 函数定义(对象)
  3. 问题: 变量提升和函数提升是如何产生的?
    • 在js中js引擎会优先解析var变量和function定义!在预解析完成后从上到下逐步进行!
    • 解析var变量时,会把值存储在“执行环境”中,而不会去赋值,值是存储作用!例如:
      alert(a); var a = 2; 这时会输出undifiend,意思是没有被初始化没有被赋值!
      这并不是没有被定义、错误了的意思!
    • 在解析function时会把函数整体定义,这也就解释了为什么在function定义函数时为什么可以先调用后声明了!其实表面上看是先调用了,其实在内部机制中第一步实行的是把以function方式定义的函数先声明了(预处理)
/*
面试题 : 输出 undefined
  */
var a = 3
function fn () {
  console.log(a)
  var a = 4
}
fn() // undefined

console.log(b) //undefined  变量提升
fn2() //可调用  函数提升 fn2()
// fn3() //不能  变量提升 Uncaught TypeError: fn3 is not a function

var b = 3
function fn2() {
  console.log('fn2()')
}

var fn3 = function () {
  console.log('fn3()')
}

image-20200815122404360

(2) 执行上下文

  1. 代码分类(位置)
    • 全局代码
    • 函数(局部)代码
  2. 全局执行上下文
    • 在执行全局代码前将window确定为全局执行上下文
    • 对全局数据进行预处理
      • var定义的全局变量==>undefined, 添加为window的属性
      • function声明的全局函数==>赋值(fun), 添加为window的方法
      • this==>赋值(window)
    • 开始执行全局代码
      // 全局声明的变量和函数都会在window中:window.a1、window.a2()
      console.log(a1, window.a1) // undefined undefined
      window.a2() // a2() 
      console.log(this) // Window
      

var a1 = 3 // 在这声明,事实上调到上面声明了,这里赋值(因此上面能访问)
function a2() { // 在这里声明,能够在上面调用
console.log(‘a2()’)
}
console.log(a1) // 3




3. 函数执行上下文
  * 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  * 对局部数据进行预处理
    * 形参变量——>赋值(实参)——>添加为执行上下文的属性
    * arguments==>赋值(实参列表), 添加为执行上下文的属性
    * var定义的局部变量==>undefined, 添加为执行上下文的属性
    * function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
    * this==>赋值(调用函数的对象)
  * 开始执行函数体代码

![image-20200815123117687](https://cdn.jsdelivr.net/gh/wallleap/cdn/img/pic/illustration/20200815123118.png)



(3) 执行上下文栈

1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下window

```javascript
var a = 10
var bar = function (x) {
  var b = 5
  foo(x + b)
}
var foo = function (y) {
  var c = 5
  console.log(a + c + y)
}
bar(10)
// bar(10)

image-20200815123327077

image-20200815123401244

image-20200815123414448

console.log('gb: '+ i)
var i = 1
foo(1)
function foo(i) {
  if (i == 4) {
    return
  }
  console.log('fb:' + i)
  foo(i + 1) //递归调用: 在函数内部调用自己
  console.log('fe:' + i)
}
console.log('ge: ' + i)
/*
1. 依次输出什么?
  gb: undefined
  fb: 1
  fb: 2
  fb: 3
  fe: 3
  fe: 2
  fe: 1
  ge: 1
2. 整个过程中产生了几个执行上下文?  5   */

(4) 面试题

/*
  测试题1:  先执行变量提升, 再执行函数提升(先找var和function xxx(){})
  */
function a() {}
var a
console.log(typeof a) // function
/*
  测试题2: 先提出去,window中有b,且未赋值
  */
if (!(b in window)) {
  var b = 1
}
console.log(b) // undefined
/*
  测试题3: 针对变量名同名或函数名同名的情况:如果声明了同名的函数其定义会被后者覆盖,声明了同名的变量其值也会被后者覆盖
  */
var c = 1
function c(c) {
  console.log(c)
  var c = 3
}
console.log(c) // 1
c(2) // 报错 Uncaught TypeError: c is not a function

再看一个
//声明阶段
function x(){//函数声明
    //console.log(5)此句会被下句代码覆盖
    console.log(3)
}
var x;//变量声明,因为x已经声明过了,此处不进行声明(忽略)
//执行阶段
console.log(x) // ƒ x(){//函数声明//console.log(5);此句会被下句代码覆盖console.log(3);}
console.log(x()) // 3
x=1 
x=100 //x的值被覆盖
console.log(x) // 100
console.log(x()) // Uncaught TypeError: x is not a function

image-20200815125002631

3、作用域与作用域链

(1) 作用域

  1. 理解
    • 就是一块”地盘”, 一个代码段所在的区域
    • 它是静态的(相对于上下文对象), 在编写代码时就确定了
  2. 分类
    • 全局作用域
    • 函数作用域
    • 没有块作用域(ES6有了)
      /*  //没块作用域
      if(true) {
      var c = 3
      }
      console.log(c) // 3 有块作用域则报错*/
  1. 作用
    • 隔离变量,不同作用域下同名变量不会有冲突

例如把如下代码分割

var a = 10,
  b = 20
function fn(x) {
  var a = 100,
    c = 300;
  console.log('fn()', a, b, c, x) 
  function bar(x) {
    var a = 1000,
      d = 400
    console.log('bar()', a, b, c, d, x)
  }
  bar(100) // bar() 1000 20 300 400 100
  bar(200) // bar() 1000 20 300 400 200
}
fn(10) // fn() 100 20 300 10

image-20200815125422438

(2) 作用域与执行上下文

  1. 区别1
    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
    • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
    • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
  2. 区别2
    • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
    • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
  3. 联系
    • 执行上下文(对象)是从属于所在的作用域
    • 全局上下文环境==>全局作用域
    • 函数上下文环境==>对应的函数使用域

还是上面那串代码

image-20200815125635951

(3) 作用域链

  1. 理解
    • 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
    • 查找变量时就是沿着作用域链来查找的
  2. 查找一个变量的查找规则
    • 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
    • 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
    • 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
var a = 1
function fn1() {
  var b = 2
  function fn2() {
    var c = 3
    console.log(c) // 3
    console.log(b) // 2
    console.log(a) // 1
    console.log(d) // Uncaught ReferenceError: d is not defined
  }
  fn2()
}
fn1()

image-20200815125755108

(4) 面试题

var x = 10;
function fn() {
  console.log(x);
}
function show(f) {
  var x = 20;
  f();
}
show(fn); // 10

image-20200815125902910

var fn = function () {
  console.log(fn)
}
fn() // f(){console.log(fn)}

var obj = {
  fn2: function () {
    console.log(fn2)
    //console.log(this.fn2) // ƒ () {// console.log(fn2) console.log(this.fn2)}
  }
}
obj.fn2() // Uncaught ReferenceError: fn2 is not defined

image-20200815125935489

4、闭包

(1) 引入实例

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
  //遍历加监听
  /*// 无论点哪个都是 第4个 ——点击的时候循环已经执行完了
  for (var i = 0,length=btns.length; i < length; i++) {
    var btn = btns[i]
    btn.onclick = function () {
      alert('第'+(i+1)+'个')
    }
  }*/
  /*
  for (var i = 0,length=btns.length; i < length; i++) {
    var btn = btns[i]
    //将btn所对应的下标保存在btn上,利用这中方式可以实现
    btn.index = i
    btn.onclick = function () {
      alert('第'+(this.index+1)+'个')
    }
  }*/

  //利用闭包
  for (var i = 0,length=btns.length; i < length; i++) {
    (function (j) {
      var btn = btns[j]
      btn.onclick = function () {
        alert('第'+(j+1)+'个')
      }
    })(i)
  }
</script>

(2) 理解闭包

  1. 如何产生闭包?
    • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  2. 闭包到底是什么?
    • 使用chrome调试查看
    • 理解一: 闭包是嵌套的内部函数(绝大部分人)
    • 理解二: 包含被引用变量(函数)的对象(极少数人)
    • 注意: 闭包存在于嵌套的内部函数中
  3. 产生闭包的条件?
    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)
function fn1 () {
  var a = 2
  var b = 'abc'
  function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
    console.log(a)
  }
  // fn2()
}
fn1()

function fun1() {
  var a = 3
  var fun2 = function () { // 声明变量形式定义函数,不会产生闭包
    console.log(a)
  }
}
fun1()

(3) 常见的闭包

  1. 将函数作为另一个函数的返回值
  2. 将函数作为实参传递给另一个函数调用
// 1. 将函数作为另一个函数的返回值
function fn1() {
  var a = 2
  function fn2() {
    a++
    console.log(a)
  }
  return fn2
}
var f = fn1()
f() // 3
f() // 4
/* f调用的是内部函数,fn1外部函数执行了一次,产生1个闭包 */

// 2. 将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
  setTimeout(function () {
    alert(msg)
  }, time)
}
showDelay('test', 2000)

image-20200815131912058

(4) 闭包的作用

  1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:

  • 函数执行完后, 函数内部声明的局部变量是否还存在?

    一般是不存在, 存在于闭中的变量才可能存在

  • 在函数外部能直接访问函数内部的局部变量吗?

    不能, 但我们可以通过闭包让外部操作它

function fn1() {
  var a = 2
  function fn2() {
    a++
    console.log(a)
    // return a
  }
  function fn3() {
    a--
    console.log(a)
  }
  return fn3
}
var f = fn1()
f() // 1
f() // 0

(5) 闭包的生命周期

  1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
  2. 死亡: 在嵌套的内部函数成为垃圾对象时
function fn1() {
  //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
  var a = 2
  function fn2 () {
    a++
    console.log(a)
  }
  return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)

(6) 闭包的应用: 自定义JS模块

模块:

  • 具有特定功能的js文件
    • 将所有的数据和功能都封装在一个函数内部(私有的)
    • 只向外暴露一个包括n个方法的对象或函数
    • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能

例如:

文件myModule1.js就是一个模块

function myModule() {
  //私有数据
  var msg = 'Hello This is Module1'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}

在其他文件中引入

<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
  var module = myModule()
  module.doSomething()
  module.doOtherthing()
</script>

下面这种更好用一点,可以不需要先执行那个函数(IIFE)

myModule2.js

(function () {
  //私有数据
  var msg = 'Hello This is Module1'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  window.myModule2 = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})()

引入使用

<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
  myModule2.doSomething()
  myModule2.doOtherthing()
</script>

压缩代码时,会把变量改为a、b、c这种的,因此最好这样做

image-20200815132852872

(7) 闭包的缺点及解决

  1. 缺点
    • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
    • 容易造成内存泄露
  2. 解决
    • 能不用闭包就不用
    • 及时释放
function fn1() {
  var arr = new Array[100000]
  function fn2() {
    console.log(arr.length)
  }
  return fn2
}
var f = fn1()
f()

f = null //让内部函数成为垃圾对象-->回收闭包

补充:内存溢出与内存泄露

  • 内存溢出
    • 一种程序运行出现的错误
    • 当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误
  • 内存泄露
    • 占用的内存没有及时释放
    • 内存泄露积累多了就容易导致内存溢出
    • 常见的内存泄露:
      • 意外的全局变量
      • 没有及时清理的计时器或回调函数
      • 闭包

image-20200815133731332

image-20200815133739782

image-20200815133755479

(8) 面试题

//代码片段一
var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
    };
  }
};
alert(object.getNameFunc()());  // the window

//代码片段二
var name2 = "The Window";
var object2 = {
  name2 : "My Object",
  getNameFunc : function(){
    var that = this;
    return function(){
      return that.name2;
    };
  }
};
alert(object2.getNameFunc()()); // my object

// 三
function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n)
    }
  }
}
var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)//undefined,0,0,0

var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2

var c = fun(0).fun(1)
c.fun(2)
c.fun(3)//undefined,0,1,1

image-20200815133830697

三、面向对象高级

1、对象创建模式

有如下五种方式:

(1) Object构造函数模式

  • 套路: 先创建空Object对象, 再动态添加属性/方法
  • 适用场景: 起始时不确定对象内部数据
  • 问题: 语句太多
/*
一个人: name:"Tom", age: 12
  */
// 先创建空Object对象
var p = new Object()
p = {} //此时内部数据是不确定的
// 再动态添加属性/方法
p.name = 'Tom'
p.age = 12
p.setName = function (name) {
  this.name = name
}

//测试
console.log(p.name, p.age)
p.setName('Bob')
console.log(p.name, p.age)

(2) 对象字面量模式

  • 套路: 使用{}创建对象, 同时指定属性/方法
    • 适用场景: 起始时对象内部数据是确定的
    • 问题: 如果创建多个对象, 有重复代码
var p = {
  name: 'Tom',
  age: 12,
  setName: function (name) {
    this.name = name
  }
}

//测试
console.log(p.name, p.age)
p.setName('JACK')
console.log(p.name, p.age)

var p2 = {  //如果创建多个对象代码很重复
  name: 'Bob',
  age: 13,
  setName: function (name) {
    this.name = name
  }
}

(3) 工厂模式

  • 套路: 通过工厂函数动态创建对象并返回
    • 适用场景: 需要创建多个对象
    • 问题: 对象没有一个具体的类型, 都是Object类型
function createPerson(name, age) { //返回一个对象的函数===>工厂函数
  var obj = {
    name: name,
    age: age,
    setName: function (name) {
      this.name = name
    }
  }

  return obj
}

// 创建2个人
var p1 = createPerson('Tom', 12)
var p2 = createPerson('Bob', 13)

// p1/p2是Object类型

function createStudent(name, price) {
  var obj = {
    name: name,
    price: price
  }
  return obj
}
var s = createStudent('张三', 12000)
// s也是Object

(4) 自定义构造函数模式

  • 套路: 自定义构造函数, 通过new创建对象
  • 适用场景: 需要创建多个类型确定的对象
  • 问题: 每个对象都有相同的数据, 浪费内存
//定义类型
function Person(name, age) {
  this.name = name
  this.age = age
  this.setName = function (name) {
    this.name = name
  }
}
var p1 = new Person('Tom', 12)
p1.setName('Jack')
console.log(p1.name, p1.age)
console.log(p1 instanceof Person)

function Student (name, price) {
  this.name = name
  this.price = price
}
var s = new Student('Bob', 13000)
console.log(s instanceof Student)

var p2 = new Person('JACK', 23)
console.log(p1, p2)

方法两者都有,且相同,不需要单独拥有,可放到原型中(方法一般放到原型中)——>

(5) 构造函数+原型的组合模式

  • 套路: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
  • 适用场景: 需要创建多个类型确定的对象
function Person(name, age) { //在构造函数中只初始化一般函数
  this.name = name
  this.age = age
}
Person.prototype.setName = function (name) {
  this.name = name
}

var p1 = new Person('Tom', 23)
var p2 = new Person('Jack', 24)
console.log(p1, p2)

2、继承模式

继承,在js中有三种方式:

(1) 原型链继承

  1. 套路
    • 定义父类型构造函数
    • 给父类型的原型添加方法
    • 定义子类型的构造函数
    • 创建父类型的对象赋值给子类型的原型
    • 将子类型原型的构造属性设置为子类型
    • 给子类型原型添加方法
    • 创建子类型的对象: 可以调用父类型的方法
  2. 关键
    • 子类型的原型为父类型的一个实例对象
//父类型
function Supper() {
  this.supProp = 'Supper property'
}
Supper.prototype.showSupperProp = function () {
  console.log(this.supProp)
}

//子类型
function Sub() {
  this.subProp = 'Sub property'
}

// 子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper()
// 让子类型的原型的constructor指向子类型
Sub.prototype.constructor = Sub
Sub.prototype.showSubProp = function () {
  console.log(this.subProp)
}

var sub = new Sub()
sub.showSupperProp()
// sub.toString()
sub.showSubProp()

console.log(sub)  // Sub

image-20200815135206553

(2) 借用构造函数继承

  1. 套路:
    • 定义父类型构造函数
    • 定义子类型构造函数
    • 在子类型构造函数中调用父类型构造
  2. 关键:
    • 在子类型构造函数中通用call()调用父类型构造函数
function Person(name, age) {
  this.name = name
  this.age = age
}
function Student(name, age, price) {
  Person.call(this, name, age)  // 相当于: this.Person(name, age)
  /*this.name = name
  this.age = age*/
  this.price = price
}

var s = new Student('Tom', 20, 14000)
console.log(s.name, s.age, s.price)

(3) 组合继承

  1. 利用原型链实现对父类型对象的方法继承

  2. 利用super()借用父类型构建函数初始化相同属性

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.setName = function (name) {
  this.name = name
}

function Student(name, age, price) {
  Person.call(this, name, age)  // 为了得到属性
  this.price = price
}
Student.prototype = new Person() // 为了能看到父类型的方法
Student.prototype.constructor = Student //修正constructor属性
Student.prototype.setPrice = function (price) {
  this.price = price
}

var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)

四、线程机制与事件机制

1、进程与线程

(1) 进程(process)

  • 程序的一次执行, 它占有一片独有的内存空间
  • 可以通过windows任务管理器查看进程

(2) 线程(thread)

  • 是进程内的一个独立执行单元
  • 是程序执行的一个完整流程
  • 是CPU的最小的调度单元

线程与进程

应用程序必须运行在某个进程的某个线程上
一个进程中至少有一个运行的线程: 主线程, 进程启动后自动创建
一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的
一个进程内的数据可以供其中的多个线程直接共享
多个进程之间的数据是不能直接共享的
线程池(thread pool): 保存多个线程对象的容器, 实现线程对象的反复利用

(3) 相关问题

  1. 何为多进程与多线程?
  • 多进程运行: 一应用程序可以同时启动多个实例运行
  • 多线程: 在一个进程内, 同时有多个线程运行
  1. 比较单线程与多线程?
  • 多线程
    • 优点
      • 能有效提升CPU的利用率
    • 缺点
      • 创建多线程开销
      • 线程间切换开销
      • 死锁与状态同步问题
  • 单线程
    • 优点
      • 顺序编程简单易懂
    • 缺点
      • 效率低
  1. JS是单线程还是多线程?
  • js是单线程运行的,但使用H5中的 Web Workers可以多线程运行
  1. 浏览器运行是单线程还是多线程?
  • 都是多线程运行的
  1. 浏览器运行是单进程还是多进程?
  • 有的是单进程
    • firefox
    • 老版IE
  • 有的是多进程
    • chrome
    • 新版IE
  • 如何查看浏览器是否是多进程运行的呢?
    • 任务管理器–>进程

2、浏览器内核

  • 支撑浏览器运行的最核心的程序
  • 不同的浏览器可能不一样
    • Chrome, Safari : webkit
    • firefox : Gecko
    • IE : Trident
    • 360,搜狗等国内浏览器: Trident + webkit
  • 内核由很多模块组成
    • 主线程
      • js引擎模块 : 负责js程序的编译与运行
      • html,css文档解析模块 : 负责页面文本的解析
      • DOM/CSS模块 : 负责dom/css在内存中的相关处理
      • 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)
        ......
    • ​ 分线程
      • 定时器模块 : 负责定时器的管理
      • DOM事件响应模块 : 负责事件的管理
      • 网络请求模块 : 负责ajax请求

3、定时器引发的思考

  1. 定时器真是定时执行的吗?
  • 定时器并不能保证真正定时执行

  • 一般会延迟一丁点(可以接受), 也有可能延迟很长时间(不能接受)

  1. 定时器回调函数是在分线程执行的吗?
  • 在主线程执行的, js是单线程的
  1. 定时器是如何实现的?
  • 事件循环模型(后面讲)
document.getElementById('btn').onclick = function () {
  var start = Date.now()
  console.log('启动定时器前...')
  setTimeout(function () {
    console.log('定时器执行了', Date.now()-start)
  }, 200)
  console.log('启动定时器后...')

  // 做一个长时间的工作
  for (var i = 0; i < 1000000000; i++) {

  }
}

4、JS是单线程执行的

  1. 如何证明js执行是单线程的?

    • setTimeout()的回调函数是在主线程执行的
    • 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行
  2. 为什么js要用单线程模式, 而不用多线程模式?

    • JavaScript的单线程,与它的用途有关。
    • 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。
    • 这决定了它只能是单线程,否则会带来很复杂的同步问题
  3. 代码的分类:

    • 初始化代码
    • 回调代码
  4. js引擎执行代码的基本流程

    • 先执行初始化代码: 包含一些特别的代码 回调函数(异步执行)
      • 设置定时器
      • 绑定事件监听
      • 发送ajax请求
    • 后面在某个时刻才会执行回调代码(回调函数包含的代码)
setTimeout(function () {
  console.log('timeout 2222')
  alert('22222222')
}, 2000)
setTimeout(function () {
  console.log('timeout 1111')
  alert('1111111')
}, 1000)
setTimeout(function () {
  console.log('timeout() 00000')
}, 0)
function fn() {
  console.log('fn()')
}
fn()

console.log('alert()之前')
alert('------') //暂停当前主线程的执行, 同时暂停计时, 点击确定后, 恢复程序执行和计时
console.log('alert()之后')

5、浏览器的事件循环(轮询)模型

(1) 模型原理图

image-20200815141017614

  1. 所有代码分类
    • 初始化执行代码(同步代码): 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
    • 回调执行代码(异步代码): 处理回调逻辑
  2. js引擎执行代码的基本流程:
    • 初始化代码===>回调代码
  3. 模型的2个重要组成部分:
    • 事件(定时器/DOM事件/Ajax)管理模块
    • 回调队列
  4. 模型的运转流程
    • 执行初始化代码, 将事件回调函数交给对应模块管理
    • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
    • 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行
function fn1() {
  console.log('fn1()')
}
fn1()
document.getElementById('btn').onclick = function () {
  console.log('点击了btn')
}
setTimeout(function () {
  console.log('定时器执行了')
}, 2000)
function fn2() {
  console.log('fn2()')
}
fn2()

(2) 相关重要概念

  1. 执行栈 execution stack
  • 所有的代码都是在此空间中执行的
  1. 浏览器内核 browser core

    • js引擎模块(在主线程处理)

    • 其它模块(在主/分线程处理)

      运行原理图

  1. 任务队列 task queue

  2. 消息队列 message queue

  3. 事件队列 event queue

    上面这三个在同一个 callback queue

  1. 事件轮询 event loop
    从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)

  2. 事件驱动模型 event-driven interaction model

  3. 请求响应模型 request-response model

6、H5 Web Workers(多线程)

(1) 介绍

  • Web Workers 是 HTML5 提供的一个javascript多线程解决方案
  • 我们可以将一些大计算量的代码交由web Worker运行而不冻结用户界面
  • 但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质

(2) 相关API

  • Worker: 构造函数, 加载分线程执行的js文件
  • Worker.prototype.onmessage: 用于接收另一个线程的回调函数
  • Worker.prototype.postMessage: 向另一个线程发送消息

(3) 使用

  • 创建在分线程执行的js文件
var onmessage =function (event){ //不能用函数声明
    console.log('onMessage()22');
    var upper = event.data.toUpperCase();//通过event.data获得发送来的数据
    postMessage( upper );//将获取到的数据发送会主线程
}
  • 在主线程中的js中发消息并设置回调
//创建一个Worker对象并向它传递将在新线程中执行的脚本的URL
var worker = new Worker("worker.js");  
//接收worker传过来的数据函数
worker.onmessage = function (event) {     
    console.log(event.data);             
};
//向worker发送数据
worker.postMessage("hello world");    

image-20200815143821391

(4) 应用练习

  • 直接在主线程
<input type="text" placeholder="数值" id="number">
<button id="btn">计算</button>
<script type="text/javascript">
// 1 1 2 3 5 8    f(n) = f(n-1) + f(n-2)
function fibonacci(n) {
  return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2)  //递归调用
}
// console.log(fibonacci(7))
var input = document.getElementById('number')
document.getElementById('btn').onclick = function () {
  var number = input.value
  var result = fibonacci(number)
  alert(result)
}
</script>
  • 使用Worker在分线程

    • 主线程
    var input = document.getElementById('number')
    document.getElementById('btn').onclick = function () {
      var number = input.value
    
      //创建一个Worker对象
      var worker = new Worker('worker.js')
      // 绑定接收消息的监听
      worker.onmessage = function (event) {
        console.log('主线程接收分线程返回的数据: '+event.data)
        alert(event.data)
      }
    
      // 向分线程发送消息
      worker.postMessage(number)
      console.log('主线程向分线程发送数据: '+number)
    }
    // console.log(this) // window
  • 分线程 worker.js

    function fibonacci(n) {
    return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2)  //递归调用
    }
    
    console.log(this)
    this.onmessage = function (event) {
    var number = event.data
    console.log('分线程接收到主线程发送的数据: '+number)
    //计算
    var result = fibonacci(number)
    postMessage(result)
    console.log('分线程向主线程返回数据: '+result)
    // alert(result)  alert是window的方法, 在分线程不能调用
    // 分线程中的全局对象不再是window, 所以在分线程中不可能更新界面
    }

(5) 不足

  • worker内代码不能操作DOM(更新UI)
  • 不能跨域加载JS
  • 不是每个浏览器都支持这个新特性