mongoose 使用

1. 命令行中创建并启动数据库

  1. 指定存储的位置和端口号
1
mongod --dbpath --port=27018  code/mongo_demo
  1. 另开一个命令行窗口,进入 code/mongo_demo
1
mongo

2. 使用 mongoose 链接数据库并创建 Collection

  1. 链接数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const mongoose = require('mongoose');
    const Schema = mongoose.Schema;

    const uri = 'mongodb://localhost/what_i_love';

    const ObjectId = Schema.Types.ObjectId;

    mongoose.set('useCreateIndex', true)

    mongoose.Promise = Promise;

    const connection = mongoose.connect(uri, {useNewUrlParser: true})

    const db = mongoose.connection;
  2. 创建 Collection

    1
    2
    3
    4
    5
    6
    let UserSchema = new Schema({
    name: {type: String, required: true, unique: true, index: 1},
    age: {type: Number, required: true, max: 199, min: [0,'你还没生出来呢']}
    });
    const UserModel = mongoose.model('user', UserSchema);
    //创建一个叫做 user 的 collection
  3. 操作 Collection

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    async function insert(userInfo) {
    return await UserModel.create(userInfo)
    }
    async function getOneById(id) {
    return await UserModel.findOne({
    _id: id
    })
    }

    async function getOneByName(name) {
    return await UserModel.findOne({
    name
    })
    }

    async function list(params) {
    const match = {}
    const flow = UserModel.find(match)
    return await flow.exec()

    }
    module.exports = {
    insert,
    getOneById,
    getOneByName,
    list,
    }

3. 使用中

一般来说链接数据库的方法单独放在一个地方。(第一步操作)

创建 Collection 并 操作Collection 放在一个地方。(第二步操作)

NodeJS 库 (持续更新)

1.加密 解密 工具

  • bcrypt

加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const saltRounds = 10; //加密轮数
let password = userInfo.password //密码
bcrypt.genSalt(saltRounds, function (err, salt) {
bcrypt.hash(password, salt, function (err, hash) {
//把hash值赋值给password变量
password = hash;
storeUInfo({ //存储
username: userInfo.username,
password,
name: userInfo.name,
age: userInfo.age
});
})
})

解密验证

1
2
3
4
5
6
7
8
9
10
11
async function login(userInfo) {
const user = await UserModel.findOne({ //从数据库中找到该用户
username: userInfo.username
})
const match = await bcrypt.compare(userInfo.password, user.password) // 看看 密码是否相同
if (match) {
return user
} else {
throw new HttpParamError('user', '用户名密码错误', 'username password error')
}
}

Vue父子传参的方式

感觉Vue中父子传参的方式,实在是太多了,于是做一个小总结,只是总结我所知道的。

1.父传子

基本就用一个方式,props

父亲通过在 标签 中写入需要传入的数据。

1
2
3
4
5
6
7
<!--father.vue-->
<template>
<div>
我是爸爸,下面是儿子
<son title='儿子' :selected='selected'></son>
</div>
</template>

儿子在 实例中的 props 选项中获取

1
2
3
4
5
6
7
8
9
10
11
12
//son.vue
export default {
name:'son',
props:{
selected:{
type:Boolean
},
title:{
type:String
}
}
}

2.子传父

# update:my-prop-name 模式

Vue 是单项数据流,所以不允许 儿子 直接修改父亲的数据,也不允许儿子直接修改自己的props

假设一个情况,点击儿子,儿子需要改变 selected 的状态。

儿子方面

触发点击事件后, 让儿子触发一个 update 事件,把新的 selected 传出去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!--son.vue-->
<template>
<div class="son" @click="onClick">
title:{{title}} selected:{{selected}}
</div>
</template>
<script>
export default {
name: "son",
props: {
selected: {
type: Boolean
},
title: {
type: String
}
},
methods: {
onClick() {
this.$emit("update:selected", !this.selected); //关键点
}
}
};
</script>

<style>
.son {
border: 1px solid red;
}
</style>

父亲方面

在标签中监听 update事件,并将传过来的 $event付给 selected,这样就完成了一次传参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--father.vue-->
<template>
<div>
我是爸爸,下面是儿子
<son title='儿子' :selected='selected' @update:selected='selected=$event'></son>
<!--关键点-->
</div>
</template>

<script>
import Son from "./son";
export default {
name: "father",
components: {
Son
},
data() {
return {
selected: true
};
}
};
</script>

简单方式

.sync 修饰符

1
2
3
4
5
6
7
8
<!--father.vue-->
<template>
<div>
我是爸爸,下面是儿子
<son title='儿子' :selected.sync='selected'></son>
<!--关键点-->
</div>
</template>

# $parents API

儿子方面

this.$parent中可以获取到 父组件 的 data 数据 ,直接进行修改,是不是很刺激。

1
2
3
4
5
methods: {
onClick() {
this.$parent.selected = !this.$parent.selected;
}
}

虽然刺激,但是,我建议调用父组件的函数,来切换状态。

父亲方面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//father.vue
export default {
name: "father",
components: {
Son
},
data() {
return {
selected: true
};
},
methods: {
changeSelected() {
this.selected = !this.selected;
}
}
};

儿子方面

1
2
3
4
5
6
//son.vue 
methods: {
onClick() {
this.$parent.changeSelected();
}
}

# EventBus

如果只是一个父亲,一个儿子上面的方法非常的简单实用,但是如果是祖孙三代传参呢?上面的方法就很麻烦了。

具体怎么麻烦,可以看一下我的这篇文章,用原始的方法造 tabs轮子:https://zhuanlan.zhihu.com/p/39601572

废话不多说,开始用 EventBus做一个简单的 tabs组件。

#app.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div id="app">
<tab selected='news'>
<tab-item name='news'>新闻</tab-item>
<tab-item name='car'>汽车</tab-item>
<tab-item name=‘code’>代码</tab-item>

<tab-pane name='news'>新闻列表</tab-pane>
<tab-pane name='car'>汽车列表</tab-pane>
<tab-pane name=‘code’>代码列表</tab-pane>
</tab>
</div>
</template>

<script>
import Tab from "./components/tabs.vue";
import TabItem from "./components/tab-item";
import TabPane from "./components/tab-pane";

export default {
name: "app",
components: {
Tab,
TabItem,
TabPane
}
};
</script>
# tabs.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
<div>
<slot></slot>
</div>
</template>

<script>
import TabItem from "./tab-item.vue";
import Vue from "vue"; //引入VUE
export default {
name: "tab",
props: {
selected: {
type: [Number, String]
}
},
data() {
return {
eventBus: new Vue() // 创建 eventBus
};
},
provide() {
return {
eventBus: this.eventBus // 提供 eventBus
};
},
mounted() {
this.eventBus.$emit("update:selected", this.selected);
//发布消息,告诉大家,现在的selected是啥
}
};
</script>

<style>
</style>
# tabs-item.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<div @click="onClick" :class="{active}">
<slot/>
</div>
</template>

<script>
export default {
name: "tab-item",
props: {
name: {
type: [String, Number]
}
},
inject: ["eventBus"], //注入 eventBus
data() {
return {
active: false
};
},
created() {
this.eventBus.$on("update:selected", newSelected => {
this.active = this.name === newSelected;
}); //接收消息,如果newselected 和我的 name 相同,那么我就被选中了
},
methods: {
onClick() {
this.eventBus.$emit("update:selected", this.name);
//发布消息,如果点击了我,我就告诉大家,我被选中了
}
}
};
</script>

<style>
.active {
color: red;
}
</style>
# tab-pane.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div v-if="active" class="pane">
<slot/>
</div>
</template>

<script>
export default {
name: "tab-pane",
props: {
name: {
type: [String, Number]
}
},
data() {
return {
active: false
};
},
inject: ["eventBus"],//注入 eventBus
created() {
this.eventBus.$on("update:selected", newSelected => {
this.active = this.name === newSelected;
});
//接收消息,如果newselected 和我的 name 相同,那么我就被选中了
}
};
</script>

<style>
.pane {
color: red;
}
</style>

# 灵活运用 provide inject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//father.vue
export default {
name:'father',
data(){
return {
someThing:'father'
}
},
provide(){
return {
father:this
}
}
}
1
2
3
4
5
6
7
8
9
10
//son.vue
export default {
name:'son',
inject:['father'],
methods:{
onClick(){
this.father.someThing = 'son'
}
}
}

关于JavaScript日期的一些小技巧

最近在做小程序,要做一些关于日期方面的计算,于是整理下来,方便下次使用

1. 获得年月日

1
2
3
4
5
6
7
8
9
10
11
12
function newDateInit() {
let newDate = new Date();
let month = newDate.getMonth();
if (month < 10) {
month = "0" + month; //补齐
}
day = newDate.getDate();
if (newDate.getDate() < 10) {
day = "0" + day; //补齐
}
year = newDate.getFullYear();
}

2.计算前一天,后一天

一开始我考虑假如我现在是 1 号,前一天不就是 0 了吗?其实不用考虑这么多,JS 帮我们做好了判断。

1
2
3
4
5
6
7
8
9
var currentDate = new Date();
var currentDay = currentDate.getDate();
var currentMonth = currentDate.getMonth();
var currentYear = currentDate.getFullYear();
//现在要计算明天的日期
var nextDate = new Date(currentYear,currentMonth,currentDay+1);
//前一天的日期
var preDate = new Date(currentYear,currentMonth,currentDay-1);
//下个月,上个月同理

无需考虑边界问题。

3.假如给的是 2018-01-03 这种数值,传回去也要这种类型怎么办?

先用 split 分隔为数组,再转换为 Number ,再进行加减,再转换为 String ,再拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
data() {
return {
currentYear: 2018,
currentMonth: 9,
currentDay: 1,
currentDate: "2018-09-01",
}
},
methods: {
currentDateSplit() { //用 '-'分隔传入数组
let num = [];
let str = [];
let current = this.currentDate.split("-");
current.forEach((item) => {
num.push(parseInt(item));
});
return num;
},
newDateInit(newDate) { // 初始化 新生成的日期
this.currentMonth = newDate.getMonth();
if (this.currentMonth < 10) {
this.currentMonth = "0" + this.currentMonth; //补齐
}
this.currentDay = newDate.getDate();
if (newDate.getDate() < 10) {
this.currentDay = "0" + newDate.getDate(); //补齐
}
this.currentYear = newDate.getFullYear();
},
preDay() { //前一天
let num = this.currentDateSplit();
let newDate = new Date(num[0], num[1], num[2] - 1);
this.newDateInit(newDate);
this.DateJoin();
},
nextDay() { // 后一天
let num = this.currentDateSplit();
let newDate = new Date(num[0], num[1], num[2] + 1);
this.newDateInit(newDate);
this.DateJoin();
this.DateJoin();
},
DateJoin() { //拼接为 'xxxx-xx-xx' 返回去
this.currentDate =
this.currentYear + "-" + this.currentMonth + "-" + this.currentDay;
}
}

如何在mpvue中使用第三方UI组件

最近接触了小程序业务,了解到美团的 mpvue 也可以做小程序。

使用方式也很简单,和 vue 一样, 但是他会自动将 Vue 编译为 小程序 语言。

下面言归正传,介绍一下如何在 mpvue 中使用第三方组件。

  1. 创建 mpvue

    1
    vue init mpvue/mpvue-quickstart my-app
  1. 以 ivew weapp UI 举例 ,进入其 github 地址,下载后将 dist 目录下的所有东西,拷贝到 my-app 下 static 的 ivew (自己创建) 目录。
  1. 在需要使用的页面中创建 main.json。

    1
    2
    3
    4
    5
    6
    {
    "usingComponents": {
    "i-card": "../../../static/iview/card/index",
    "i-button": "../../../static/iview/button/index"
    }
    }
  1. 接下来就可以在 index.vue 中使用该标签了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <template>
    <div>
    <i-card full title="卡片标题" extra="额外内容" thumb="https://i.loli.net/2017/08/21/599a521472424.jpg">
    <view slot="content">内容不错</view>
    <view slot="footer">尾部内容</view>
    </i-card>
    <i-button type="success">按钮</i-button>
    </div>
    </template>

在 Vue Cli 3 中使用 px2rem

# 安装 postcss-px2rem

1
yarn add postcss-px2rem

# 在 vue.config.js 中添加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const px2rem = require('postcss-px2rem')

const postcss = px2rem({
remUnit: 32 //基准大小 baseSize,需要和rem.js中相同
})

module.exports = {
css: {
loaderOptions: {
postcss: {
plugins: [
postcss
]
}
}
}
}

# 添加 rem.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 基准大小
const baseSize = 32
// 设置 rem 函数
function setRem() {
// 当前页面宽度相对于 750 宽的缩放比例,可根据自己需要修改。
const scale = document.documentElement.clientWidth / 750
// 设置页面根节点字体大小
document.documentElement.style.fontSize = baseSize * Math.min(scale, 2) + 'px'
}
// 初始化
setRem()
// 改变窗口大小时重新设置 rem
window.onresize = function() {
setRem()
}

# 引入 rem.js

  • 方法一:直接在 index.html 中写入语句

  • 方法二:将 rem.js 放入 public 文件夹 , 在 main.js 中添加以下语句

    1
    2
    3
    4
    5
    ;(function(){
    var rem = document.createElement('script');
    rem.src = './rem.js';
    document.body.appendChild(rem)
    })()

Array--方法

1.Array

1.join

1
2
var array = [1,2,3]
array.join('-') // 1-2-3

猜测一下源码

1
2
3
4
5
6
7
8
Array.prototype.join = function(char){
let result = this[0] || '';
let length = this.length;
for( let i = 1 ; i < length ; i++ ){
result += char + this[i]
}
return result
}

2.slice

1
array.slice(benginIndex,endIndex)

猜测一下源码

1
2
3
4
5
6
7
8
9
Array.prototype.slice = function( begin , end ){
begin = begin || 0 ;
end = end || this.length;
let result = [];
for(let i = begin ; i < end ; i++){
result.push(this[i])
}
return result
}

于是可以使用slice将伪数组转化为数组

1
2
3
4
var arraylike = 'abcde';
Array.prototype.slice.call(arraylike) //['a','b','c','d','e']
//或者
[].slice.call(....)

ES6新方法 伪数组转化为数组

Array.from(arraylike)

P.S. 伪数组原型链里面没有 Array.prototype,所以他就没有 join slice等方法

3.sort

1
2
var array = [1,3,2]
array.sort() //[1,2,3]

猜测一下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Array.prototype.sort = function (fn){
let defaultfn = (a,b)=>{ return a-b }
fn = fn|| defaultfn
var roundcount = this.length; //比较的轮数
for(let i = 0 ; i < roundcount ; i++){
let minIndex = this[i]
for( let k = 0 ; k < this.length ; k++){
if(fn.call( null , this[i] , this[k]) < 0 ){ // 决定了谁大谁小
[ this[i] , this[k] ] = [ this[k] , this[i] ] //es6新语法,交换值
}
}
}
return this
}

4.foreach

猜测一下源码

1
2
3
4
5
6
7
Array.prototype.foreach = function(fn){
for(let i = 0 ; i < this.length ; i++){
if(i in this){
fn.call(undefined , this[i] , i , this) // 三个参数代表了 item index array
}
}
}

for 和 foreach 的区别

  1. foreach 没有 break
  1. forEach 用到了函数,所以每次迭代都会有一个新的函数作用域;for 循环只有一个作用域 (著名前端面试题)

5.map

猜测一下源码

1
2
3
4
5
6
7
8
9
Array.prototype.map = function(fn){
let result = []
for(let i = 0 ; i < this.length ; i++){
if(i in this){
result[i] = fn.call(undefined , this[i] , i , this) // 三个参数代表了 item index array
}
}
return result
}

map 和 foreach 的区别就是 map 有返回值,所以推荐忘记 foreach

6.filter

猜测一下源码

1
2
3
4
5
6
7
8
9
10
Array.prototype.filter = function(fn){
let result = [];
let temp;
for(let i = 0 ; i < this.length ; i++){
if(temp = fn.call(null , this[i] , i , this)){ //如果temp的到的返回值为 true
result.push(this[i])
}
}
return result
}

所以说 filter 就是一个加了条件的 map

7.reduce

猜测一下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Array.prototype.reduce = function(fn , init){  // 函数 初始值(可以不传)
let result = init;
for(let i = 0 ; i < this.length ; i++ ){
if(i in this){
result = fn.call(undefined , result , this[i] , i , this)
// 后四个参数 当前累计值,当前值,序列,该数组
}
}
return this
}

var a = [1,2,3,4]
a.reduce( (result,currentValue,currentIndex,array)=>{ return result + currentValue } , 0 )
//累加 10

####8.总结

关于 map , filter , reduce之间的关系

用reduce实现map

1
2
3
4
5
6
var array = [1,2,3];
array.map((item)=>{return item+1});
array.reduce((result,item)=>{
result.push(item+1)
return result ;
},[])//result的初始值设置为 []

用reduce实现filter

1
2
3
4
5
6
7
8
var array = [1,2,3,4]
array.filter((item)=>{return item%2 === 0});
array.reduce((result,item)=>{
if(item%2 === 0){
result.push(item)
}
return result
},[])

结论,其实foreach,map,filter都是由reduce实现,reduce是最关键的。

2.underscore.js

1.underscore是什么

underscore是一个集合操作的库(数组和对象)

JavaScript中的异步

异步—代码烧脑的原因

1.同步和异步

同步:等待结果

异步: 不等待结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//同步
function sleep(seconds){
var a = new Date();
while(new Date() - a < seconds * 1000){

}
return
}
console.log(1);
sleep(3);
console.log('wake up');
console.log(2);
//运行结果 1 wake up 2
// 同步是需要等待的,但是 JavaScript 没有这一项功能,所以只能让他不停的工作,然后到达3秒钟
1
2
3
4
5
6
7
8
9
10
// 异步
function sleep(seconds,fn){
setTimeout(fn,seconds * 1000); // 实际上是在设置闹钟,让浏览器 xx秒 后执行 fn
return;
}
console.log(1);
sleep(3,()=>{console.log('wake up ')});
console.log(2)
//运行结果 1 2 wake up
// 为什么 setTimeout 可以模拟异步? 因为 setTimeout 方法是浏览器提供并负责执行的,JS告诉浏览器3S以后执行fn,然后它就可以去干下一步事情了。

可以看出,使用异步,JS空闲了很多。

但是注意,JS空闲的时间,浏览器的计数器一直在工作。

2.前端经常用到的异步

1
2
var w = document.getElementsByTagName('img')[0].width;
console.log(w) // 0

明明有图片,为什么宽度为0呢?

因为图片加载需要时间。

例子

注意把 Network 的缓存禁用,可以看到效果

解决方案

在图片加载完成后会有一个 onload事件

1
2
3
4
var img = document.getElementsByTagName('img')[0];
img.onload = function(){
console.log(img.width) //400
}

3.遍历的异步

1
2
3
4
5
6
7
8
9
10
11
var li_List =document.querySelectorAll('li');
for(var i = 0;i<li_List.length;i++){
li_List[i].onclick = function(){
console.log(i)
}
}
//期望值:点击哪一个 li , 就会出现该 li 的序号
//结果:打印出的结果会全部都是 n+1
//原因:var i= 0,变量声明提升,会导致循环里用的都是一个 i ,i 最后循环结果会变为 n+1
//解决方法: let i = 0 ,let 在每次循环的时候都会制造一个分身,绑定到该 li_List[i]上
//哪里的异步? li_List[i].onClick = function(){} JS没有功夫等你触发 onclick 事件,直接进行了下一步 i++

例子

4.AJAX中的异步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let requsest = $.ajax({
url:'.',
async:false
})
console.log(request.responseText);
//同步的ajax , 在请求过程中,JS会一直等待响应,非常浪费资源

$.ajax({
url:'.',
async:true,
success:function(res){
console.log(res)
}
})
console.log('OK')
//结果 : OK res
//异步的ajax
// 为什么使用 success 接收请求? 因为这是异步的,使用 var xx = $.ajax({}) 是得不到任何数据的,所以我们告诉浏览器,当接收到请求的时候再用 success 处理

5.异步拿到结果的形式

两种方式

  1. 轮训 :不推荐
  2. 回调 :推荐

回调的形式

1.nodejs的 error-first 形式

1
2
3
4
5
6
7
fs.readFile('./1.txt',(error,content)=>{
if(error){
//error
}else{
//success
}
})

2.Jquery 的 success-error 形式

1
2
3
4
5
$.ajax({
url:'',
success:()=>{},
error:()=>{}
})

3.Jquery的 done/fail/always形式

1
2
3
4
5
$.ajax({

url:'xxx'

}) . done( ()=>{} ) . fail( ()=>{} ).always( ()=>{} )

4.Promise 的 then 形式

1
2
3
4
5
axios({

url:'xxxx'

}).then( ()=>{ success } ,()=>{ fail }).then(()=>{})

6.Promise的错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
axios({
url:'.',
async:true
}).then((x)=>{
console.log('succ1')
return 'aaaaa'
},(y)=>{
console.log('err1')
}).then((x)=>{
console.log('succ2',x) // succ2 aaaa
},(y)=>{
console.log('err2')
console.log(y)
})

succ1和err1中的代码任意一个有错误,都由err2来处理 , err2的第一个参数可以接受错误值

succ1成功,转入succ2

succ1 return 的值会传入 succ2的参数

手动失败 return Promise.reject('xxx')

catch可以放在最后搞定之前没写 失败函数的then ,但是 .catch((err)=>{}) === .then(undefined,(err)=>{})

例子

7.手写一个Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function ajax(){
return new Promise((resovle,reject)=>{
//做事情
// 成功resovle
// 失败 reject
})
}
function buy(){
return new Promise((res,rej)=>{
setTimeout(()=>{
res('apple')
},2000)
})
}
buy().then((res)=>{console.log(res)},(err)=>{console.log(err)})

例子

8.async/await

以同步的方式写异步

1
2
3
4
5
6
7
8
function buy(){
return new Promise((res,rej)=>{
setTimeout(()=>{
res('apple')
},2000)
})
}
var res = await buy();

封装和继承

1.封装和继承

​ i 封装

​ 为了隐藏细节

​ ii 继承

​ 为了复用代码

​ iii 多态

​ 灵活

####1.1原型链(对象与对象之间的关系)

1
2
var obj1 = {name:'1'};
window.Object.prototype === obj.__proto__; //true

原型链

上面的原型链图形象的表示了他们之间的关系,说白了就是大家都共用同一个 “ 仓库” , 所有对象都共用 Object的原型,他们各自又有自己的 “ 仓库”

arr1.__proto__ === window.Array.prototype 自己的 Array 仓库,里面有 join ,splice 等方法

arr1.__proto__.__proto__ === window.Object.prototype Object的仓库,里面有 toString , valueOf 等方法

Object 是所有对象的最底层的原型。

1.2this(对象与函数之间的关系)

函数: 可执行代码组成的一种对象

所有使用 function声明的函数都只有两个参数 this 和 arguments,

一定要记住

1.参数的值只有传参的时候才能确定

2.this 是call()的第一个参数

=> this 的值只有在传参的时候才能确定

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
name:'mayun',
child:{
name:'sicong',
sayName:function(){console.log(this.name)}
}
}

obj.child.sayName() //sicong this === obj.child 就是函数的 点 前面的所有
obj.child.sayName.call(obj) // mayun 因为 this === obj obj.name === 'mayun'
//因此我们也可以看出 function(){} 跟这个 sayName 一点关系都没有, sayName 只是存了 function(){} 的地址而已
//更直观点
obj.child.sayName.call({name:'wtf'}) //wtf

来几个题试试

1
2
3
4
5
6
7
8
9
function f(){ console.log(this) } 
function f(){ console.log(this) } f()
function f(){ ‘use strict’ console.log(this) } f()
var obj = {sayThis:f} ; obj.sayThis()
var obj = {sayThis:f} ; obj.sayThis.call()
button.onclick = function(){console.log(this)}
$('#test').on('click',function(){console.log(this)})
$('#ul1').on('click','li',function(){console.log(this)})
varvm = new Vue({data:function(){console.log(this)}})

答案

1
2
3
4
5
6
7
8
9
1.不确定,因为没有传参啊	
2.window
3.undefined(use strict)
4.obj // . 前面的
5.window/undefined
6.button //浏览器保证的,只要点击了,this === button
7.$('#test') //JQ文档写的 this指向了当前正在执行事件的元素
8.li //JQ文档 this 代表了与 selector 相匹配的元素
9.new 出来的对象 vm //文档说的,因为它call了new 出来的东西

那么这么蛋疼的this为什么还要用它,我们要用箭头函数!!

箭头函数自己没有 thisarguments

它的 this 是外面的 this

1
2
3
4
var f = ()=>{console.log(this)} f() //window 
var obj = { sayThis : ()=>{console.log(this)}} ; obj.sayThis
// window ( 如果是function 那就是obj了)
//因为obj的this是window,所以里面的也是window, ()=>{} 不接收this,也不修改this,外面是什么,它就是什么

1.3 bind是什么

先看下MDN的定义

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

做几个实验

//
1
2
3
4
5
6
function fn1(){ console.log(this) } //this是啥?不知道
fn1()// 哦,this 是 window
fn1.call(1) // this又是 Number(1) 了
//我想写一个直接就知道this是什么的函数
function fn2(){ fn1.call(1) } //this 肯定是 Number(1) ,我都钦定了,但是这么写有点啰嗦,JS帮我整个方法
var fn3 = fn1.bind(1) ; fn3() // Number(1)

上面我们说到了,this 其实只是函数的第一个参数而已,第二个参数是arguments,我能不能也钦定一下子?

1
2
3
4
5
6
7
function fn1(){ console.log(this) ;console.log(arguments) } 
fn1() // window []
function fn2(){ fn1.call(1,'a','b','c') }
fn2()// Number(1) ['a','b','c']
//那这样的话,bind是不是也能指定arguments呢?
var fn3 = fn1.bind(1,'a','b','c');
fn3()// Number(1) ['a','b','c']

所以说bind()就是返回了一个函数,这个函数 call 原来的函数,而call的值你可以自己指定

1.4 new 是什么

1.4.1 为什么要有 new ,我们的需求

​ 我们想要 批量创建对象

1.4.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Solider(name){
this.ID = i
this.name = name || 'no name';
this.life = 42
}
Solider.prototype.兵种 = '解放军';
Solider.prototype.攻击力 = 100;
Solider.prototype.攻击 = function (){ };

var soliders = [];
for(var i = 0 ; i < 100 ; i++){
soliders.push( new Solider() )
}
console.log(soliders)

习俗:

  1. 构造函数首字母大学

    2. 构造函数可以省略掉 create
    3. 如果构造函数没有参数,可以省略括号。
    

面向对象

继承
1
2
3
4
5
6
7
8
//prototype和 __proto__的区别	
//prototype是函数特有的属性
function a(){};
a.prototype
//访问 __proto__需要 a.prototype.__proto__
var a = 1;
a.__proto__ = Number{};
a.prototype = undefined

ES5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Human(options){
//this = {}
//this.__proto__ = Human.prototype JS之父的关怀。将这个空对象this的公有属性连上了 Human的公有属性
this.name = options.name;
this.肤色 = options.肤色 //特有属性
//return this 都是 JS之父的关怀
}
Human.prototype.eat = function(){}; //公有属性

function Solider(options,id){
Human.call(this,options) //用Solider里的this,放到 Human 里面用,这样Soilder就有了 name 和 say 的特有属性
this.id = id;
this.life = 42;
}
Solider.prototype.attack = function(){};
//Solider.prototype.__proto__ = Human.prototype; 生产环境不能用
//但是 new 可以用,看看上面JS之父的关怀,我们换一种写法
//兼容IE
function fakeHuman(){}
fakeHuman.prototype = Human.prototype;
Solider.prototype = new fakeHuman()
//Solider.prototype === fakeHuman.prototype === Human.prototype;

//不兼容IE
Solider.prototype = Object.create(Human.prototype);

var s = new Solider({name:'小小',肤色:'yellow'},10)

ES6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Human {
constructor(options) {
this.name = options.name
this.肤色 = options.肤色
}
eat() {}
}
class Solider extends Human {
constructor(options, id) {
super(options)
this.id = id,
this.life = 42
}
attack() {}
}
var s = new Solider({name:'小小',肤色:'yellow'},10)

作业题1

要求不使用 class,完成如下需求:

写出一个构造函数 Animal
输入为一个对象,如 {物种:’xxx’}
输出为一个新对象,该对象的共有属性为 {物种:’xxx’,行动: function(){}},没有自有属性
再写出一个构造函数 Human
Human 继承 Animal
输入为一个对象,如 {name: ‘Frank’, birthday: ‘2000-10-10’}
输出为一个新对象,该对象自有的属性有 name 和 birthday,共有的属性有物种、行动和使用工具
在写出一个构造函数 Asian
Asian 继承 Human
输入为一个对象,如 {city: ‘北京’, name: ‘Frank’, birthday: ‘2000-10-10’ }
输出为一个新对象,改对象自有的属性有 name city 和 bitrhday,共有的属性有物种、行动和使用工具和肤色

最后一个新对象是 Asian 构造出来的
Asian 继承 Human,Human 继承 Animal
注意,不要使用 class 关键字,使用课上讲的原型链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function Animal() {

}
Animal.prototype.行动 = function () {};

function Human(options) {
this.name = options.name;
this.birthday = options.birthday;
}

// Human.prototype = Object.create(Animal.prototype)
function fake(){}
fake.prototype = Animal.prototype;
Human.prototype = new fake();
//关键点 此时Human.prototype.constructor指向function Animal(){}
//因此需要改变constructor指向Human
Human.prototype.constructor = Human
Human.prototype.物种 = '人类';
Human.prototype.使用工具 = function () {};

var s = new Human({
name: 'Frank',
birthday: '2000-10-10'
})

function Asian(options) {
//关键点,将Human的特有属性,放到Asian来
Human.call(this, options)
this.city = options.city;

}
Asian.prototype.肤色 = 'yellow'
//继承的关键点
// Asian.prototype = Object.create(Human.prototype)
function fake2(){}
fake2.prototype = Human.prototype;
Asian.prototype = new fake2();
//修改 constructor 的指向
Asian.prototype.constructor = Asian
Asian.prototype.肤色 = 'yellow'
var h = new Asian({
city: '北京',
name: 'Frank',
birthday: '2000-10-10'
})

作业题2

要求使用 class,完成如下需求:

写出一个构造函数 Animal
输入为一个对象,如 {物种:’xxx’}
输出为一个新对象,该对象的共有属性为 {行动: function(){}},自有属性为 {物种:’xxx’}
再写出一个构造函数 Human
Human 继承 Animal
输入为一个对象,如 {name: ‘Frank’, birthday: ‘2000-10-10’}
输出为一个新对象,该对象自有的属性有 name、物种和 birthday,共有的属性有行动和使用工具
在写出一个构造函数 Asian
Asian 继承 Human
输入为一个对象,如 {city: ‘北京’, name: ‘Frank’, birthday: ‘2000-10-10’ }
输出为一个新对象,改对象自有的属性有 name city 物种 肤色和 bitrhday,共有的属性有行动和使用工具

最后一个新对象是 Asian 构造出来的
Asian 继承 Human,Human 继承 Animal
注意,要使用 class 关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Animal{
constructor(){

}
行动(){}
}

class Human extends Animal{
constructor(options){
super()
this.name = options.name
this.birthday = options.birthday
}
使用工具(){}
}

class Asian extends Human{
constructor(options){
super(options)
this.city = options.city
this.物种 = '人类'
this.肤色 = '黄色'
}
}

var h = new Asian({city: '北京', name: 'Frank', birthday: '2000-10-10' })

制作一个基于Vue的Tabs轮子

tabs轮子制作

1.先简单实现一下

1
<div id='app'></div>
1
.active{background:red}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var vm = new Vue({
el: '#app',
data() {
return {
selectedTab: 1
}
},
template: `
<div class="tabs">
<ol class="nav">
<li @click = 'selectedTab = 1' :class ='{ active: selectedTab === 1 }' >tab1</li>
<li @click = 'selectedTab = 2' :class ='{ active: selectedTab === 2 }' >tab2</li>
</ol>
<ol class="panels">
<li :class = '{active : selectedTab === 1}' >content1</li>
<li :class = '{ active: selectedTab === 2 }' >content2 </li>
</ol>
</div>
`
})

很简单,但是没有复用性。

2.轮子的实现

  • 思路:用户如何使用我这个组件呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    selectedTab: 1
    }
    },
    template: `
    <tabs>
    <tabs-navs>
    <tabs-navs-item>1</tabs-navs-item>
    <tabs-navs-item>2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item>content 1</tabs-panels-item>
    <tabs-panels-item>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })

    差不多这样调用,其他的不用管,一个tabs就出来了

  • 开始写,我先声明这些tabs的组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    Vue.component('tabs',{})
    Vue.component('tans-navs',{})
    Vue.component('tabs-navs-item',{})
    Vue.component('tans-panels',{})
    Vue.component('tabs-panels-item',{})
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    selectedTab: 1
    }
    },
    template: `
    <tabs>
    <tabs-navs>
    <tabs-navs-item>1</tabs-navs-item>
    <tabs-navs-item>2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item>content 1</tabs-panels-item>
    <tabs-panels-item>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })

    现在有个问题,我怎么知道要选中那个tab呢?用1,2不大好,所以,改为字符串,并且给下面加上name

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    Vue.component('tabs',{
    props: ['selectedTab'],
    template: `
    <div class='tabs'>
    <slot/> //注意有子元素的话,添加 插槽
    </div>
    `
    })
    Vue.component('tans-navs',{
    template: `
    <div class='tabs-navs'>
    <slot/>
    </div>
    `
    })
    Vue.component('tabs-navs-item',{
    props: ['name'],
    template: `
    <div class='tabs-navs-item'>
    <slot/>
    </div>
    `
    })
    Vue.component('tans-panels',{
    template: `
    <div class='tabs-panels'>
    <slot/>
    </div>
    `
    })
    Vue.component('tabs-panels-item',{
    props: ['name'],
    template: `
    <div class='tabs-panels-item'>
    <slot/>
    </div>
    `
    })
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    selectedTab: 'tab1'
    }
    },
    template: `
    <tabs :selectedTab = 'tab1'>
    <tabs-navs>
    <tabs-navs-item name = 'tab1' >1</tabs-navs-item>
    <tabs-navs-item name = 'tab2' >2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item name = 'tab1'>content 1</tabs-panels-item>
    <tabs-panels-item name = 'tab2'>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })

    这样就初见雏形了

  • 下面就是重点了,孙子怎么知道爷爷的 selectedTab的值呢?

    答案是通过孙子的爸爸,爸爸获得selectedTab,然后再给孙子,

    问题又来了,爸爸怎么从爷爷那里获得呢?

    孙子,爸爸自己定义一个属性selectedTab,再写一个methods: SelectedTab(tab),爷爷遍历所有的爸爸,然后调用爸爸的SelectedTab(tab)把爷爷的selectedTab传给爸爸的selectedTab,同理,爸爸再用同样的方式传给孙子。

    P.S 爷爷:tabs , 爸爸 : tabs-navs , 孙子: tabs-navs-item

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    Vue.component('tabs', {
    props: ['selectedTab'],
    template: `
    <div class='tabs'>
    <slot/>
    </div>
    `,
    mounted() {
    this.$children.forEach((vm) => { //遍历所有的孩子
    if (vm.$options.name === 'tabs-navs') { //如果是爸爸
    console.log('爷爷的selectedTab是'+this.selectedTab)
    vm.SelectedTab(this.selectedTab) //通过爸爸的selectedTab方法,传给儿子
    }else if(vm.$options.name === 'tabs-panels'){
    vm.SelectedTab(this.selectedTab)
    }
    })
    }
    })
    Vue.component('tabs-navs', {
    data() {
    return {
    selectedTab: undefined //爸爸自己有一个selectedTab属性
    }
    },
    template: `
    <div class='tabs-navs'>
    <slot/>
    </div>
    `,
    methods: {
    SelectedTab(tab) {
    this.selectedTab = tab //通过这个方法,从爷爷那里获取selectedTab
    console.log('爸爸获取到了爷爷的selectedTab是'+this.selectedTab)
    this.$children.forEach((vm)=>{ //同样的方法传给孙子
    if(vm.$options.name === 'tabs-navs-item'){
    vm.SelectedTab(this.selectedTab)
    }
    })
    }
    }
    })
    Vue.component('tabs-navs-item', {
    props: ['name'],
    data () {
    return {
    selectedTab:undefined
    }
    },
    template: `
    <div class='tabs-navs-item' :class = '{active}'> //通过计算属性,判断active
    <slot/>
    </div>
    `,
    computed: {
    active() {
    return this.selectedTab === this.name //通过计算属性,判断active
    }
    },
    methods:{
    SelectedTab(tab){
    this.selectedTab = tab
    console.log('儿子获取到了从爸爸的selected是'+this.selectedTab)
    }
    }
    })
    Vue.component('tabs-panels', {
    data () {
    return {
    selectedTab:undefined
    }
    },
    template: `
    <div class='tabs-panels'>
    <slot/>
    </div>
    `,
    methods:{
    SelectedTab(tab){
    this.selectedTab = tab;
    this.$children.forEach((vm)=>{ //同样的方法传给孙子
    if(vm.$options.name === 'tabs-panels-item'){
    vm.SelectedTab(this.selectedTab)
    }
    })
    }
    }
    })
    Vue.component('tabs-panels-item', {
    props: ['name'],
    data () {
    return {
    selectedTab:undefined
    }
    },
    template: `
    <div class='tabs-panels-item' :class = '{active}'>
    <slot/>
    </div>
    `,
    computed:{
    active(){
    return this.selectedTab === this.name
    }
    },
    methods:{
    SelectedTab(tab){
    this.selectedTab = tab
    }
    }
    })
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    selectedTab: 'tab1'
    }
    },
    template: `
    <tabs :selectedTab = 'tab1'>
    <tabs-navs>
    <tabs-navs-item name = 'tab1' >1</tabs-navs-item>
    <tabs-navs-item name = 'tab2' >2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item name = 'tab1'>content 1</tabs-panels-item>
    <tabs-panels-item name = 'tab2'>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })

    这样,selectedTab 整个传递的线路,我们就打通了。tabs-panels的方法同理

  • 下面也是重点,我点击tabs-navs-item的时候,孙子要告诉爷爷该切换了,但孙子不能直接告诉爷爷,先告诉爸爸,爸爸再告诉爷爷。

    注意:Vue没有冒泡,孙子触发的$emit事件,爸爸是不知道的,所以要用爸爸监听$on孙子,爷爷监听$on爸爸,然后爷 爷再把数据传出去update()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    //孙子
    onClick(){
    this.$emit('update:selectedTab', this.name)
    //孙子传出去。
    //name = 'tab1'的作用在这里体现,之前的vm.$options.name其实是 component 的 name
    }
    //爸爸
    mounted() {
    this.$children.forEach((vm) => {
    if (vm.$options.name === 'tabs-navs-item') {
    vm.$on('update:selectedTab', (e) => { //监听儿子的 update:selectedTab
    console.log('爸爸知道了你要更新selectedTab是' + e)
    this.$emit('update:selectedTab', e) //爸爸传给爷爷需要改的数
    })
    }
    })
    }
    //爷爷
    mounted() {
    this.$children.forEach((vm) => { //遍历所有的孩子
    if (vm.$options.name === 'tabs-navs') {
    console.log('爷爷的selectedTab是' + this.selectedTab)
    vm.SelectedTab(this.selectedTab) //通过儿子的selectedTab方法,传给儿子
    //爷爷监听爸爸的update,更新selectedTab属性,更新操作在updatde里面做
    vm.$on('update:selectedTab',(e)=>{
    console.log('爷爷知道了爸爸给我的selectedTab是' + e)
    this.$emit('update:selectedTab',e)
    })
    } else if (vm.$options.name === 'tabs-panels') {
    vm.SelectedTab(this.selectedTab)
    }
    })
    },
    updated() { //更新selectedTab
    this.$children.forEach((vm) => {
    if (vm.$options.name === 'tabs-navs') {
    vm.SelectedTab(this.selectedTab)
    } else if (vm.$options.name === 'tabs-panels') {
    vm.SelectedTab(this.selectedTab)
    }
    })
    }

    //然后体现在 template 里 ,使用 .sync 修饰符
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    value: 'tab1'
    }
    },
    template: `
    <tabs :selectedTab.sync = 'value' > // 在这里更新
    <tabs-navs>
    <tabs-navs-item name = 'tab1' >1</tabs-navs-item>
    <tabs-navs-item name = 'tab2' >2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item name = 'tab1'>content 1</tabs-panels-item>
    <tabs-panels-item name = 'tab2'>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })