Joyce


  • Home

  • Archives

  • Tags

  • About

  • Search

vue起步

Posted on 2022-04-25
| Words count in article: 374

vue的两个版本

  • vue有两个版本,即完整版(vue.js)和 非完整版也叫运行时版本(vue.runtime.js)
  • 运行时:用来创建Vue实例、渲染处理虚拟DOM
  • 编译器:用于将模板字符串编译成JS渲染函数的代码
  • 完整版:运行时 + 编译器
  • @vue/cli默认引入的是运行时runtime版本
  • 最佳实践:总是使用runtime非完整版,并配合vue-loader和vue文件
  1. 保证用户体验,用户下载的JS文件体积更小,但只支持h函数
  2. 保证开发体验,开发者可以直接在vue文件里写HTML标签,而不写h函数
  3. 其他工作交给loader来做,vue-loader会引入compiler(编译器),它会将vue文件里的HTML在构建时预编译成h函数,这样既简化写法又省下空间。

    template 与 render 的用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 写法更简单,但需要编译器
    new Vue ({
    template: '<div>{{ hello }}</div>'
    })

    // 写成h函数,不需要编译器
    new Vue ({
    render(h) {
    return h('div', this.hello)
    }
    })

template标签和JS文件中的template

1
2
3
4
5
6
7
// .vue文件中的template标签
<template>
<div id="app">
{{n}}
<button @click="add">+1</button>
</div>
</template>
1
2
3
4
5
6
7
8
9
// .js文件中的template
new Vue ({
template: `
<div>
{{n}}
<button @click="add">+1</button>
</div>
`
})

codeSandbox快速写vue代码

  • 快速开始在线写vue代码,无需安装任何本地依赖 => https://codesandbox.io/s/
  • 不要登录,直接一键create Sandbox选择vue即可

浅析MVC

Posted on 2022-04-21
| Words count in article: 1,358

什么是MVC

MVC包括三个对象,分别是Model、View、Controller,通过将代码进行模块化的分离,以便于提高代码的灵活性和复用性。

  • M-model数据模型,负责与数据相关的任务(包括增删改查),会有一个或多个视图监听该模型,一旦数据发生改变,模型将通知相关视图
  • V-view视图层,是提供给用户操作的界面,当model层的数据发生改变,视图也会相应的进行更新
  • C-controller控制器,起到在不同层面的组织作用。通过监听用户事件,来处理用户行为和数据上的改变

Model层:负责操作数据

1
2
3
4
5
6
7
8
9
10
let Model = {
data: {},
create: {},
delete: {},
update(data){
Object.assign(m.data, data) // 更新数据
eventBus.trigger('m:update') // 触发'm:update'并通知view更新
},
get: {}
}

View层:负责视图

1
2
3
4
5
6
7
8
let View = {
el: el, // 需要更新的元素
html: ``, // 显示在页面中的内容
init(){
v.el: // 初始化需要更新的元素
},
render(){} // 重新渲染
}

Controller层:负责其他

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
let Controller = {
init(){
v.init(), // 初始化view
v.render(), // 第一次渲染页面
c.autoBindEvents(), // 事件绑定
eventBus.on('m:update', ()=>{ // 当eventBus触发'm:update'时,刷新view
v.render()
})
},
events: { // 用哈希表储存事件信息
'click #add1' : 'add',
'click #minus': 'minus',
'click #mul2': 'mul',
'click #divide2': 'div'
},
add(){
m.update({n: m.data.n + 1})
},
minus(){
m.update({n: m.data.n - 1})
},
mul(){
m.update({n: m.data.n * 2})
},
div(){
m.update({n: m.data.n / 2})
},
methods(){
data=新数据
m.update(data) // 通知model更新数据
},
autoBindEvents(){
for(let key in c.events){ // 遍历events表,自动绑定事件
const value = c[c.events[key]]
const spaceIndex = key.indexOf('')
const part1 = key.slice(0, spaceIndex) // 拿到'click'
const part2 = key.slice(spaceIndex + 1) // 拿到对应元素'#add1'、'#minus'、'#mul2'、'#div2'
v.el.on(part1, part2, value)
}
}
}

EventBus

EventBus用于模块间通讯,常用的api包括事件触发(trigger)、事件监听(on)、取消监听(off)

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
import $ from 'jquery'

class EventBus {
constructor() {
this._eventBus = $(window)
}
on(eventName,fn){
return this._eventBus.on(eventName,fn)
}
trigger(eventName,data){
return this._eventBus.trigger(eventName,data)
}
off(){
return this._eventBus.off(eventName,fn)
}
}

export default EventBus

// new.js
import EventBus from 'EventBus.js'
const e = new EventBus()
e.on()
e.trigger()
e.off()

表驱动编程

表驱动是一种编程模式,从哈希表里查找信息而不是重复使用if…else语句。对于简单的情况,使用逻辑语句更加轻松直白,但对于复杂的逻辑链来说,表驱动有着更稳定的复杂度。下面这段代码有着极高的相似度和重复性:

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
$button1.on('click',()=>{
let n = parseInt($number.text())
n += 1
localStorage.setItem('n',n)
$number.text(n)
})

$button2.on('click',()=>{
let n = parseInt($number.text())
n -= 1
localStorage.setItem('n',n)
$number.text(n)
})

$button3.on('click',()=>{
let n = parseInt($number.text())
n *= 2
localStorage.setItem('n',n)
$number.text(n)
})

$button4.on('click',()=>{
let n = parseInt($number.text())
n /= 2
localStorage.setItem('n',n)
$number.text(n)
})

用事件委托

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
const c = {
init(container) {
v.init(container)
v.render(m.data.n)
c.bindEvents()
}
bindEvents() {
v.el.on('click','#add1',()=>{
m.data.n += 1
v.render(m.data.n)
})
v.el.on('click','#minus1',()=>{
m.data.n -= 1
v.render(m.data.n)
})
v.el.on('click','#mul2',()=>{
m.data.n *= 2
v.render(m.data.n)
})
v.el.on('click','#divide2',()=>{
m.data.n /= 2
v.render(m.data.n)
})
}
}

用哈希表存储<按钮>及按下按钮对应的<事件>,这样使得数据和事件清晰的分离

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
const c = {
events:{
'click #add1':'add',
'click #minus1':'minus',
'click #mul2':'mul',
'click #divide2':'div'
},
add(){
m.update( data: {n:m.data.n + 1})
},
minus(){
m.update( data:{n:m.data.n - 1})
},
mul(){
m.update( data: {n:m.data.n * 2})
},
div(){
m.update(data: {n:m.data.n / 2})
},
autoBindEvents(){
for(let key in c.events){
const value = c[c.events[key]]
const spaceIndex = key.indexOf(' ')
const part1 = key.slice(0,spaceIndex)
const part2 = key.slice(spaceIndex + 1)
v.el.on(part1,part2,value)
}
}
}

模块化

  • 将一段复杂的代码依据一定的规则抽取成一个个较小规模的块,块与块之间相对独立
  • “块”可以理解为实现特定功能的一组方法
  • 块的内部数据(变量)与实现(函数)是私有的,只是向外部暴露了一些接口使其与外部其它模块通讯
  • ES6语法中引入import和export来导入和导出模块
    1
    2
    3
    4
    5
    6
    7
    // 导出
    export default c // 默认导出方式
    export {name, draw, reportArea, reportPerimeter} // 导出所有想要导出的模块,使用花括号并用逗号分隔

    // 导入
    import xxx from './app1.js'
    import {name, draw, reportArea, reportPerimeter} from '/js-examples/modules/basic-modules/modules/square.mjs'

事不过三抽象思维

  • 同样的代码写三遍,就应该抽成一个函数
  • 同样的属性写的三遍,就应该做成共用属性(原型或类)
  • 同样的原型写三遍,就应该用继承
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Model{
    constructor(options){
    ['data','update','create','get'].forEach(key)=>{
    if(key in options){
    this[key] = options[key]
    }
    }
    }
    create(){
    console && console.error && console.error('还没实现 create')
    }
    delete(){
    console && console.error && console.error('还没实现 delete')
    }
    update(){
    console && console.error && console.error('还没实现 update')
    }
    get(){
    console && console.error && console.error('还没实现 get')
    }
    }

this指向及绑定方法

Posted on 2022-04-11
| Words count in article: 1,310

this关键字

this指向

简单来说,this就是属性或方法当前所指的对象,并且它也总是返回一个对象

1
2
3
4
5
6
7
var person = {
name: "joyce",
describe: function() {
return '姓名:' + this.name;
}
};
person.describe() // "姓名:joyce"

this.name 表示name属性所在对象,name是在describe方法中调用,而describe方法所在的当前对象是person,故this指向person。this.name就是person.name

this指向的可变性

由于对象属性可以赋给另一个对象,属性所在对象的可变性,所以this指向也是可变的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function callName() {
return '姓名:' + this.name
}

var A = {
name: 'joyce',
describe: callName
};

var B = {
name: 'tony',
describe: callName
}

A.describe() // "姓名:joyce"
B.describe() // "姓名:tony"

callName函数内部使用了this关键字,随着callName所在对象不同,this指向也发生改变

绑定this的方法

Function.prototype.call()

该方法可以指定函数内部this的指向(即函数执行时的作用域),然后在指定的作用域中调用该函数

1
2
3
4
5
6
7
var obj = {};
var f = function () {
return this;
};

f() === window // true
f.call(obj) === obj // true

指定call方法的第一个参数,改变this指向。若参数为空、null、undefined则默认传入全局对象

1
2
3
4
5
6
7
8
9
10
11
12
var n = 123;
var obj = { n: 456 };

function a() {
console.log(this.n);
}

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456

call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数

1
2
3
4
5
function add(a,b) {
return a+b
}

add.call(this, 1,2) // 3

Function.prototype.apply()

apply与call方法类似,同样是改变this指向,然后再调用该函数。唯一区别是apply将参数放在数组里而call需要将参数按顺序传递进去

1
2
3
4
5
6
function f(x,y) {
console.log(x+y);
}

f.call(null, 1, 3) // 4
f.apply(null, [1,3]) // 4

apply方法的应用

  1. 找出数组最大元素
    使用apply和Math.max方法,可以返回数组最大元素

    1
    2
    var arr = [1,4,7,10,26,9]
    Math.max.apply(null, a) // 26
  2. 将数组中的空元素变为undefined
    通过apply,利用Array构造函数将数组元素变成undefined

    1
    Array.apply(null, ['a', ,'b'])  // ['a',undefined,'b']

空元素与undefined差别在于,数组的forEach方法会跳过空元素,但不会跳过undefined。因此遍历内部元素时,会得到不同结果

1
2
3
4
5
6
7
8
var arr = ['a', ,'b']

function print(i) {
console.log(i)
}

a.forEach(print) // a b
Array.apply(null,a).forEach(print) // a undefined b

  1. 转换类似数组的对象
    利用数组对象的slice方法,可将一个伪数组(如arguments对象)转为真正的数组
    1
    2
    3
    4
    Array.prototype.slice.apply({0: 1, length: 1})  // [1]
    Array.prototype.slice.apply({0: 1}) // []
    Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
    Array.prototype.slice.apply({length: 1}) // [undefined]

上述apply方法的参数都是对象,返回结果都是数组,这就达到将对象转为数组的目的,但被处理的对象需要有length属性以及相对应的数字键

Function.prototype.bind()

bind()方法会创建一个新函数,称为绑定函数。当调用这个绑定函数时,绑定函数会以创建它时传入 bind() 方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

常见的用法,我们通常使用_this、that、self等保存this,方便在改变了上下文后继续引用到它

1
2
3
4
5
6
7
8
9
var foo = {
bar: 1,
eventBind: function(){
var _this = this;
$('.oneClass').on('click', function(event){
console.log(_this.bar) // 1
})
}
}

由于Javascript特有机制,上下文环境在 eventBind:function(){} 过渡到 $(‘.oneClass’).on(‘click’, function(event){}) 发生了改变。使用新的变量保存this这样的方法是有效的,使用bind()可以更优雅的解决

1
2
3
4
5
6
7
8
var foo = {
bar: 1,
eventBind: function(){
$('.oneClass').on('click', function(event) {
console.log(this.bar) // 1
}.bind(this));
}
}

再举一个简单的栗子🌰

1
2
3
4
5
6
7
8
9
10
11
var bar = function(){
console.log(this.x)
}

var foo = {
x: 3
}

bar() // undefined
var func = bar.bind(foo)
func() // 3

在创建了一个新函数func,使用bind()创建一个绑定函数,在执行的时候this被设置成foo,而不是直接调用bar()时的全局作用域

三者比较

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
x: 886
}

var foo = {
getX: function(){
return this.x
}
}

console.log(foo.getX.bind(obj)()) // 886
console.log(foo.getX.call(obj)) // 886
console.log(foo.getX.apply(obj)) // 886

三个输出都是886,不同的是bind方法后多了一个括号,当你希望改变上下文环境后并非立即执行,而是回调时执行,使用bind()方法,而call、apply会立即执行函数

总结:

  • call、apply、bind三者都是用来改变函数的this对象的指向,并且第一个参数都是要指向的对象(也即函数执行的上下文)
  • 三者都可以利用后续参数传参
  • bind是返回对应的函数,便于稍后调用,call和apply则是立即调用

DOM事件模型&事件委托

Posted on 2022-03-29
| Words count in article: 1,198

事件模型

一个事件发生后,会在子元素和父元素之间传播(propagation),这种传播分成三阶段

  • 从上到下的捕获阶段(capture phase):从window对象传导到目标节点(上层传到底层)
  • 目标阶段(target phase):在目标节点上触发
  • 从下到上的冒泡阶段(bubbling phase):从目标节点传导回window对象(从底层传回上层)

dom.png

冒泡(bubbling phase)

当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素的处理程序,一直向上到其他祖先

假设我们有 3 层嵌套 FORM > DIV > P,它们各自拥有一个处理程序:

1
2
3
4
5
6
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>

1
2
3
4
5
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>

当你点击内部的p标签会首先运行 onclick:

  1. 在该 p标签 上的。
  2. 然后是外部 div标签 上的。
  3. 然后是外部 form标签 上的。
  4. 以此类推,直到最后的 document 对象。

点击 p标签,那么我们将看到 3 个 alert:p → div → form。

这个过程称为冒泡,因为事件是从内部冒泡到所有父级元素,就像水里的气泡一样

event.target 与 event.currentTarget

  • event.target 是引发事件的嵌套层级最深的目标元素,也就是用户操作的元素
  • event.currentTarget 是程序员监听的元素

🌰举个例子🌰

如果我们有一个处理程序 form.onclick,它可以“捕获”表单内的所有点击。无论用户点击发生在哪里,它都会冒泡到 form 并运行处理程序。

在 form.onclick 处理程序中:

  • this(=event.currentTarget)是
    元素,因为处理程序在它上面运行(程序员监听的元素)
  • event.target 是表单中实际被用户点击的元素
  • event.target 等于 event.currentTarget的情况:当点击事件发生在 form 元素上时

阻止冒泡

用于停止冒泡的方法是event.stopPropagation(),下面这个例子中,点击button元素,body.onclick不会生效

1
2
3
<body onclick="alert(`the bubbling doesn't reach here`)">
<button onclick="event.stopPropagation()">Click me</button>
</body>

捕获(capture phase)

1
div.addEventListener('click', fn, bool)
  • bool 不传 或为 falsy(默认值),则在 冒泡阶段 设置处理程序fn(一般默认情况就是冒泡)
  • bool 为 true,则在 捕获阶段 设置处理程序fn(true 是 {capture: true} 的简写形式)

在现实世界中,当事故发生时,当地警方会最先做出反应。他们最了解发生这件事的地方。然后,如果需要,上级主管部门再进行处理。

事件处理程序也是如此。特定于 td元素 的处理程序可能恰好适合于该 td元素,这个处理程序知道关于该元素的所有信息。所以该处理程序应该首先获得机会。然后,它的直接父元素也了解相关上下文,但了解的内容会少一些,以此类推,直到处理一般性概念并运行最后一个处理程序的最顶部的元素为止。

事件委托

  • 如果我们有许多以类似方式处理的元素,那么就不必为每个元素分配一个处理程序 —— 而是将单个处理程序放在它们的共同祖先上
  • 这种方式是基于事件传播过程中,逐层冒泡总能被祖先节点捕获
  • 在处理程序中,我们获取 event.target 以查看事件实际发生的位置并进行处理

事件委托的优点

  1. 节省监听数,也就是节省了内存

当你想要监听100个按钮的点击事件,在每个按钮上添加onclick事件显然是笨拙的做法。用事件委托我们可以监听100个按钮的祖先元素,等到冒泡阶段,再判断 event.target 是不是这100个按钮中的一个

1
2
3
4
5
6
7
div.addEventListener('click', (e) => {
const t = event.target
if(t.tagName.toLowerCase() === 'button'){
console.log('button被点击了')
console.log('button data-id是'+ t.dataset.id)
}
})

  1. 可以动态监听元素

当我们想要监听一个暂时还不存在的元素时,可以监听它的祖先,在用户点击了后看是不是我想要监听的元素

1
2
3
4
5
6
7
8
9
10
11
12
setTimeout(()=>{
const button = document.createElement('button')
button.textContent = 'click me'
div1.appendChild(button)
},1000)

div1.addEventListener('click',(e)=>{
const t = e.target
if(t.targetName.toLowerCase() === 'button'){
console.log('button被click')
}
})

手写可拖拽的 div

预览
注意点:

  1. clientX 是鼠标在当前窗口下的水平坐标
  2. deltaX = 鼠标移动后的位置 - 初始位置
  3. css里要加上position

jQuery设计思想与实践

Posted on 2022-03-28
| Words count in article: 1,180
  • jQuery设计思想:选择某个网页元素,然后对其进行操作
  • jQuery(css选择器)用于获取对应元素,但它不返回这些元素,而是返回一个对象api(这个对象可以直接操作对应的元素)
  • 使用jQuery的第一步,将一个选择表达式,放进构造函数jQuery(),得到被选中的元素

选择网页元素

选择表达式可以是CSS选择器

1
2
3
4
$(document) // 整个文档对象
$('#myId') // 选择ID为myId的网页元素
$('div.myClass') // 选择class为myClass的 div 元素
$('input[name=first]') // 选择name属性=first的 input 元素

也可以是jQuery特有表达式

1
2
3
4
5
6
$('a:first')  // 选择网页中第一个a元素
$('tr:odd') // 选择表格的奇数行
$('#myForm: input') // 选择表单中的input元素
$('div:visible') // 选择可见的div元素
$('div:gt(2)') // 选择所有的div元素,除了前三个
$('div:animated') // 选择当前处于动画状态的div元素

改变结果集

提供各种强大的过滤器,对结果集进行筛选,缩小选择结果

1
2
3
4
5
$('div').has('p')  // 选择包含p元素的div
$('div').not('.myClass') // 选择class不等于myClass的div元素
$('div').filter('.myClass') // 选择class等于myClass的div元素
$('div').first() // 选择第一个div元素
$('div').eq(5) // 选择第6个div元素

有时需要从结果集出发,移动到附近的相关元素

1
2
3
4
5
$('div').next('p')  // 选择div元素后面的第一个p元素
$('div').parent() // 选择div元素的父元素
$('div').closest('form') // 选择离div最近的form元素
$('div').children() // 选择div的所有子元素
$('div').siblings() // 选择div的同级元素

链式操作

选中网页元素后,对它进行一系列操作,操作可以像链条一样连接起来。原理在于每次jQuery操作返回的都是一个jQuery对象,而不是元素

1
2
3
4
5
6
7
$('.test').find('span').addClass('.yellow').text('hi,我是新加的内容')

// 拆分开
$('.test') // 找到div元素,不返回元素们,而是返回一个api对象
.find('span') // 选择其中的span元素
.addClass('.yellow') // 遍历所有刚才获取到的span元素,添加'.yellow'
.text('hi,我是新加的内容') // 将文本内容进行替换

jQuery 还提供了 end() 方法,可使结果集后退一步

1
2
3
4
5
6
7
$('div')
.find('h3')
.eq(2)
.html('Hello')
.end() //退回到选中所有的h3元素的那一步
.eq(0) //选中第一个h3元素
.html('World'); //将它的内容改为World

取值getter 和 赋值setter

jQuery使用同一个函数,完成取值和赋值,这是由函数的参数来决定

1
2
$('h1').html()  // html没有参数,表明是取值
$('h1').html('hi') // html有参数,表明是赋值

常见的取值和赋值函数

1
2
3
4
5
6
.html() //取出或设置html内容
.text() //取出或设置text内容
.attr() //取出或设置某个属性的值
.width() //取出或设置某个元素的宽度
.height() //取出或设置某个元素的高度
.val() //取出某个表单元素的值

注意:

  • 如果结果集包含多个元素,赋值时,将对其中所有元素赋值;取值时,则只取出第一个元素的值

  • .text()例外,它取出所有元素的text内容

移动元素

提供两组方法,操作元素在网页中的位置移动。一是直接移动该元素,二是移动其他元素,使目标元素到达我们想要的位置

1
2
3
4
5
// 已经选中了一个div元素,要将它移动到p元素后
$('div').insertAfter($('p')); // 把div元素移到p元素后
$('p').after($('div')); // 把p元素加到div元素前

// 看似效果一样,但操作视角不同,返回的元素也不相同。第一种方法返回div元素,第二种方法返回p元素。

使用该模式的操作方法一共有四对

  1. .insertAfter()和.after():在现存元素的外部,从后面插入元素
  2. .insertBefore()和.before():在现存元素的外部,从前面插入元素
  3. .appendTo()和.append():在现存元素的内部,从后面插入元素
  4. .prependTo()和.prepend():在现存元素的内部,从前面插入元素

复制、删除、创建元素

  1. 复制元素.clone()
  2. 删除元素.remove()和.detach(),区别在于前者不保留被删除元素的事件,后者保留,有利于重新插入文档时使用
  3. 清空元素内容,但不删除该元素.empty()
  4. 创建新元素,将新元素直接传入jQuery的构造函数
    1
    2
    3
    4
    5
    $('<p>Hello</p>');

    $('<li class="new">new list item</li>');

    $('ul').append('<li>list item</li>');

JS函数

Posted on 2022-03-16
| Words count in article: 828

定义一个函数

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
// 1. 具名函数
function 函数名(形式参数1, 形式参数2){
语句
return 返回值
}

// 2. 匿名函数(即具名函数去掉函数名,也叫函数表达式)
let a = function(x,y) {
return x+y;
}

// 3. 箭头函数
let f1 = x => x * x;

let f2 = (x,y) => x+y; // 圆括号不能省

let f3 = (x,y) => {
return x+y;
} // 花括号不能省

let f4 = (x,y) => ({
name: x;
age: y;
}) // 直接返回对象会报错,需要加圆括号


// 4. 用构造函数
let f5 = new Function('x','y','return x+y') // 基本没人用,所有函数都是Function构造出来的,包括Object、Array

函数自身与函数调用

1
2
let fn = () => console.log('hi');
fn // 不会有任何结果,因为fn没有调用
1
2
let fn = () => console.log('hi');
fn() // 打印出hi,有圆括号()才是调用
1
2
3
4
5
let fn = () => console.log('hi')
let fn2 = fn
fn2()
// fn保存了匿名函数的地址,这个地址被复制给了fn2,fn2()调用了匿名函数
// fn和fn2()都是匿名函数的引用而已,真正的函数不是fn也不是fn2

JS函数执行时机

函数的调用时机不同,会得到不同结果

1
2
3
4
5
6
let a = 1
function fn() {
console.log(a)
}

// 不知道会打印出什么,因为没有调用函数
1
2
3
4
5
6
7
let a = 1
function fn(){
console.log(a)
}
fn()

// 打印出1
1
2
3
4
5
6
7
8
let a = 1
function fn(){
console.log(a)
}
a = 2
fn()

// 打印出2
1
2
3
4
5
6
7
8
let a = 1
function fn(){
console.log(a)
}
fn()
a = 2

// 打印出1
1
2
3
4
5
6
7
8
9
10
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
},0)
}
fn()
a = 2

// 打印出2

异步函数

1
2
3
4
5
6
7
8
9
let i = 0
for(i = 0; i < 6; i++){
setTimeout(()=>{
console.log(i)
},0)
}

// 会打印出6个6,而不是0,1,2,3,4,5
// setTimeout会等当前的for循环执行完,再去执行console.log(i),而for循环执行完后,i=6,所以会打印出6个6

如何让上面的代码打印出0,1,2,3,4,5

  1. for、let配合使用,let会单独创建一个作用域,每次循环会多创建一个i,相当于有6个i

    1
    2
    3
    4
    5
    for(let i = 0; i < 6; i++){
    setTimeout(()=>{
    console.log(i)
    },0)
    }
  2. 在for循环内部声明一个新的变量来存储i的值

    1
    2
    3
    4
    5
    6
    7
    let i
    for(i = 0; i < 6; i++){
    let x = i
    setTimeout(()=>{
    console.log(x)
    },0)
    }
  3. 立即执行函数,把当前for循环过程中的i传递进去,构建块级作用域

    1
    2
    3
    4
    5
    6
    7
    for (var i = 0; i < 6; i++){
    (function(i){
    setTimeout(()=>{
    console.log(i)
    }, 0)
    })(i)
    }
  4. 利用setTimeout函数的第三个参数,会作为回调函数的第一个参数传入

    1
    2
    3
    for(i = 0; i < 6; i++){
    setTimeout(console.log(i), 1000, i)
    }
  5. 利用try…catch…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for(var i = 0; i < 6; i++){ 
    try{
    throw i;
    }catch(i){
    setTimeout(() => {
    console.log(i)
    }, 0)
    }
    }
  6. 使用递归配合setTimeout()

    1
    2
    3
    4
    5
    function fn(i){
    return i < 6 ? setTimeout(()=>{console.log(i);fn(++i),0}) : i
    }

    fn(0)
  7. 利用promise

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    for (var i = 0; i < 6; i++) {
    timeoutPromise(i);
    }
    function timeoutPromise(i) {
    return new Promise((resolve) => {
    setTimeout(() => {
    console.log(i);
    resolve(true);
    }, 0)
    })
    }

JS数组

Posted on 2022-03-15
| Words count in article: 1,997

JS中的数组

  • 元素的数据类型可以不同

    1
    2
    // 混合值
    let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];
  • 内存不一定连续,对象是随机存储的

  • 不能通过数字下标,而是字符串下标
  • 数组可以有任何的key

创建数组

  • 新建

    1
    2
    3
    let arr = [1,2,3]
    let arr = new Array(1,2,3) // 数组元素为1,2,3
    let arr = new Array(3) // 数组长度为3
  • 转化

    1
    2
    3
    let arr = '1,2,3'.split(',');
    let arr = '123'.split('');
    Array.from('123');
  • 伪数组

    1
    2
    3
    4
    let divList = document.querySelectorAll('div');  // 这是一个伪数组
    console.dir(divList); // 伪数组的原型链中没有数组的原型

    var divArray = Array.from(divList); // 将伪数组转换成数组
  • 合并两个数组

    1
    arr1.concat(arr2);  // 得到一个新数组,并不会改变arr1和arr2
  • 截取数组的一部分

    1
    2
    arr1.slice(1);  // 从第二个元素开始截取
    arr1.slice(0); // 浅拷贝(复制)一个数组

增加元素

1
2
3
4
5
6
7
8
9
10
11
// 在尾部增加元素
arr.push(newItem) // 修改arr,返回新长度
arr.push(item1, item2) // 修改arr,返回新长度

// 在头部增加元素
arr.unshift(newItem) // 修改arr,返回新长度
arr.unshift(item1, item2) // 修改arr,返回新长度

// 在中间添加元素
arr.splice(index,0,'x') // 在index处,一个元素都不删,再插入'x'
arr.splice(index,0,'a','b','c') // 在index处,一个元素都不删,再插入'a''b''c'

删除数组元素

1
2
3
4
arr.shift()  // 删除头部元素,并返回被删除的元素
arr.pop() // 删除尾部元素,并返回被删除元素
arr.splice(0,3) // 从第一个元素开始,删除3个元素
arr.splice(2,5,666,7777) // 从第三个元素开始,删除5个元素,并添加两个新元素666,7777

修改元素

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
// 反转顺序
arr.reverse();

// 将一串字符串倒序
var cc = 'abcde';
cc.split('').reverse().join('');

// 自定义数组顺序
let arr = [1,11,3,56,8,11,4,33,22]
arr.sort(function(a,b) { // [1, 3, 4, 8, 11, 11, 22, 33, 56]
if( a > b ){
return 1 // a>b return 1,从小到大排序
}else if(a===b){
return 0
}else{
return -1
}
})

arr.sort(function(a,b) { // [56, 33, 22, 11, 11, 8, 4, 3, 1]
if( a > b ){
return -1 // a>b return -1,从大到小排序
}else if(a===b){
return 0
}else{
return 1
}
})

// 对象根据某个属性值排序
let arr = [ {name: '小明', score: 60}, {name:'小芳', score: 55}, {name: '小花', score: 69} ];

arr.sort(function(a,b){
if(a.score > b.score) {
return 1
} else if (a.score === b.score) {
return 0
} else {
return -1
}
})
// 简单写法
arr.sort((a,b) => (a.score - b.score))

查找元素

1
2
3
4
5
6
7
// 查找某个元素是否在数组里
arr.indexOf(item) // 存在返回索引,不存在返回-1
arr.includes(item) // 存在返回true,不存在返回false

// 使用条件查找
arr.find(item => item % 2 === 0) // 找第一个偶数
arr.findIndex(item => item % 2 === 0) // 找第一个偶数的索引

遍历数组

for 语句 & forEach 语句 & for-of 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// for 语句
var arr = [1,2,3,4,5,6,7,8, ,10]
for(var i = 0; i < arr.length; i++){
console.log(arr[i])
if(arr[i] === 5) {
break; // 1 2 3 4
}
}

// forEach 语句
arr.forEach((item, index, arr)=> {
if(item === 5) return;
})

// for-of 语句
var arr = [{name: 'xxx'}, 18, 'joyce'];
for(item of arr) {
console.log(item);
}

小结

  • for和for-of可以使用break跳出循环,forEach无法跳出循环
  • for遍历数组索引,for-of直接访问实际的元素,forEach回调参数更丰富(元素、索引、原数组)都可获取
  • for与for-of在数组中存在空元素时,同样会执行

map 方法 & filter 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// filter方法
const recordList = [
{name: '餐饮', type: '-', amount: 150},
{name: '住房', type: '-', amount: 2000},
{name: '基金', type: '+', amount: 1800}
]
const targetItem = recordList.filter(item => {
return item.type === '+'
})

// map方法
let names = ["Bilbo", "Gandalf", "Nazgul"];
let lengths = names.map(item => item.length); // 将每个元素转化为它的字符串长度

小结

  • 两种方法都是生成一个新数组,并不会改变原数组
  • map 会将回调函数的返回值组成一个新数组,数组长度与原数组一致,并且生成的新数组元素是可自定义
  • filter 会将符合回调函数条件的元素组成一个新数组,生成的新数组元素不可自定义,与对应原数组元素一致

some 方法 & every 方法

1
2
3
4
5
6
7
8
// some 方法
var arr = [1,2,3,4,5]
var result = arr.some(function(item,index)=> {
return item > 3 // true
})

// every 方法
[12, 15, 7, ,50, 101].every(x => x >= 10); // false

小结

  • 两种方法都用来做条件判断,都返回一个布尔值
  • some若某一元素满足,就返回true并中断循环。所有元素不满足条件,才返回false
  • every则相反,若有一个元素不满足条件,就返回false并中断循环。若所有元素满足条件,则返回true

reduce 方法 & reduceRight 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var arr1 = [1,4,6,9]
arr1.reduce((result,item) => {
return result.concat(item * item); // [1, 16, 36, 81]
},[])

var arr2 = [1,2,3,4,5,6,7,8,9];
arr2.reduce( (result,item) => {
if (item % 2 === 1){
return result.concat(item); // 奇数就留下
} else {
return result // 不是奇数就返回之前的结果
}
},[])

// 简便写法
arr2.reduce((result,item) =>
(result.concat(item % 2 === 1 ? item : [])) // [1,3,5,7,9]
,[])

小结

  1. reduce方法接受两个参数,第一个参数是回调函数,第二个参数是初始值
  2. reduceRight 方法除了与reduce执行方向相反外,其他完全一致
  3. 回调函数接受四个参数:
    • previousValue:之前所有的数组元素被回调函数处理累计的结果(前一次调用回调函数得到的返回值)
    • currentValue:当前被执行的数组元素
    • currentIndex: 当前被执行的数组元素索引
    • array:原数组,也就是调用 reduce 方法的数组(被遍历的对象)
  4. 如果不传入初始值,reduce 方法会从索引 1 开始执行回调函数,如果传入初始值,将从索引 0 开始、并从初始值的基础上累计执行回调。

25个你不得不知道的数组reduce高级用法

find 方法 & findIndex 方法

1
2
3
4
5
6
7
// find 方法
const arr = [5, 8, 12, 33, 150]
const found = arr.find(item => item > 10) // 12

// findIndex 方法
const arr = [5, 8, 12, 33, 150]
const found = arr.findIndex(item => item > 50) // 4

小结

  • 两者都是用来查找数组元素
  • find方法返回数组中满足callback函数的第一个元素的值,若不存在返回undefined
  • findIndex方法返回数组中满足条件的元素的索引值,若不存在返回-1

习题

  1. 将数字变成星期

    1
    2
    3
    4
    5
    6
    let arr = [0,1,2,2,3,3,3,4,4,4,4,6]
    let arr2 = arr.map(
    item => { return { 0:'周日', 1:'周一', 2:'周二', 3:'周三', 4:'周四', 5:'周五', 6:'周六' }[item] }
    )

    console.log(arr2) // ['周日', '周一', '周二', '周二', '周三', '周三', '周三', '周四', '周四', '周四', '周四','周六']
  2. 找出大于60的数字

    1
    2
    3
    let scores = [95,91,59,55,42,82,72,85,67,66,55,91]
    let scores2 = scores.filter( item => item > 60 )
    console.log(scores2) // [95,91,82,72,85,67,66, 91]
  3. 算出所有奇数之和

    1
    2
    3
    4
    5
    let scores = [95,91,59,55,42,82,72,85,67,66,55,91]
    let sum = scores.reduce((sum, n)=>{
    return ( n%2 === 1) ? sum+n : sum
    },0)
    console.log(sum) // 奇数之和:598
  4. 将数组变成对象

    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
    let arr = [
    { 名称:'动物', id: 1, parent: null},
    { 名称:'狗', id: 2, parent: 1},
    { 名称:'猫', id: 3, parent: 1}
    ]

    将上面的数组变成对象
    {
    id: 1, 名称: '动物', children: [
    {id: 2, 名称:'狗', children: null},
    {id: 3, 名称:'猫', children: null},
    ]
    }

    arr.reduce( (result,item) => {
    if(item.parent === null) {
    result.id = item.id;
    result['名称'] = item['名称'];
    } else {
    result.children.push(item);
    item.children = null;
    delete item.parent;
    }
    return result
    },{ id:null, children:[]})

JS对象分类

Posted on 2022-03-14
| Words count in article: 1,148

构造函数

1
2
3
4
5
6
7
8
9
10
11
function Square(width) {
this.width = width;
}
Square.prototype.getArea = function() {
return this.width * this.width;
}
Square.prototype.getLength = function() {
return this.width * 4
}

let square = new Square(5) // 使用new操作调用构造函数,创建一个对象

构造函数Square

  • Square函数本身负责给对象本身增加自身属性
  • Square.prototype负责创建对象的共有属性

new Square 自动帮你做了四件事

  1. 自动创建了一个空对象
  2. 自动为这个空对象关联原型,原型地址为Square.prototype
  3. 自动将空对象作为 this关键字 运行构造函数
  4. 自动 return this

规范

  • 构造函数,首字母大写
  • 被构造出来的对象,首字母小写
  • new 后面的函数,使用名词形式 eg: new Person()、new Circle()

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.getArea = function() {
return Math.pow(this.radius, 2) * Math.PI;
}
Circle.prototype.getLength = function() {
return this.radius * 2 * Math.PI;
}

let circle = new Circle(5);
circle.radius
circle.getArea();
circle.getLength();

原型公式(JS里唯一的一个公式)

规律

  • let obj = new Object() 原型是 Object.prototype
  • let arr = new Array() 原型是 Array.prototype
  • let square = new Square() 原型是 Square.prototype
  • let fn = new Function() 原型是 Function.prototype

    结论

  • 你是谁构造的,你的原型就是谁的 prototype属性所对应的对象

    公式

  • 对象.proto === 其构造函数.prototype

JS中的类

类型 VS 类

  • 类型是JS数据的分类,包含7种数据类型(string、number、bool、symbol、undefined、null、object)
  • 类是针对对象的分类,常见的有(Array、Function、Date)

    为什么对象需要分类

  • 有很多对象拥有一样的属性和行为(函数),需要把他们归为一类,如 square1 和 square2,这样创建类似对象的时候就很方便
  • 还有很多对象拥有其他的属性和行为,所以就需要不同的分类。比如 Square / Circle / Rect 就是不同的分类,Array / Function 也是不同的分类

数组对象

  • 定义一个数组

    1
    2
    3
    let arr = [1,2,3];
    let arr = new Array(1,2,3); // 数组包含元素1,2,3
    let arr = new Array(3); // 数组长度为3
  • 数组对象的自身属性
    ‘0’ ‘1’ ‘2’ ‘length’,属性名没有数字,只有字符串

  • 数组对象的共用属性
    ‘push’ ‘pop’ ‘join’ ‘shift’ ‘unshift’

函数对象

  • 定义一个函数

    1
    2
    3
    4
    function fn(x,y) {return x+y}
    let fn2 = function fn(x,y) {return x+y}
    let fn = (x,y) => x+y
    let fn = new Function('x', 'y', 'return x+y')
  • 函数对象的自身属性
    ‘name’ ‘length’

  • 函数对象的共用属性
    ‘call’ ‘apply’ ‘bind’

ES6 class新语法

用class重写circle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Circle {
constructor(radius){
this.radius = radius
}
getArea(){
return Math.pow(this.radius,2) * Math.PI
}
getLength(){
return this.radius * 2 * Math.PI
}
}
let circle = new Circle(5)
circle.radius
circle.getArea
circle.getLength

JS终极一问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1. window 是谁构造的

Window
可通过 constructor属性 查看构造者
window.__proto__ === Window.prototype

2.window.Object 是谁构造的

window.Function
因为所有函数都是由 window.Function 构造出来的
window.Object.constructor === window.Function

3. window.Function 是谁构造的

window.Function
因为所有函数都是由window.function构造的
自己构造自己?
浏览器构造了Function,然后指定它的构造者是自己

问答

  1. 关于 「原型」 ,正确的是
  • 「x 的原型」等价于「x.proto 所指的对象」 ,有时为了方便,我们可以认为「x 的原型」等价于「x.proto 」
  • 一个对象的原型指的是这个对象与其他同类对象的公有属性的集合,比如 obj1 和 ob2 同时拥有 toString / valueOf,那么 toString / valueOf 等属性组成的对象,就是 obj1 和 obj2 的原型,这个原型的地址一般储存在构造函数的 prototype 里
  • x.proto和 Object.prototype 存储着同一个对象的地址,这个对象就是 x 的原型
  • 每个对象都有原型,但除了「根对象 Object.prototype」比较特殊,Object.prototype 这个对象的原型为空 null
  1. 关于 prototype 属性,正确的有
  • 所有函数一出生就有一个 prototype 属性(除了箭头函数)
  • 所有 prototype 一出生就有一个 constructor 属性
  • 所有 constructor 属性一出生就保存了对应的函数的地址
  • 如果一个函数不是构造函数,它依然拥有 prototype 属性,只不过这个属性暂时没什么用
  • 如果一个对象不是函数,那么这个对象一般来说没有 prototype 属性,但这个对象一般一定会有 proto 属性

JS对象基本用法

Posted on 2022-03-14
| Words count in article: 499

如何声明对象

1
2
3
let obj = { 'name': 'frank', 'age': 18}

let obj = new Object({ 'name':'frank', 'age': 18})
  1. 键名是字符串,不是标识符,可以包含任意字符
  2. 引号可以省略,省略之后就只能写标识符
  3. 就算引号省略,键名key永远都是字符串

删除对象的属性

1
2
3
delete obj['name']  // 新手推荐写法

delete obj.name

注意区分’不含属性名’和’属性值为undefined’

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {}  // 不含属性xxx
var obj2 = {'xxx': undefined} // 含属性xxx,但值为undefined

obj.xxx === undefined // true
obj2.xxx === undefined // true

'xxx' in obj // false
'xxx' in obj2 // true

obj.xxx === undefined 不能断定 'xxx' 是否为obj的属性

应该使用'xxx' in obj

查看对象属性

1
2
3
4
5
// 查看自身属性

Object.keys(obj) // 自身属性
Object.values(obj) // 自身属性值
Object.entries(obj) // 查看所有属性及属性值,返回键值对数组
1
2
3
// 查看自身属性+共有属性

console.dir(obj)
1
2
3
4
5
6
7
8
9
// 判断一个属性是否是自身属性
var obj = { 'name': 'joyce', 'age': 25, 'gender': 'women'}

obj.hasOwnProperty('toString') // false
obj.hasOwnProperty('name') // true


// 查看'name'属性是否在obj中,但不能断定是obj的自身属性还是共有属性
'name' in obj // true
1
2
3
4
// 查看obj的一个属性

obj.name
obj['name'] // 两种写法均可,新手推荐第二种,键名是字符串而不是变量,新手容易与obj[name]混淆

修改或增加对象属性

  • 修改或增加自身属性

    1
    2
    3
    4
    5
    6
    7
    // 单个赋值
    let obj = {'name': 'joyce'}
    obj.age = 25
    obj['hobby'] = cooking

    // 批量赋值
    Object.assign(obj, {'age': 25, 'hobby': 'cooking'})
  • 修改或增加共有属性(原型)

    1
    2
    3
    4
    5
    6
    7
    8
    var common = {'nation':'China', 'hair':'brown'}

    var person = Object.create(common) // 以common为原型创建person
    Object.assign(person, { // 批量给person增加属性
    'name' :'joyce',
    'age' : 25,
    'hobby' : 'cooking',
    })
  • 修改或增加共有属性(原型)的值

    1
    2
    3
    obj.__proto__.toString = 'yyy'

    Object.prototype.toString = 'yyy'

JS基本语法

Posted on 2022-03-13
| Words count in article: 1,545

表达式与语句

  • 表达式(expression),指一个为了得到返回值的计算式
  1. 1+2表达式的值为3
  2. add(1,2)表达式的值为函数的返回值
  3. console.log表达式的值为函数本身
  4. console.log(3)表达式的值为undefined,3是控制台打印出来的
  • 语句(statement)是为了完成某种任务而进行的操作,下面是两行赋值语句。语句以分号结尾,一个分号就表示一个语句结束,多个语句可以写在一行内。
  1. var a = 1;
  2. var b = ‘abc’;
  • 语句和表达式的区别
  1. 语句主要为了进行某种操作,一般情况下不需要返回值
  2. 表达式是为了得到返回值,一定会返回一个值
  3. 语句一般会改变环境(声明、赋值)

大小写敏感

  • var a 与 var A 是不同的
  • object 与 Object 是不同的

空格

  • 大部分空格没有实际意义
  • 加回车大部分时候也不影响,只有在return后面不能加回车

WechatIMG2334.png


标识符

  • 第一个字符,可以是unicode字母 $ _ 中文(但不可以以数字开头)
  • 变量名是标识符
    1
    2
    3
    4
    var _ = 1
    var $ = 2
    var _____ = 6
    var 你好 = ‘hi’

区块block

将代码包裹在一起,且常与if/for/while合用

1
2
3
4
{
let a = 1
let b = 2
}


if语句

  • 语法
  1. if(表达式) {语句1}else{语句2}
  2. {}在语句只有一句的时候可以省略,但不建议这样做
  • 变态情况
  1. 表达式里可以很变态,如 a = 1
  2. 语句1里可以非常变态,如嵌套if else
  3. 语句2里可以非常变态,如嵌套 if else
  4. 缩进也可以很变态,如面试题常常下套
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    let a = 2
    if(a = 1){ // a = 1,指将1赋值给a
    console.log('a是1')
    }else{
    console.log('a不是1')
    // 会打印出 a是1
    }

    let a = 1
    if(a === 2)
    console.log('a')
    console.log('a等于2')
    // 会打印出 a等于2

    let a = 1
    if(a === 2)
    console.log('a'); console.log('b')
    // 会打印出b

    let a = 1
    if(a === 2)
    console.log('a'),console.log('b')
    // 什么也不打印

推荐写法

1
2
3
4
5
6
7
if(表达式) {
语句1
} else if(表达式){
语句2
} else {
语句3
}

次推荐写法

1
2
3
4
5
6
7
8
9
function fn() {
if(表达式) {
return 表达式
}
if(表达式) {
return 表达式
}
return 表达式
}


switch语句

1
2
3
4
5
6
7
8
9
10
11
12
13
switch (fruit){
case "banana":
执行语句1;
break;
case:"apple":
执行语句2;
break;
default:
执行语句3;
}

// 根据变量fruit的值,选择执行相应的case。如果所有case都不符合,则执行最后的default部分
// 不要省略break,否则不会退出switch,并且会继续执行下一个case

问号冒号表达式

语法:条件? 表达式1:表达式2

1
var even = (n % 2 === 0) ? true : false;

等同于

1
2
3
4
5
6
var even;
if(n % 2 === 0) {
even = true;
} else {
even = false;
}

可视为if…else…的简写形式

1
var msg = '数字' + n + '是' + (n%2 === 0 ? '偶数' : '奇数');


&&短路逻辑

1
2
3
4
5
6
7
window.f1 && console.log('f1存在');

等价于

if(window.f1){
console.log('f1存在');
} // undefined
1
2
3
4
5
6
7
fn && fn() // 表示fn若存在,就调用fn()

等价于

if (fn) {
fn();
}
  • ‘A && B’: 若A为真,就执行B,若A为假,就不执行后面的
  • ‘A && B && C && D’: 取第一个假值或D

||短路逻辑

1
2
3
4
5
6
7
8
9
a || b

等价于

if( !a ){
b
} else{

}

常常用于给a设定一个兜底值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = a || 100

等价于

if(a){
a = a
}else if{
a = 100
}

等价于

if(!a) { // 若a不存在,就给a赋值为100
a = 100
}


循环语句

while循环

语法: while(条件){语句};

包括一个循环条件和一段代码块,只要条件为真,就不断循环执行代码块。

1
2
3
4
5
var i = 1;
while (i<10) {
console.log('i当前为' + i);
i = i+1;
}

1
2
3
4
5
let a = 0.1
while(a ! == 1){
console.log(a)
a = a + 0.1
} // 由于浮点数的问题,会死循环

for循环

for语句是循环命令的另一种形式(while循环的方便写法),可以指定循环的起点、终点和终止条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
for (let i = 0; i < 5; i++) {
console.log(i);
} // 打印出 1 2 3 4 5


for(var i = 0; i < 10; i++) {
for (var j=101; j<100; j++){
if(i===5) {
break;
}
}
console.log(i);
} // 打印出0,1,2,3,4,5,6,7,8,9 break只会退出距离它最近的for循环,外面的for循环中的i会执行完

1
2
3
4
5
6
for (var i = 0; i < 5 ;i++){
setTimeout(()=> {
console.log(i);
},0)
}
// setTimeout是过一段时间执行,结果打印出5个5,在for循环结束后i的值为5,setTimeout作为循环体,会等到for循环执行完后,才会打印
1
2
3
4
5
6
for(let i = 0; i < 5; i++){
setTimeout(()=>{
console.log(i)
},0)
}
// 将var换成let,就会打印出0,1,2,3,4

break和continue语句

两者都具有跳转作用,可以让代码不按既有的顺序执行。

break 是跳出离它最近的一个循环。

1
2
3
4
5
for (var i = 0; i < 5; i++) {
console.log(i);
if (i === 3)
break;
} // 执行到i等于3,就会跳出循环。 打印出0,1,2,3

continue 会立即跳出当前的一次循环,返回循环结构的头部,开始下一轮循环。

1
2
3
4
5
6
7
var i = 0;

while (i < 100){
i++;
if (i % 2 === 0) continue;
console.log('i 当前为:' + i);
} // 打印出i为奇数的值,i为偶数时,直接进入下一轮循环

label标签

1
2
3
{
a:1;
}

一个代码块里有一个label,a是一个label,语句是1*它并不是一个对象

label标签通常与 break语句 和 continue语句 配合使用,跳出特定循环

1
2
3
4
5
6
foo: {
console.log(1);
break foo;
console.log('本行不会输出');
}
console.log(2); // 当执行到break foo就会跳出代码块,打印出1,2
123…6
Joyce

Joyce

53 posts
14 tags
RSS
Links
  • 网易云音乐
© 2018 — 2022 Joyce
Theme — NexT.Mist v5.1.4
0%