课程效果
项目简介
本课程将以 evernote 云笔记 mac 客户端版的 ui 做原型,做一款线上的云笔记 webapp 产品。产品包括登录、注册、笔记本的创建、修改、删除,笔记的markdown编辑、删除到回收站、markdown 预览、回收站管理等功能。采用前后端分离的开发方式,本课程只负责前端部分的开发。
前置知识
var-let-const
- var 可声明前置
- 声明前置:变量可以在声明之前使用,值为 undefined
在JavaScript中,变量声明前置是指在代码执行过程中,JavaScript解析器会在代码执行之前将变量声明提升到作用域的顶部。这意味着你可以在变量声明之前使用变量,而不会引发错误。
例子:
1 2
| console.log(x); var x = 5;
|
在上面的例子中,尽管变量x
在打印语句之前被声明,但由于变量声明前置的特性,代码不会引发错误,而是打印出undefined
。这是因为在执行代码之前,var x
会被提升到作用域的顶部,但是变量的赋值操作仍然是按照代码的实际顺序执行的。
需要注意的是,只有变量声明会被提升,而变量赋值并不会被提升。因此,在使用变量之前进行赋值操作仍会导致变量值为undefined
。
1 2 3
| console.log(x); var x = 5; console.log(x);
|
1 2 3 4
| a = 3; var a; console.log(a); var a = 3;
|
- let 不可声明前置
1 2 3
| a = 3; let a; let a = 3;
|
- let 不可重复声明
1 2 3
| let a = 3; let a = 4; let a = 5;
|
- let 有块级作用域
1 2 3 4
| if (true) { let a = 3; } console.log(a);
|
1 2 3 4
| for (let i = 0; i < 3; i++) { console.log(i); } console.log(i);
|
- IIFE 的替换
1 2 3 4 5
| (function () { var a = 3; })();
console.log(a);
|
1 2 3 4 5
| { let a = 3; }
console.log(a);
|
暂时性死区
- 在let声明变量之前都是该变量的死区, 在死区内该变量是不能被访问的
1 2 3 4 5
| var a = 3; if (true) { console.log(a); let a = 4; }
|
IIFE指的是Immediately Invoked Function Expression(立即调用的函数表达式)。它是一个在定义后立即执行的 JavaScript 函数。
IIFE的一般语法如下:
上述语法中,在函数定义的末尾加上一对圆括号 ()
,表示立即调用该函数。
IIFE 的主要作用有两个:
- 创建一个独立的作用域:在 IIFE 内部定义的变量和函数在外部是不可访问的,从而避免变量冲突和污染全局命名空间。
- 执行一些初始化的操作:可以在 IIFE 内部执行某些操作,并且不会暴露在全局作用域中。
示例:
1 2 3 4 5 6
| (function () { var x = 10; console.log(x); })();
console.log(x);
|
在上述示例中,IIFE 内部定义了变量 x
,并且可以在函数内部访问该变量。而在外部,由于 x
是在 IIFE 内部声明的,因此无法访问到该变量,会报错。
IIFE 还可以接收参数,并在调用时传递参数进去,以便在函数内部使用。这样可以进一步扩展 IIFE 的功能。
1 2 3
| (function (name) { console.log("Hello, " + name); })("Alice");
|
在上述示例中,IIFE 接收一个参数 name
,并在调用时传递了参数值 "Alice"
。在函数内部,通过使用参数 name
,输出了相应的消息。
- const 声明的常量不可改变
1 2 3
| const obj = {a: 1} obj.a = 2 obj = {a: 2}
|
- 适用于let的同样适用于const
解构赋值
- 数组的解构
1 2 3 4 5 6 7
| let [a,b,c] = [1,2,3] console.log(a,b,c)
let [a,[b],c] = [2,[4],6] console.log(a,b,c)
let [a] = 1
|
- 默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let [a,b,c=3] = [1,2] console.log(a,b,c)
let [a,b,c=3] = [1,2,undefined] console.log(a,b,c)
let [a,b,c=3] = [1,2,null] console.log(a,b,c)
let [a,b=2] = [3,4] console.log(a,b)
let [a,b=2] = [3,undefined] console.log(a,b)
let [a,b=2] = [3,null] console.log(a,b)
|
数组对应对值有没有? 如果没有(数组对没有指的是undefined), 就用默认值 如果有, 就用对应的值
1 2
| let [a=2,b=3] = [undefined,null] console.log(a,b)
|
1 2
| let [a=1, b=a] = [2] console.log(a,b)
|
- 对象的解构赋值
前置知识
1 2 3 4
| let [name, age] = ["hunger", 3] let p1 = {name, age}
let p2 = {name: name, age: age}
|
解构范例
1 2
| let {name, age} = {name: "hunger", age: 3} console.log(name, age)
|
以上代码等同于
1 2
| let {name: name, age: age} = {name: "hunger", age: 3} console.log(name, age)
|
- 默认值
1 2
| let {name, age=3} = {name: "hunger"} console.log(name, age)
|
- 函数结构
1 2 3 4 5
| function add([x,y]=[1,2]){ return x + y } add() add([3,4])
|
1 2 3 4
| function sum ({x, y} = {x: 0, y: 0}, {a = 1, b = 2}){ return [x + a, y + b]; } sum({x:1, y:2}, {a:2})
|
- 作用
1 2 3
| let [x, y] = [1, 2]; [x, y] = [y, x]; console.log(x, y);
|
1 2 3 4
| function ajax({url, type="GET"}){
} ajax({url: "xxx"})
|
字符串-函数-数组-对象
字符串
- 多行字符串
- 字符串模板
1 2 3
| let name = "hunger" let age = 3 let str = `hello, ${name}, age is ${age}`
|
- 字符串查找
1 2 3 4
| let str = "hello world" str.includes("hello") str.startsWith("hello") str.endsWith("world")
|
数组
- 扩展运算符
1 2
| let arr = [1,2,3] console.log(...arr)
|
- 合并数组
1 2 3 4
| let arr1 = [1,2,3] let arr2 = [4,5,6] let arr3 = [...arr1, ...arr2] console.log(arr3)
|
- 数组克隆
1 2 3
| let arr1 = [1,2,3] let arr2 = [...arr1] console.log(arr2)
|
- 函数参数的扩展
1 2 3 4 5 6 7 8 9
| function sort(...arr){ console.log(arr.sort()) } sort(3,2,1)
function max(arr){ return Math.max(...arr) } max([3,4,1])
|
- 类数组对象转数组
1 2 3 4 5 6 7
| let ps = document.querySelectorAll("p") Array.from(ps).forEach(function(p){ console.log(p.innerText) }) [...ps].forEach(function(p){ console.log(p.innerText) })
|
函数
- 默认值
1 2 3 4 5
| function sayHi(name='jirengu'){ console.log(`hi, ${name}`) } sayHi() sayHi("Zkeq")
|
1 2 3 4
| function fetch (url, {body="", method="GET", headers={}}){ console.log(method) } fetch("http://www.baidu.com", {})
|
以下两种写法的区别?
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
| function m1({x=0, y=0} = {}) { return [x, y]; }
function m2({x, y} = { x: 0, y: 0 }) { return [x, y]; }
m1() m2()
m1({x: 3, y: 8}) m2({x: 3, y: 8})
m1({x: 3}) m2({x: 3})
m1({}) m2({})
m1({z: 3}) m2({z: 3})
ex1: 调用函数需要传入一个对象, 如果不传, 就用默认值 `{}`, 默认值对象里面都是 undefined, 所以属性使用初始值 ex2: 调用函数需要传入一个对象, 如果不传, 就用默认值 `{x: 0, y: 0}`, 如果传了对象, 就用传入的对象
|
- 箭头函数
1 2 3 4 5 6 7
| let f = v => v + 1 f(2)
var f = function(v){ return v + 1 } f(2)
|
1 2 3 4 5 6 7
| var f = () => 5 f()
var f = function(){ return 5 } f()
|
1 2 3 4 5 6 7
| var sum = (num1, num2) => num1 + num2 sum(1,2)
var sum = function(num1, num2){ return num1 + num2 } sum(1,2)
|
1 2 3 4 5 6 7 8 9
| var arr = [1,2,3] var arr2 = arr.map(v=>v*v) arr2
var arr = [1,2,3] var arr2 = arr.map(function(v){ return v*v }) arr2
|
箭头函数里的 this
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); }
function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); }
|
对象
1 2 3
| var name = 'jirengu' var age = 3 var people = {name, age}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let app = { init(){ console.log("init") }, start(){ console.log("start") } } app.init()
let app = { init: function(){ console.log("init") }, start: function(){ console.log("start") } }
|
模块化
- export
写法1
1 2 3 4 5 6 7 8 9 10
| export var a = 1 export function add(x,y){ return x + y } export class Person{ constructor(name){ this.name = name } }
|
1 2 3 4 5
| import {a, add, Person} from "./a.js" console.log(a) console.log(add(1,2)) console.log(new Person("hunger"))
|
1 2 3 4 5
| import * as a from "./a.js" console.log(a.a) console.log(a.add(1,2)) console.log(new a.Person("hunger"))
|
1 2 3
| import {a as b} from "./a.js" console.log(b)
|
写法2
1 2 3 4 5 6 7 8 9 10 11
| var a = 1 function add(x,y){ return x + y } class Person{ constructor(name){ this.name = name } } export {a, add, Person}
|
使用
1 2 3 4 5
| import {a, add, Person} from "./a.js" console.log(a) console.log(add(1,2)) console.log(new Person("hunger"))
|
1 2 3 4 5
| import * as a from "./a.js" console.log(a.a) console.log(a.add(1,2)) console.log(new a.Person("hunger"))
|
1 2 3
| import {a as b} from "./a.js" console.log(b)
|
写法3
1 2 3 4
| export function getName(){} export function getAge(){}
|
1 2 3
| import {getName, getAge} from "./a.js" getName()
|
写法4
1 2 3 4
| function getName(){} function getAge(){} export {getName, getAge}
|
1 2 3
| import {getName, getAge} from "./a.js" getName()
|
写法5
1 2 3
| export default function(){ console.log("hello") }
|
1 2 3
| import foo from "./a.js" foo()
|
类和继承
- 构造函数
1 2 3 4 5 6 7 8 9 10 11
| class Person{ constructor(name){ this.name = name } sayHi(){ console.log(`hi, ${this.name}`) } }
let p = new Person("hunger") p.sayHi()
|
等价于
1 2 3 4 5 6 7 8
| function Person(name){ this.name = name } Person.prototype.sayHi = function(){ console.log(`hi, ${this.name}`) }
let p = new Person("hunger")
|
- 静态方法
1 2 3 4 5 6 7 8
| class EventCenter { static fire() { return "fire"; } static on() { return "on"; } }
|
等价于
1 2 3 4 5 6 7
| function EventCenter(){}; EventCenter.fire = function(){ return "fire"; } EventCenter.on = function(){ return "on"; }
|
- 继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Person{ constructor(name){ this.name = name; } sayHi(){ console.log(`hi, ${this.name}`); } }
class Student extends Person{ constructor(name, number){ super(name); this.number = number; } sayHi(){ console.log(`姓名 ${this.name} 学号 ${this.number}`); } }
|
Vue基础知识
阅读以下 vue 教程,跟随教程手写并运行代码。
- 介绍
- Vue 实例
- 模板语法
- 计算属性和观察者
- class 和 style 绑定
- 条件渲染
- 列表渲染
- 表单输入绑定
- 组件
Vue-router 初体验 && Vue-router 基础
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
|
const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' }
const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ]
const router = new VueRouter({ routes })
const app = new Vue({ router }).$mount('#app')
|
动态路由匹配
1 2 3 4 5 6 7 8 9 10
| const User = { template: '<div>User</div>' }
const router = new VueRouter({ routes: [ { path: '/user/:id', component: User } ] }
|
模式 |
匹配路径 |
$route.params |
/user/:username |
/user/evan |
{ username: 'evan' } |
/user/:username/post/:post_id |
/user/evan/post/123 |
{ username: 'evan', post_id: '123' } |
捕获所有路由或 404 Not found 路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { path: '*' } { path: '/user-*' }
this.$router.push('/user-admin') this.$route.params.pathMatch
this.$router.push('/non-existing') this.$route.params.pathMatch
|
匹配优先级
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:路由定义得越早,优先级就越高。
嵌套路由
Vue.js 添加 进入/离开 & 列表过渡
Vue生命周期图示

选项 / 生命周期钩子
实例生命周期钩子
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
比如 created
钩子可以用来在一个实例被创建之后执行代码:
1 2 3 4 5 6 7 8 9 10
| new Vue({ data: { a: 1 }, created: function () { console.log('a is: ' + this.a) } })
|
也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mounted
、updated
和 destroyed
。生命周期钩子的 this
上下文指向调用它的 Vue 实例。
不要在选项 property 或回调上使用箭头函数,比如 created: () => console.log(this.a)
或 vm.$watch('a', newValue => this.myMethod())
。因为箭头函数并没有 this
,this
会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefined
或 Uncaught TypeError: this.myMethod is not a function
之类的错误。
vm.$nextTick
vm.$nextTick 是 Vue 提供的实例方法之一,它的作用是在下次 DOM 更新循环结束之后执行延迟回调。
Vue 在更新 DOM 时是异步执行的,当你修改了 Vue 实例的数据后,Vue 不会立即更新 DOM,而是将这个更新放入一个队列中,等到下一个事件循环时进行批量更新。
而 vm.$nextTick 方法就是用来在 DOM 更新完毕后执行一些回调函数的。这经常用于以下情况:
当需要操作 DOM 元素的时候,因为通过 vm.$nextTick 方法可以确保我们操作的是最新的 DOM。
当需要等待 DOM 更新的时候执行一些其他逻辑,例如获取元素宽高、计算元素位置等。
下面是一个示例,展示了使用 vm.$nextTick 的常见用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Vue.component('example-component', { template: '<div>{{ message }}</div>', data() { return { message: 'Hello Vue.js' }; }, mounted() { this.message = 'Hello World'; this.$nextTick(function() { console.log(this.$el.textContent); }); } });
|
在上面的示例中,当数据 message 被更新为 ‘Hello World’ 后,我们使用了 vm.$nextTick 方法来确保获取更新后的 DOM 内容。
总结一下,vm.$nextTick 方法允许我们在 DOM 更新之后执行一些回调,以确保我们操作最新的 DOM 或执行一些其他逻辑。
聊一聊Vue 的生命周期?beforeCreate、created、beforeMount、mounted 分别有什么区别?
Vue的生命周期钩子函数指的是在Vue实例从创建、运行到销毁的过程中,会触发的一系列函数。这些函数可以让开发者在不同的阶段添加自己的逻辑代码,以实现更灵活的控制。
在Vue的生命周期中,包括了以下四个阶段:
- beforeCreate(创建前):在实例初始化之前被调用。此时,Vue实例的数据观测、事件和生命周期钩子都尚未初始化。
- created(创建后):在实例创建完成之后被调用。此时,Vue实例已完成数据观测、属性和方法的设置,但是$el属性尚未被创建,因此无法访问到DOM元素。
- beforeMount(挂载前):在挂载元素之前被调用,此时,模板编译已完成,但是还没有将编译后的模板替换到页面上的DOM中。
- mounted(挂载后):在挂载元素之后被调用,此时,Vue实例已经完成了DOM的挂载,可以访问到通过$el属性获取到的DOM元素。
这四个生命周期钩子函数的区别如下:
- beforeCreate:在实例初始化之前被调用,此时Vue实例还没有初始化完成,无法访问到实例的数据和方法。
- created:在实例创建之后被调用,此时Vue实例已经完成了数据观测和属性、方法的设置,但是DOM还没有挂载,无法访问到$el。
- beforeMount:在挂载元素之前被调用,此时编译已完成,但还没有将模板替换到DOM中,可以在此阶段进行一些DOM操作。
- mounted:在挂载元素之后被调用,Vue实例已经完成了DOM的挂载,可以访问到通过$el属性获取到的DOM元素,常用于初始化插件、获取服务器数据等操作。
总的来说,beforeCreate和created主要用于初始化Vue实例的数据和方法,beforeMount和mounted主要用于DOM操作和其他Vue实例的初始化操作。
如何进行不同组件间事件传递?
在 Vue 中,组件之间的事件传递可以通过以下几种方式来实现:
父子组件通信:父组件可以通过 props 向子组件传递数据,并在子组件中使用 $emit
触发自定义事件来通知父组件。父组件可以在子组件上监听这些自定义事件,并通过回调函数获取传递的数据。
子组件向父组件通信:子组件可以通过 $emit
触发自定义事件并传递数据到父组件。父组件可以在子组件上使用 v-on
监听这些自定义事件,并通过回调函数获取传递的数据。
兄弟组件通信:可以使用一个共享的 Vue 实例或者其他的中央事件总线(例如 Vue 的事件系统或者一个全局的事件总线库,比如 EventBus)来实现兄弟组件之间的通信。兄弟组件通过共享的实例或事件总线来触发和监听事件,从而传递信息。
跨级组件通信:如果组件处于不同的层级,可以使用 provide 和 inject 来进行跨级组件通信。父级组件通过 provide 来提供数据,而子孙级组件通过 inject 来注入数据。
这些是 Vue 中常用的几种组件间事件传递方式。具体使用哪种方式取决于你的应用场景和组件的关系。