0%

两栏布局

实现思路也非常的简单:

  • 使用 float 左浮左边栏
  • 右边模块使用 margin-left 撑出内容块做内容展示
  • 为父级元素添加BFC,防止下方元素飞到上方内容
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
<style>
body {
/* 设置最小宽度,防止挤压使中间内容消失 */
min-width: 600px;
}
.box {
/* 添加BFC */
overflow: hidden;
}
.left {
float: left;
width: 200px;
height: 400px;
background-color: gray;
}
.right {
margin-left:210px;
background-color: red;
height: 200px;
}
</style>
<body>
<div class="box">
<div class="left"></div>
<div class="right"></div>
</div>

</body>

flex弹性布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<style>
.box{
display: flex;
}
.left {
width: 100px;
}
.right {
flex: 1;
}
</style>
<div class="box">
<div class="left">左边</div>
<div class="right">右边</div>
</div>

flex可以说是最好的方案了,代码少,使用简单

注意的是,flex容器的一个默认属性值:align-items: stretch;

这个属性导致了列等高的效果。 为了让两个盒子高度自动,需要设置: align-items: flex-start

三栏布局

实现三栏布局中间自适应的布局方式有:

  • 两边使用 float,中间使用 margin
  • 两边使用 absolute,中间使用 margin
  • 两边使用 float 和负 margin
  • display: table 实现
  • flex实现
  • grid网格布局

两边使用 float,中间使用 margin

需要将中间的内容放在html结构最后,否则右侧会呈在中间内容的下方

实现代码如下:

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
<style>
.wrap {
background: #eee;
overflow: hidden; <!-- 生成BFC,计算高度时考虑浮动的元素 -->
padding: 20px;
height: 200px;
}
.left {
width: 200px;
height: 200px;
float: left;
background: coral;
}
.right {
width: 120px;
height: 200px;
float: right;
background: lightblue;
}
.middle {
margin-left: 220px;
height: 200px;
background: lightpink;
margin-right: 140px;
}
</style>
<div class="wrap">
<div class="left">左侧</div>
<div class="right">右侧</div>
<div class="middle">中间</div>
</div>

原理如下:

  • 两边固定宽度,中间宽度自适应。
  • 利用中间元素的margin值控制两边的间距
  • 宽度小于左右部分宽度之和时,右侧部分会被挤下去

这种实现方式存在缺陷:

  • 主体内容是最后加载的。

  • 右边在主体内容之前,如果是响应式设计,不能简单的换行展示

  • 两边使用 absolute,中间使用 margin

    基于绝对定位的三栏布局:注意绝对定位的元素脱离文档流,相对于最近的已经定位的祖先元素进行定位。无需考虑HTML中结构的顺序

    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
    <style>
    .container {
    position: relative;
    }

    .left,
    .right,
    .main {
    height: 200px;
    line-height: 200px;
    text-align: center;
    }

    .left {
    position: absolute;
    top: 0;
    left: 0;
    width: 100px;
    background: green;
    }

    .right {
    position: absolute;
    top: 0;
    right: 0;
    width: 100px;
    background: green;
    }

    .main {
    margin: 0 110px;
    background: black;
    color: white;
    }
    </style>

    <div class="container">
    <div class="left">左边固定宽度</div>
    <div class="right">右边固定宽度</div>
    <div class="main">中间自适应</div>
    </div>

两边使用 float 和负 margin

双飞翼布局:就是把主列嵌套在一个新的父级块中并利用主列的左右外边距进行布局调整

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
<style>
.left,
.right,
.main {
height: 200px;
line-height: 200px;
text-align: center;
}

.main-wrapper {
float: left;
width: 100%;
}

.main {
margin: 0 110px;
background: black;
color: white;
}

.left,
.right {
float: left;
width: 100px;
margin-left: -100%;
background: green;
}

.right {
margin-left: -100px; /* 同自身宽度 */
}
</style>

<div class="main-wrapper">
<div class="main">中间自适应</div>
</div>
<div class="left">左边固定宽度</div>
<div class="right">右边固定宽度</div>

实现过程:

  • 中间使用了双层标签,外层是浮动的,以便左中右能在同一行展示
  • 左边通过使用负 margin-left:-100%,相当于中间的宽度,所以向上偏移到左侧
  • 右边通过使用负 margin-left:-100px,相当于自身宽度,所以向上偏移到最右侧,相对于最左边偏移自身的宽度,因此到最右侧

缺点:

  • 增加了 .main-wrapper 一层,结构变复杂
  • 使用负 margin,调试也相对麻烦

圣杯布局:

利用父容器的左右内边距+左右两列的相对定位

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
 <style>
#middle,#left,#right {
/* 设置浮动 */
float: left;
}
/* 父容器左右内边距 */
#content {
height: 400px;
overflow: hidden;
padding: 0 200px;
}
#middle {
width: 100%;
height: 400px;
background: red;
}

#left {
width: 200px;
height: 400px;
background: blue;
/* left在下方,-100%移到左边,因为设置了padding,此时有左内边距,因此需要设置定位为相对定位,left移动负自身宽度 */
margin-left: -100%;
position: relative;
left: -200px;
}
#right {
width: 200px;
height: 400px;
background: blue;
/* right在右边,margin-left移动负自身宽度移动到上方右边,因为存在右内边距,设置相对定位,left移动自身宽度 */
margin-left: -200px;
position: relative;
left: 200px;
}
</style>
</head>
<body>
<div id="content">
<div id="middle"></div>
<div id="left"></div>
<div id="right"></div>
</div>
</body>

圣杯布局和双飞翼布局都是把主列放在文档最前面,使主列优先加载,且都是让三列浮动,然后通过负外边距形成三列布局

使用 display: table 实现

标签用于展示行列数据,不适合用于布局。但是可以使用 display: table 来实现布局的效果
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
<style>
.container {
height: 200px;
line-height: 200px;
text-align: center;
display: table;
table-layout: fixed;
width: 100%;
}

.left,
.right,
.main {
display: table-cell;
}

.left,
.right {
width: 100px;
background: green;
}

.main {
background: black;
color: white;
width: 100%;
}
</style>

<div class="container">
<div class="left">左边固定宽度</div>
<div class="main">中间自适应</div>
<div class="right">右边固定宽度</div>
</div>

实现原理:

  • 层通过 display: table设置为表格,设置 table-layout: fixed`表示列宽自身宽度决定,而不是自动计算。

  • 内层的左中右通过 display: table-cell设置为表格单元。

  • 左右设置固定宽度,中间设置 width: 100% 填充剩下的宽度

    使用flex实现

    利用flex弹性布局,可以简单实现中间自适应

    代码如下:

    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
    <style>
    .main {
    display: flex;
    height: 200px;
    justify-content: space-between;
    line-height: 200px;
    text-align: center;

    }
    .left {
    flex:1;
    background-color: green;

    }
    .right {
    flex:1;
    background-color: green;
    }
    .middle {
    flex:5;
    background-color: black;
    color:white;
    }
    </style>
    <body>
    <div class="main">
    <div class="left">左边固定宽度</div>
    <div class="middle">中间自适应</div>
    <div class="right">右边固定宽度</div>
    </div>
    </body>

    实现过程:

    • 仅需将容器设置为display:flex;
    • 盒内元素两端对其,将中间元素设置为100%宽度,或者设为flex:1,即可填充空白
    • 盒内元素的高度撑开容器的高度

    优点:

    • 结构简单直观
    • 可以结合 flex的其他功能实现更多效果,例如使用 order属性调整显示顺序,让主体内容优先加载,但展示在中间

grid网格布局

代码如下:

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
<style>
.wrap {
display: grid;
width: 100%;
grid-template-columns: 300px auto 300px;
}
.left,
.right,
.middle {
height: 100px;
}

.left {
background: coral;
}

.right {
width: 300px;
background: lightblue;
}

.middle {
background: #555;
}
</style>
<div class="wrap">
<div class="left">左侧</div>
<div class="middle">中间</div>
<div class="right">右侧</div>
</div>

一、是什么

webpack proxy,即webpack提供的代理服务

基本行为就是接收客户端发送的请求后转发给其他服务器

其目的是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制)

想要实现代理首先需要一个中间服务器,webpack中提供服务器的工具为webpack-dev-server

webpack-dev-server

webpack-dev-serverwebpack 官方推出的一款开发工具,将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起

目的是为了提高开发者日常的开发效率,只适用在开发阶段

关于配置方面,在webpack配置对象属性中通过devServer属性提供,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1// ./webpack.config.js
2const path = require('path')
3
4module.exports = {
5 // ...
6 devServer: {
7 contentBase: path.join(__dirname, 'dist'),
8 compress: true,
9 port: 9000,
10 proxy: {
11 '/api': {
12 target: 'https://api.github.com'
13 }
14 }
15 // ...
16 }
17}

devServetr里面proxy则是关于代理的配置,该属性为对象的形式,对象中每一个属性就是一个代理的规则匹配

属性的名称是需要被代理的请求路径前缀,一般为了辨别都会设置前缀为 /api,值为对应的代理匹配规则,对应如下:

  • target:表示的是代理到的目标地址
  • pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite
  • secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false
  • changeOrigin:它表示是否更新代理后请求的 headers 中host地址

二、工作原理

proxy工作原理实质上是利用http-proxy-middleware 这个http代理中间件,实现请求转发给其他服务器

举个例子:

在开发阶段,本地地址为http://localhost:3000,该浏览器发送一个前缀带有/api标识的请求到服务端获取数据,但响应这个请求的服务器只是将请求转发到另一台服务器中

1
2
3
4
5
6
7
8
9
1const express = require('express');
2const proxy = require('http-proxy-middleware');
3
4const app = express();
5
6app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
7app.listen(3000);
8
9// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar

三、跨域

在开发阶段, webpack-dev-server 会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost 的一个端口上,而后端服务又是运行在另外一个地址上

所以在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题

通过设置webpack proxy实现代理请求后,相当于浏览器与服务端中添加一个代理者

当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地

在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常接收数据

注意:服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制

一、Object.defineProperty

定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

为什么能实现响应式

通过defineProperty 两个属性,getset

  • get

属性的 getter 函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值

  • set

属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined

下面通过代码展示:

定义一个响应式函数defineReactive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1function update() {
2 app.innerText = obj.foo
3}
4
5function defineReactive(obj, key, val) {
6 Object.defineProperty(obj, key, {
7 get() {
8 console.log(`get ${key}:${val}`);
9 return val
10 },
11 set(newVal) {
12 if (newVal !== val) {
13 val = newVal
14 update()
15 }
16 }
17 })
18}

调用defineReactive,数据发生变化触发update方法,实现数据响应式

1
2
3
4
5
1const obj = {}
2defineReactive(obj, 'foo', '')
3setTimeout(()=>{
4 obj.foo = new Date().toLocaleTimeString()
5},1000)

在对象存在多个key情况下,需要进行遍历

1
2
3
4
5
6
7
8
1function observe(obj) {
2 if (typeof obj !== 'object' || obj == null) {
3 return
4 }
5 Object.keys(obj).forEach(key => {
6 defineReactive(obj, key, obj[key])
7 })
8}

如果存在嵌套对象的情况,还需要在defineReactive中进行递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1function defineReactive(obj, key, val) {
2 observe(val)
3 Object.defineProperty(obj, key, {
4 get() {
5 console.log(`get ${key}:${val}`);
6 return val
7 },
8 set(newVal) {
9 if (newVal !== val) {
10 val = newVal
11 update()
12 }
13 }
14 })
15}

当给key赋值为对象的时候,还需要在set属性中进行递归

1
2
3
4
5
6
1set(newVal) {
2 if (newVal !== val) {
3 observe(newVal) // 新值是对象的情况
4 notifyUpdate()
5 }
6}

上述例子能够实现对一个对象的基本响应式,但仍然存在诸多问题

现在对一个对象进行删除与添加属性操作,无法劫持到

1
2
3
4
5
6
7
1const obj = {
2 foo: "foo",
3 bar: "bar"
4}
5observe(obj)
6delete obj.foo // no ok
7obj.jar = 'xxx' // no ok

当我们对一个数组进行监听的时候,并不那么好使了

1
2
3
4
5
6
7
1const arrData = [1,2,3,4,5];
2arrData.forEach((val,index)=>{
3 defineProperty(arrData,index,val)
4})
5arrData.push() // no ok
6arrData.pop() // no ok
7arrDate[0] = 99 // ok

可以看到数据的api无法劫持到,从而无法实现数据响应式,

所以在Vue2中,增加了setdelete API,并且对数组api方法进行一个重写

还有一个问题则是,如果存在深层的嵌套对象关系,需要深层的进行监听,造成了性能的极大问题

小结

  • 检测不到对象属性的添加和删除
  • 数组API方法无法监听到
  • 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

二、proxy

Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了

ES6系列中,我们详细讲解过Proxy的使用,就不再述说了

下面通过代码进行展示:

定义一个响应式方法reactive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1 function reactive(obj) {
2 if (typeof obj !== 'object' && obj != null) {
3 return obj
4 }
5 // Proxy相当于在对象外层加拦截
6 const observed = new Proxy(obj, {
7 get(target, key, receiver) {
8 const res = Reflect.get(target, key, receiver)
9 console.log(`获取${key}:${res}`)
10 return res
11 },
12 set(target, key, value, receiver) {
13 const res = Reflect.set(target, key, value, receiver)
14 console.log(`设置${key}:${value}`)
15 return res
16 },
17 deleteProperty(target, key) {
18 const res = Reflect.deleteProperty(target, key)
19 console.log(`删除${key}:${res}`)
20 return res
21 }
22 })
23 return observed
24}

测试一下简单数据的操作,发现都能劫持

1
2
3
4
5
6
7
8
9
10
11
1const state = reactive({
2 foo: 'foo'
3})
4// 1.获取
5state.foo // ok
6// 2.设置已存在属性
7state.foo = 'fooooooo' // ok
8// 3.设置不存在属性
9state.dong = 'dong' // ok
10// 4.删除属性
11delete state.dong // ok

再测试嵌套对象情况,这时候发现就不那么 OK 了

1
2
3
4
5
6
1 const state = reactive({
2 bar: { a: 1 }
3})
4
5// 设置嵌套对象属性
6 state.bar.a = 10 // no ok

如果要解决,需要在get之上再进行一层代理

1
2
3
4
5
6
7
8
9
10
11
12
13
1function reactive(obj) {
2 if (typeof obj !== 'object' && obj != null) {
3 return obj
4 }
5 // Proxy相当于在对象外层加拦截
6 const observed = new Proxy(obj, {
7 get(target, key, receiver) {
8 const res = Reflect.get(target, key, receiver)
9 console.log(`获取${key}:${res}`)
10 return isObject(res) ? reactive(res) : res
11 },
12 return observed
13}

三、总结

Object.defineProperty只能遍历对象属性进行劫持

1
2
3
4
5
6
7
8
1function observe(obj) {
2 if (typeof obj !== 'object' || obj == null) {
3 return
4 }
5 Object.keys(obj).forEach(key => {
6 defineReactive(obj, key, obj[key])
7 })
8}

Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1function reactive(obj) {
2 if (typeof obj !== 'object' && obj != null) {
3 return obj
4 }
5 // Proxy相当于在对象外层加拦截
6 const observed = new Proxy(obj, {
7 get(target, key, receiver) {
8 const res = Reflect.get(target, key, receiver)
9 console.log(`获取${key}:${res}`)
10 return res
11 },
12 set(target, key, value, receiver) {
13 const res = Reflect.set(target, key, value, receiver)
14 console.log(`设置${key}:${value}`)
15 return res
16 },
17 deleteProperty(target, key) {
18 const res = Reflect.deleteProperty(target, key)
19 console.log(`删除${key}:${res}`)
20 return res
21 }
22 })
23 return observed
24}

Proxy可以直接监听数组的变化(pushshiftsplice

1
2
3
1const obj = [1,2,3]
2const proxtObj = reactive(obj)
3obj.psuh(4) // ok

Proxy有多达13种拦截方法,不限于applyownKeysdeletePropertyhas等等,这是Object.defineProperty不具备的

正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外setdelete方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
1// 数组重写
2const originalProto = Array.prototype
3const arrayProto = Object.create(originalProto)
4['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(method => {
5 arrayProto[method] = function () {
6 originalProto[method].apply(this.arguments)
7 dep.notice()
8 }
9});
10
11// set、delete
12Vue.set(obj,'bar','newbar')
13Vue.delete(obj),'bar')

Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9

使用场景

  • 拦截和监视外部对对象的访问

  • 降低函数或类的复杂度

  • 在复杂操作前对操作进行校验或对所需资源进行管理

    保障数据类型的准确性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let numericDataStore={count:0,amount:1234,total:14}
    numericDataStore=new Proxy(numericDataStore,{
    set(target,key,value,proxy){
    if(typeof value!='number'){
    throw Error("属性只能是Number类型")
    }
    return Reflect.set(target,key,value,proxy)
    }
    });
    numericDataStore.count="foo"
    numericDataStore.count=33;

    实现观察者模式

1
2
3
4
5
6
7
8
9
//观察者模式,观察者函数都放在Set集合,当修改obj的值,会在set函数中拦截,自动执行Set所有的观察者
const queueObservers=new Set();
const observe=fn=>queueObservers.add(fn)//将观察者函数加入队列
const observable=obj=>new Proxy(obj,{set});//返回一个原始对象的Proxy代理,拦截赋值操作,触发充当观察者的每个函数
function set(target,key,value,receiver){
const res=Reflect.set(target,key,value,receiver);
queueObservers.forEach(observer=>observer());//触发充当观察者的每个函数
return res;
}

声明一个私有的apiKey,便于api这个对象内部的方法调用,但不希望外部也能够访问api._apiKey

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let api={
_apiKey:'123456def'
}
const RESTRICTED=['_apiKey']
api=new Proxy(api,{
get(target,key,proxy){
if(RESTRICTED.indexOf(key)>-1){
throw Error(`${key}不可访问`)
}
return Reflect.get(target,key,proxy)
},
set(target,key,value,proxy){
if(RESTRICTED.indexOf(key)>-1){
throw Error(`${key}不可修改`)
}
return Reflect.set(target,key,value,proxy)
}
})
console.log(api._apiKey);
api._apiKey='12345';//上述都抛出错误

代理就是一种由你创建的特殊对象,它“封装”另一个普通对象,或者说挡在这个普通对象前面,你可以在代理对象上注册特殊的处理函数(trap),代理上执行各种操作的时候会调用这个程序

1
2
3
4
5
6
7
8
9
10
11
12
var obj={a:1},
handles={
get(target,key,context){
//target==obj,context==pobj,key为属性名
console.log("accessing",key);
return Reflect.get(target,key,context)
}
}
pobj=new Proxy(obj,handlers);
obj.a;//1
pobj.a;//acessing:a
//1

这里的映射是有意对称的,每个代理处理函数在对应的元编程任务执行的时候进行拦截,而每个Reflefct工具在一个对象上执行相应的元编程任务,每个代理函数都有一个自动调用相应的Reflect工具的默认定义。

一、介绍

定义: 用于定义基本操作的自定义行为

本质: 修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(meta programming)

元编程(Metaprogramming,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作

一段代码来理解

1
2
3
4
5
6
7
1#!/bin/bash
2# metaprogram
3echo '#!/bin/bash' >program
4for ((I=1; I<=1024; I++)) do
5 echo "echo $I" >>program
6done
7chmod +x program

这段程序每执行一次能帮我们生成一个名为program的文件,文件内容为1024行echo,如果我们手动来写1024行代码,效率显然低效

  • 元编程优点:与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译

Proxy 亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

二、用法

Proxy为 构造函数,用来生成 Proxy 实例

1
1var proxy = new Proxy(target, handler)

参数

target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))

handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为

handler解析

关于handler拦截属性,有如下:

  • get(target,propKey,receiver):拦截对象属性的读取
  • set(target,propKey,value,receiver):拦截对象属性的设置
  • has(target,propKey):拦截propKey in proxy的操作,返回一个布尔值
  • deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
  • ownKeys(target):拦截Object.keys(proxy)for...in等循环,返回一个数组
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),返回一个布尔值
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作

Reflect

若需要在Proxy内部调用对象的默认行为,建议使用Reflect,其是ES6中操作对象而提供的新 API

基本特点:

  • 只要Proxy对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在
  • 修改某些Object方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false
  • Object操作都变成函数行为

下面我们介绍proxy几种用法:

get()

get接受三个参数,依次为目标对象、属性名和 proxy 实例本身,最后一个参数可选

1
2
3
4
5
6
7
8
9
10
11
1var person = {
2 name: "张三"
3};
4
5var proxy = new Proxy(person, {
6 get: function(target, propKey) {
7 return Reflect.get(target,propKey)
8 }
9});
10
11proxy.name // "张三"

get能够对数组增删改查进行拦截,下面是试下你数组读取负数的索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1function createArray(...elements) {
2 let handler = {
3 get(target, propKey, receiver) {
4 let index = Number(propKey);
5 if (index < 0) {
6 propKey = String(target.length + index);
7 }
8 return Reflect.get(target, propKey, receiver);
9 }
10 };
11
12 let target = [];
13 target.push(...elements);
14 return new Proxy(target, handler);
15}
16
17let arr = createArray('a', 'b', 'c');
18arr[-1] // c

注意:如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1const target = Object.defineProperties({}, {
2 foo: {
3 value: 123,
4 writable: false,
5 configurable: false
6 },
7});
8
9const handler = {
10 get(target, propKey) {
11 return 'abc';
12 }
13};
14
15const proxy = new Proxy(target, handler);
16
17proxy.foo
18// TypeError: Invariant check failed

set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身

假定Person对象有一个age属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy保证age的属性值符合要求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1let validator = {
2 set: function(obj, prop, value) {
3 if (prop === 'age') {
4 if (!Number.isInteger(value)) {
5 throw new TypeError('The age is not an integer');
6 }
7 if (value > 200) {
8 throw new RangeError('The age seems invalid');
9 }
10 }
11
12 // 对于满足条件的 age 属性以及其他属性,直接保存
13 obj[prop] = value;
14 }
15};
16
17let person = new Proxy({}, validator);
18
19person.age = 100;
20
21person.age // 100
22person.age = 'young' // 报错
23person.age = 300 // 报错

如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1const obj = {};
2Object.defineProperty(obj, 'foo', {
3 value: 'bar',
4 writable: false,
5});
6
7const handler = {
8 set: function(obj, prop, value, receiver) {
9 obj[prop] = 'baz';
10 }
11};
12
13const proxy = new Proxy(obj, handler);
14proxy.foo = 'baz';
15proxy.foo // "bar"

注意,严格模式下,set代理如果没有返回true,就会报错

1
2
3
4
5
6
7
8
9
10
11
1'use strict';
2const handler = {
3 set: function(obj, prop, value, receiver) {
4 obj[prop] = receiver;
5 // 无论有没有下面这一行,都会报错
6 return false;
7 }
8};
9const proxy = new Proxy({}, handler);
10proxy.foo = 'bar';
11// TypeError: 'set' on proxy: trap returned falsish for property 'foo'

deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1var handler = {
2 deleteProperty (target, key) {
3 invariant(key, 'delete');
4 Reflect.deleteProperty(target,key)
5 return true;
6 }
7};
8function invariant (key, action) {
9 if (key[0] === '_') {
10 throw new Error(`无法删除私有属性`);
11 }
12}
13
14var target = { _prop: 'foo' };
15var proxy = new Proxy(target, handler);
16delete proxy._prop
17// Error: 无法删除私有属性

注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错

Reflect.apply()

静态方法 **Reflect**.apply() 通过指定的参数列表发起对目标(target)函数的调用。

1
Reflect.apply(target, thisArgument, argumentsList)

参数

  • target

    目标函数。

  • thisArgument

    target函数调用时绑定的this对象。

  • argumentsList

    target函数调用时传入的实参列表,该参数应该是一个类数组的对象。

返回值

返回值是调用完带着指定参数和 this 值的给定的函数后返回的结果。

可取消代理

普通代理总是陷入到目标对象,并且在创建之后不能修改——只要还保持着对这个代理的引用,代理的机制就将维持下去,如果你想要的创建一个在你想要停止它作为代理时便可以被停止的代理,可以创建可取消代理。(revocable proxy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj={a:1},
handlers={
get(target,key,context){
console.log('accessing:',key);
return target[key];
}
};
var p=Proxy.revocable(obj,handlers);//返回一个有两个属性proxy和revoke的对象
const pobj=p.proxy;
const prevoke=p.revoke;
console.log(pobj.a);
prevoke();//代理被取消后,任何对它的访问都会抛出错误
pobj.a()//Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

可取消代理可能的应用场景:在应用中把代理分发到第三方,其中管理你的模型数据,而不是给出真实模型本身的引用。如果你的模型对象改变或被替换,就可以使分发出去的代理失效,这样第三方就可以知晓变化并请求更新到这个模型的引用。

使用代理:

代理在前:

代理与目标交流,首先与代理交互,通过与代理交互来增加某些特殊的规则,这些是message本身没有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var messages=[],
handlers={
get(target,key){
if(typeof target[key]=='string'){
return target[key].replace(/[^w]/g,"");//过滤标点符号
}
return target[key];//所有其他的传递下去
},
set(target,key,val){
//设定唯一字符串,改为小写
if(typeof val=='string'){//值为字符串,且是唯一值添加元素
if(target.indexOf(val)==-1){
target.push(val.toLowerCase());
}
}
return true;
}
}
var messages_proxy=new Proxy(messages,handlers);
messages_proxy.push('heLLo...',42,"wOrlD!!","WoRld!!");
messages_proxy.forEach(val=>{
console.log(val);
})
messages.forEach(v=>console.log(v));//hello... world!! world!!

代理在后:

让目标与代理交流,而不是代理与目标交流。代理只能与主对象交互,实现方式就是把proxy对象放到主对象的[[Prototype]]链中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var handlers={
get(target,key,context){
return function(){
context.speak(key+"!")
}
}
},
catchall=new Proxy({},handlers),
greeter={
speak(who="someone"){
console.log("hello",who)

}
}
Object.setPrototypeOf(greeter,catchall);//将代理设置为目标对象的__proto___
greeter.speak();
greeter.speak("world");
greeter.everyone();//检查[[Prototype]]链会查看catchall是否有everyone属性,然厚代理的get()处理函数介入并返回一个用访问的属性名("everyone")调用speak()函数

访问不存在的属性名抛出错误

代理在前

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
var obj={
a:1,
foo(){
console.log("a:",this.a)
}
},
handlers={
get(target,key,context){
if(Reflect.has(target,key)){
return Reflect.get(target,key,context)
}
else{
throw Error("no such property/method")
}
},
set(target,key,val,context){
if(Reflect.has(target,key)){
return Reflect.set(target,key,val,context)
}else{
throw Error("no such property/method")
}
}
},
pobj=new Proxy(obj,handlers)
pobj.a=3;
pobj.foo();
pobj.b=4;//Uncaught Error: no such property/method

代理在后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var handlers={
get(){
throw "no such property/method"
},
set(){
throw "no such property/method"

}
}
var pobj=new Proxy({},handlers);
var obj={
a:1,
foo(){
console.log("a:",this.a)
}
}
Object.setPrototypeOf(obj,pobj);
obj.a=3;
obj.foo();
obj.b=4;//Uncaught Error: no such property/methodxxc

代理实现多个[[Prototype]]链接

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
var obj1={
name:"obj-1",
foo(){
console.log("obj1.foo",this.name)
}
},obj2={
name:"obj-2",
foo(){
console.log("obj2.foo",this.name)
},
bar(){
console.log("obj2.bar",this.name)
}
},
handlers={
get(target,key,context){
if(Reflect.has(target,key)){
return Reflect.get(target,key,context)
}else{
//伪装多个[[Prototype]]
for(let p of target[Symbol.for("[[Prototype]]")]){
if(Reflect.has(p,key)){
return Reflect.get(p,key,context)
}
}
}
}
},
obj3=new Proxy({
name:"obj3",
baz(){
this.foo();
this.bar()
}
},handlers)
//伪装多个[[Prototype]]的链接
obj3[Symbol.for("[[Prototype]]")]=[obj1,obj2];
obj3.baz();
//obj1.foo:obj-3
//obj2.var:obj-3

XMLHttpRequest对象

XHR对象有一个readyState属性,表示当前处在请求/响应过程的哪个阶段

0:未初始化

1:已打开(open),已调用open()方法,未调用send()方法

2:已发送(send)已调用send()方法,尚未收到响应

3:接受中(Receiving),已经收到部分响应

4:完成(Complete),已经收到所有响应,可以使用

每次readyState从一个值变为另一个值,会触发readystatechange事件,可以借此机会检查readyState的值,一般来说,我们只关心readyState值是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
var form = document.getElementById("myForm");
let xhr=new XMLHttpRequest();
var formData = new FormData(form);
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if((xhr.status>=200&&xhr.status<300)||xhr.status===304){
alert(xhr.responseText)
}else{
alert("Request was unsuccessful:"+xhr.status)
}


}
xhr.open("post","http://127.0.0.1:3007/api/login",true)
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')//设置发送请求的头部
xhr.timeout=1000;//设置超时
xhr.ontimeout=function(){
alert("Request didn not return in a second")
}
xhr.send(serialize(form))
//使用FormData
//xhr.send(new FormData(form))

}

凭据请求:

默认情况下,跨域请求不提供凭据(cookie,HTTP认证和客户端SSL证书),可以通过withCredentials属性设置为true来表明请求会发送凭据,如果服务器允许待凭据的请求,那么可以在响应中包含如下HTTP头部:Access-Control-Allow-Credentials:true

如果发送了凭据请求而服务器返回的响应中没有这个头部,则浏览器不会把响应交给JavaScript,服务器也可以在预检请求的响应中发送这个HTTP头部,以表明这个源允许发送凭据请求

Fetch API

fetch()只有一个必须的参数input,这个参数是要获取的资源的URL,这个方法返回一个期约;

当你的服务端返回的数据是 JSON 格式时,你肯定希望 fetch 返回给你的是一个普通 JavaScript 对象,然而你拿到的是一个 Response 对象,而真正的请求结果 —— 即 response.body —— 则是一个 ReadableStream 。

此外, Response 还限制了响应内容的重复读取和转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var button=document.querySelector('[type=button]')
button.onclick=function(){
var form = document.getElementById("myForm");
let paramsHeaders=new Headers({
'Content-Type':'application/x-www-form-urlencoded'
});
fetch('http://127.0.0.1:3007/api/login',{// 服务端返回 {"name": "test", "age": 1} 字符串
method:'POST',
body:serialize(form),
headers:paramsHeaders
}).then((res)=>{
return res.json()// 将 response.body 通过 JSON.parse 转换为 JS 对象并且记得return给下一个.then使用,或者使用res.text()
},(err)=>{
console.log(err)
})

常见Fetch请求模式

发送JSON数据

1
2
3
4
5
6
7
8
9
let payload=JSON.stringfy({foo:'bar'});
let jsonHeaders=new Headers({
'Content-Type':'application/json'
})
fetch('/send-me-json',{
method:'POST',
body:payload,
headers:jsonHeaders
})

请求体中发送参数

1
2
3
4
5
6
7
8
9
let payload='foo=bar&baz=qux';
let paramheaders=newHeaders({
'Content-Type':'application/x-www-form-urlencoded'
})
fetch('/send-me-params',{
method:'POST',
body:payload,
headers:paramHeaders
})

发送文件:

fetch()序列化并发送文件字段中的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
let imageFormData=new FormData();
let imageInput=document.querySelector("input[type='file']");
//上传多个文件
/*imageInput=document.querySelctor("input[type='file'][multiple]");
for(let i=0;i<imaeInput.files.length;++i){
imageFormData.append('image',imageInput.files[i])
}*/
imageFormData.append('image',imageInput.files[0]);
fetch('/img-upload',{
method:'POST',
body:imageFormData
})

中断请求:

通过AbortController/AbortSignal对中断请求,调用AbortController.abort()会中断所有网络的传输

1
2
3
4
5
6
let abortController=new AbortController();
fetch('wikipedia.zip',{signal:abortController.signal})
.catch(()=>{console.log('aborted');
//10ms中断请求
setTimeout(()=>abortController.abort(),10)
})

Request对象

通过构造函数初始化Request对象,接收两个参数,第一个参数是一个input参数,一般是URL,第二个参数是一个init对象,没有在init对象中涉及的值会使用默认值

创建Request对象副本

  • 创建Request构造函数
  • 使用clone()方法

使用构造函数:

1
2
3
4
5
6
let r1=new Request('http://foo.com');
let r2=new Request(r1,{method:'POST'});
console.log(r1.method);//GET
console.log(r2.method);//POST
//这种克隆方式不能总得到一模一样的副本

使用clone

请求体被读取后再克隆会导致抛出TypeError

1
2
3
4
5
6
7
let r=new Request('http://foo.com');
r.clone();
new Request(r);//没有错
r.text();//设置bodyUsed为true
r.clone();//TypeError


在fetch()中使用Request对象

如果有第二个参数init对象,也会覆盖传入请求对象的值

1
2
3
4
5
6
7
8
9
10
let r=new Request('http://127.0.0.1:3007/api/login');
fetch(r,{
method:'POST',
body:serialize(form),
headers:paramsHeaders
}).then((res)=>{
return res.json()
},(err)=>{
console.log(err)
})

要基于包含请求体的相同Request对象多次调用fetch(),必须在第一次发送fetch()请求前调用clone()

1
2
3
4
let r=new Request(...);
fetch(r.clone());
fetch(r.clone());
fetch(r);

Response对象

产生Response对象的主要方式是调用fetch(),它返回一个最后会解决Response对象的期约

响应状态信息

1

其它问题

  1. fetch 不支持同步请求

大家都知道同步请求阻塞页面交互,但事实上仍有不少项目在使用同步请求,可能是历史架构等等原因。如果你切换了 fetch 则无法实现这一点。

  1. fetch 不支持取消一个请求

使用 XMLHttpRequest 你可以用 xhr.abort() 方法取消一个请求(虽然这个方法也不是那么靠谱,同时是否真的「取消」还依赖于服务端的实现),但是使用 fetch 就无能为力了,至少目前是这样的。

  1. fetch 无法查看请求的进度

使用 XMLHttpRequest 你可以通过 xhr.onprogress 回调来动态更新请求的进度,而这一点目前 fetch 还没有原生支持。

区别:

==允许在相等比较中做强制类型转换而===不允许

1
2
3
4
var a=42;
var b="42";
a===b;//false;
a==b;//true

ES5中定义:

  1. 如果Type(x)是数字,Type(y)是字符串,则返回x==ToNumber(y)的结果
  2. 如果Type(x)是字符串,Type(y)是数字,则返回ToNumber(x)==y的结果

其他类型和布尔类型之间的相等比较

1
2
3
4
var a='42';
var b=true;
a==b;//false
//ToNumber()将b转为1,变成'42'==1,又把'42'转为42就出现不相等

规范:

1.如果Type(x)是布尔类型,则返回ToNumber(x)==y的结果

2.如果Type(y)是布尔类型,则返回x==ToNumber(y)的结果

这个结果是错误的,因为’42‘本身是真值,要避免==true,==false

1
2
3
4
5
6
var a='42';
//不要这样用,不成立
if(a==true)
if(a)
if(!!a)
if(Boolean(a))

null和undefined之间的相等比较

规范:

  1. 如果x为null,y为undefined,则结果为true
  2. 如果x为undefined,y为null,则结果为true

因此判断a是否是null或者undefined时直接判断a==null即可

对象和非对象的相等比较

规范:

  1. 如果Type(x)是字符串或者数字,Type(y)是对象,则返回x==ToPrimitive(y)的结果
  2. 如果Type(x)是对象,Type(y)是字符串或数字,则返回ToPrimitive(x)==y的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a=42;
var b=[42];
a==b;//true,变成42=='42'=>42==42
var a="abc";
var b=Object(a);
a===b;//false
a==b;//true,b通过ToPrimitive强制类型转换为"abc"
//没有对应封装对象,null和undefined不能被封装,下面返回false
var a==null;
var b=Object(a);
a==b;//false
var a=undefined;
var b=Object(a);
a==b;//false
var a=NAN;
var b=Object(a);
a==b;//false,因为NAN==NAN本身是false

显示类型转换

1

显示转换,即我们很清楚可以看到这里发生了类型的转变,常见的方法有:

  • Number()
  • parseInt()
  • String()
  • Boolean()

Number()

将任意类型的值转化为数值

先给出类型转换规则:

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
Number(324) // 324

// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324

// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN

// 空字符串转为0
Number('') // 0

// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0

// undefined:转成 NaN
Number(undefined) // NaN

// null:转成0
Number(null) // 0

// 对象:通常转换成NaN(除了只包含单个数值的数组)
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5

从上面可以看到,Number转换的时候是很严格的,只要有一个字符无法转成数值,整个字符串就会被转为NaN

parseInt()

parseInt相比Number,就没那么严格了,parseInt函数逐个解析字符,遇到不能转换的字符就停下来

1
1parseInt('32a3') //32

String()

可以将任意类型的值转化成字符串

给出转换规则图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 数值:转为相应的字符串
String(1) // "1"

//字符串:转换后还是原来的值
String("a") // "a"

//布尔值:true转为字符串"true",false转为字符串"false"
String(true) // "true"

//undefined:转为字符串"undefined"
String(undefined) // "undefined"

//null:转为字符串"null"
String(null) // "null"

//对象
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

Boolean()

可以将任意类型的值转为布尔值,转换规则如下:

1
2
3
4
5
6
7
8
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true

“|”运算符

它仅执行ToInt32转换

1
2
3
4
0|=0//0
0|NAN;//0
0|Infinity;//0
0|-Infinity;//0

以上字符无法以32位格式呈现,他们都来自64位IEE754标准,因此ToInt返回0

“~”运算符

它首先将值转为32位数字,然后执行字位操作“非”,对每一位进行反转,也就是返回2的补码,x相当于-(x+1),-(x+1)唯一能够得到0的x值时-1,也就是说如果x为-1,和一些数字值连在一起就会返回假值,其他情况返回真值

而-1是一个“哨位值”,即在各个类型中被赋予了特殊含义的值,如indexOf(),在字符串中找到指定字符返回子字符串所在位置,否则返回-1

1
2
3
4
5
6
7
8
9
10
11
var a="Hello world";
~a.indexOf("lo");//-4
if(~a.indexOf("lo")){//true
//找到匹配

}
~a.indexOf("ol");//0,假值
!~a.indexOf("ol");//true
if(!~a.indexOf("ol")){//true
//没有找到匹配
}

隐式类型转换

在隐式转换中,我们可能最大的疑惑是 :何时发生隐式转换?

我们这里可以归纳为两种情况发生隐式转换的场景:

  • 比较运算(==!=><)、ifwhile需要布尔值地方
  • 算术运算(+-*/%

除了上面的场景,还要求运算符两边的操作数不是同一类型

自动转换为布尔值

在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用Boolean函数

可以得出个小结:

  • undefined
  • null
  • false
  • +0
  • -0
  • NaN
  • “”

除了上面几种会被转化成false,其他都换被转化成true

自动转换成字符串

遇到预期为字符串的地方,就会将非字符串的值自动转为字符串

具体规则是:先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串

常发生在+运算中,一旦存在字符串,则会进行字符串拼接操作

1
2
3
4
5
6
7
8
1'5' + 1 // '51'
2'5' + true // "5true"
3'5' + false // "5false"
4'5' + {} // "5[object Object]"
5'5' + [] // "5"
6'5' + function (){} // "5function (){}"
7'5' + undefined // "5undefined"
8'5' + null // "5null"

自动转换成数值

除了+有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值

1
2
3
4
5
6
7
8
9
10
11
1'5' - '2' // 3
2'5' * '2' // 10
3true - 1 // 0
4false - 1 // -1
5'1' - 1 // 0
6'5' * [] // 0
7false / '5' // 0
8'abc' - 1 // NaN
9null + 1 // 1
10undefined + 1 // NaN
null`转为数值时,值为`0` 。`undefined`转为数值时,值为`NaN

||和&&

在JS中||和&&返回的是两个操作数中的一个

||和&&首先会对第一个操作数(a和c)执行条件判断,如果其不是布尔值,就进行TooBoolean强制类型转换,然后再进行条件判断

对于||:

如果条件判断结果为true返回第一个操作数,为false则返回第二个操作数的值。

&&则相反,如果条件判断结果为true返回第二个操作数,如果为fakse返回第一个操作数

a||b相当于a?a:b;

a&&b相当于a?b:a

常见的用||

1
2
3
4
5
6
function foo(a,b){
a=a||"hello";//检查变量a如果未赋值则赋值一个默认值hello
b=b||"world";
console.log(a+" "+b);

}

&&:

1
2
3
4
5
function foo(){

}
var a=42;
a&&foo();//foo()只有在条件判断a通过时才会调用foo();如果条件判断未通过,则a&&foo()就会终止

假值:

  • undefined
  • null
  • false
  • +0 -0 NAN
  • “ “

假值列表以外的其他对象为真值

假值对象

封装了假值的对象为true

Js为基本数据类型提供了封装对象,称为原生函数(String,Number,Boolean等)

原生构造函数都有自己的prototype对象,这些对象包含其对应子类型所特有的行为特征

有些原生原型不是普通对象

1
2
3
4
5
6
console.log(typeof Function.prototype)//function
console.log(typeof RegExp.prototype)//object
console.log(Function.prototype);//空函数
console.log(Array.prototype.length);//0,空数组
console.log(RegExp.prototype.toString())//"/?:/"空正则表达式

Symbol是具有唯一性的特殊标识符,符号可以用作属性名,但无论是在代码还是控制开发台都无法查看和访问它的值,只会显示Symbol(Symbol.create)类似的值

ES6中有预定义的符号,以Symbol的静态属性形式出现,如Symbol.create,Symbol.iterator,可以这样使用:

1
obj[Symbol.iterator]=function(){}

可以使用Symbol(…)原生构造函数来自定义符号,但是不能用new

1
2
3
4
5
6
7
var mysym=Symbol("my on symbol")
console.log(mysym.toString())//Symbol(my on symbol)
console.log(typeof mysym);//symbol
var a={}
a[mysym]="foorbar"
console.log(Object.getOwnPropertySymbols(a));//通常用来命名私有或特殊属性,替代下划线(_)前缀的属性
//[Symbol(my on symbol)]

Symbol不是对象,是一种简单的标量基本类型