面向对象编程

面向对象

面向对象 MDN

JavaScript面向对象编程

命名空间

如果你在公司 var a = 1;是要被离职的

因为你不知道 a以前是什么

1
2
3
4
5
6
7
8
// 全局命名空间
var MYAPP = MYAPP || {};
//等价于
if(MYAPP){
var MYAPP = MYAPP
}else{
var MYAPP = {}
}

代码技巧 先看看表达式的值是什么

1
2
3
4
5
6

1 || 2 //1

1 && 2 && 3 //3

0||null||undefined||1 //1

重要概念

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

c && d

//它们的值基本不可能是 true / false
//五个falsy值

// 0 NaN '' null undefined
1
2
3
4
5
6
7
8
9
10
11
12
1 && 0  // 0

1 && 0 && 2 // 0

1 && 0 && console.log(3) //打印3不会执行 会返回 0

1 && 2 && 3 // 3


0 || undefined || null || 1 // 1

0 || undefined || null || 1 || 0 || null // 1

回到之前的命名空间写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var app = {}//危险代码

if(app){
//app = app 这句可以不写 是废话
}
}else{
app = {}
}

// 安全写法

var app2 = app2 || {}

if(app2){
app2 = app2
}else{
app2 = {}
}

this复习

待更新。。。。

new是做啥的

当我们需要100个士兵 就要写100次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var 士兵们 = [];
var 士兵 ;
for(var i=0;i<100;i++){
士兵 = {
ID: i, // 用于区分每个士兵
兵种:"美国大兵",
攻击力:5,
生命值:42,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}
士兵们.push(士兵)
}

这样是很耗内存的因为 每个士兵自己都有 这些方法 但这些方法内容是一样的 就会出现500个方法

1
2
3
4
5
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }

士兵共有属性

proto__ __ 这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var 士兵共有属性 = {
兵种:"美国大兵",
攻击力:5,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}

var 士兵们 = [];
var 士兵 ;
for(var i=0;i<100;i++){
士兵 = {
ID: i, // 用于区分每个士兵
生命值:42
}
/*实际工作中不要这样写__proto__ 不是标准属性*/

士兵.__proto__ = 士兵共有属性

士兵们.push(士兵)
}

这样你会感觉士兵和 士兵共有属性很松散

使用函数把 士兵 和 士兵共有属性 包裹起来

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 士兵共有属性 = {
兵种:"美国大兵",
攻击力:5,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}

function create士兵(id){
var 士兵 = {
ID:i,//
生命值:42
}

士兵.__proto__ = 士兵共有属性

return 士兵
}


//创建士兵
var 士兵们 = [];
var 士兵 ;
for(var i=0;i<100;i++){
士兵们.push( create士兵(i) )
}

这样 create士兵 和 士兵共有属性还是没太大关联性

把 “士兵的共有属性” 放到 “create士兵” 的属性 里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function create士兵(id){
var 士兵 = {
ID:i,//
生命值:42
}

士兵.__proto__ = create士兵.士兵共有属性

return 士兵
}

create士兵.士兵共有属性 = {
兵种:"美国大兵",
攻击力:5,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}

JS之父的关怀

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 士兵(id){
//step1
//var temp = {}
//step2
//this = temp
//step3
//this.__proto__ = create士兵.prototype
this.ID = id;
this.生命值 = 42
//step4
//return this
}

士兵.prototype = {
constructor:士兵,
兵种:"美国大兵",
攻击力:5,
行走:function(){ /*走俩步的代码*/},
奔跑:function(){ /*狂奔的代码*/ },
死亡:function(){ /*Go die*/ },
攻击:function(){ /*糊他熊脸*/ },
防御:function(){ /*护脸*/ }
}

// 你要做的事情 就是 加个new

var 士兵 = new 士兵(id);

原理 new 士兵()之后

  1. 生成一个临时对象 temp
  2. 然后用 this指向 这个临时对象 temp
  3. 然后让this的原型 等于这个函数的prototype
  4. 而且也不用return 这个事 也帮你做了 帮你返回this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj =new Object()

自有属性 空
obj.__proto__ === Object.prototype

var array = new Array('a','b','c')
自有属性 0:'a' 1:'b' 2:'c' length:3
array.__proto__ === Array.prototype
Array.prototype.__proto__ === Object.prototype

var fn =new Function('x','y','return x+y')
自有属性 length:2 不可见函数体: 'return x+y'
fn.__proto__ === Function.prototype


Array is a Function
Array = function(){...}
Array.__proto__ === Function.prototype

CRM之从零搭建一个数据库系统

引入LeanCloud

https://leancloud.cn/ 上注册一个账户。

前端很少涉及数据库 把数据永久的保存在一个地方 跨设备访问数据

阿里云最低配的30元/月

leancloud这是一个将服务器能力免费给你用的网站

注册的时候有一点要注意 至少8位密码 包含数字大写小写字母

一个自带数据库和增删改查(CRUD)功能的后台系统。

拥有:

  1. 登录注册、手机验证码功能(收费)
  2. 存储任意信息
  3. 读取任意信息
  4. 搜索任意信息
  5. 删除任意信息
  6. 更新任意信息

等功能。

没有见过的东西?!

不要慌,使用 Copy-Run-Modify 套路即可。

演示如何使用 LeanCloud 存一个 Hello World

  1. 创建一个应用 cv-20180128
  2. 引入 av-min.js,得到 window.AV
  3. 初始化 AV 对象(代码直接拷)
  4. 新建一条数据(代码直接拷)
  • 创建你的应用之后
  • 点击右上角帮助/快速入门
  • 选择我们的语言 js
  • 然后开始看文档 copy

先引入cdn 验证是否成功

1
2
3
<script src="//cdn1.lncld.net/static/js/3.5.0/av-min.js"></script>
//F12
console.log(AV) //不报错代表成功

注意每个应用有自己的key 不要使用我的key

加入留言模块

message.js

1
2
3
4
5
6
7
8
9
var APP_ID = 'fqJwht8RtYXK5nVQ1uaDjUc5-gzGzoHsz';
var APP_KEY = 'rLVeL79CM0mAVV6GNa27xDpb';

AV.init({
appId: APP_ID,
appKey: APP_KEY
});

console.log('ok') //成功打印代表成功了

验证 你的应用跟我不一样

1
ping "fqjwht8r.api.lncld.net"

ping成功后在继续测试

然后在项目中编写如下测试代码:

message.js

1
2
3
4
5
6
7
8
9
10
11
//创建了  TestObject表
var TestObject = AV.Object.extend('TestObject');
// 在表中创建一行数据
var testObject = new TestObject();
// 数据的内容是 words:'Hello World' 保存
// 如果保存成功,则允许alert
testObject.save({
words: 'Hello World!'
}).then(function(object) {
alert('LeanCloud Rocks!');
})

控制台 > 存储 > 数据 > TestObject你会看到变化

CRM之 M

1
2
3
4
5
6
7
var hjx = AV.Object.extend('Hjx');
var obj = new hjx();
obj.save({
words: 'hi'
}).then(function(object) {
alert('LeanCloud Rocks!');
})

刷新你会发现多了个Hjx的表

再次修改

1
2
3
4
5
6
7
8
var hjx = AV.Object.extend('Hjx');
var obj = new hjx();
obj.save({
words: 'hi',
xxxx:'sdfjklsdjflsdjkfadklas',
}).then(function(object) {
alert('LeanCloud Rocks!');
})

刷新之后你会发现 Hjx里 多了一列 xxxx 这就是数据库

套路

  1. 创建一个应用 cv-20180128
  2. 引入av-main.js得到window.AV
  3. 初始化 AV 对象 (代码直接拷)
  4. 新建一条数据(代码直接拷)

添加section留言模块

1
2
3
4
5
6
7
<section>
<h2>留言</h2>
<form id="postMessageForm" style="width:700px;margin:50px auto;">
<input type="text" name="content">
<input type="submit" value="提交">
</form>
</section>
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
var APP_ID = 'fqJwht8RtYXK5nVQ1uaDjUc5-gzGzoHsz';
var APP_KEY = 'rLVeL79CM0mAVV6GNa27xDpb';

AV.init({
appId: APP_ID,
appKey: APP_KEY
});

let myForm = document.querySelector('#postMessageForm')

myForm.addEventListener('submit',function(e){
e.preventDefault(); //防止刷新页面
let content = myForm.querySelector('input[name=content]').value;
var Message = AV.Object.extend('Message');
var message = new Message();
message.save({
'content':content,
}).then(function(object) {
alert('存入成功')
console.log(object)
})
})

// var hjx = AV.Object.extend('Hjx');
// var obj = new hjx();
// obj.save({
// words: 'hi',
// xxxx:'sdfjklsdjflsdjkfadklas',
// }).then(function(object) {
// console.log(object)
// })
技术细节 监听submit同时能监听用户点回车

如果你不这么写就要这样写

1
2
3
4
5
6
7
8
let btn = myForm.querySelector('input[type=submit]')
btn.addEventListener('click',function(){});
let input = myForm.querySelector('input[name=content]')
input.addEventListener('keypress',function(e){
if(e.keyCode === 13){

}
});

MVC版简历message模块为mvc分离的

MVC之VC

项目引入链接如下

基础版代码

引入轮播后

模块化之后

立即执行函数的使用避免全局污染

模块间通信与闭包的使用

初步提取view和controller

进阶后的vc

引入轮播

在啥都不清楚下引入 swiper

swiper插件

get Started

  1. 安装

    1
    npm install swiper //安装这个swiper 或者直接用cdn的链接
  2. 引入相关文件

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
...
<link rel="stylesheet" href="path/to/swiper.min.css">
</head>
<body>
...
<script src="path/to/swiper.min.js"></script>
</body>
</html>
  1. 添加页面模板html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- Slider main container -->
    <div class="swiper-container">
    <!-- Additional required wrapper -->
    <div class="swiper-wrapper">
    <!-- Slides -->
    <div class="swiper-slide">Slide 1</div>
    <div class="swiper-slide">Slide 2</div>
    <div class="swiper-slide">Slide 3</div>
    ...
    </div>
    <!-- If we need pagination -->
    <div class="swiper-pagination"></div>

    <!-- If we need navigation buttons -->
    <div class="swiper-button-prev"></div>
    <div class="swiper-button-next"></div>

    <!-- If we need scrollbar -->
    <div class="swiper-scrollbar"></div>
    </div>
  2. 加入样式文件

1
2
3
4
.swiper-container {
width: 600px;
height: 300px;
}
  1. 初始化JS代码
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
<body>
<script>
var mySwiper = new Swiper ('.swiper-container', {
// Optional parameters
direction: 'vertical',
loop: true,

// If we need pagination
pagination: {
el: '.swiper-pagination',
},

// Navigation arrows
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},

// And if we need scrollbar
scrollbar: {
el: '.swiper-scrollbar',
}
})
</script>
</body>

美化你的作品集 肯定不能直接截图你的作品除非很好看

https://unsplash.com/ 去这里搜你作品的关键字 一定能找到你要的精美图片

注意图片的比例 如果是轮播要一致

不要吝惜你原来无用的代码直接删掉

不断的调修磨你的作品集样式 调到你觉得满意为止

代码链接

模块化

  1. 将style里的CSS放到一个地方 main.css

  2. 将如下的各个功能独立成对应的js模块

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
131
132
133
134
135
136
<script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.1.0/js/swiper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.1.1/Tween.min.js"></script>
<script>
var mySwiper = new Swiper ('.swiper-container', {
// Optional parameters
//direction: 'vertical',
loop: true,

// If we need pagination
pagination: {
el: '.swiper-pagination',
},

// Navigation arrows
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
})
</script>
<script>
siteWelcome.classList.remove('active');
// setTimeout(function(){
// siteWelcome.classList.remove('active');
// },100);

//给每个模块添加偏移距离的样式 然后当页面滚到对应模块时 去除该样式
let specialTags = document.querySelectorAll('[data-x]');
for(let i=0;i<specialTags.length;i++){
specialTags[i].classList.add('offset');
}

setTimeout(() => {
yyy();
}, 0);

window.onscroll = function(){
//console.log(window.scrollY)
// 添加粘性导航
if(window.scrollY>0){
topNavBar.classList.add('sticky');
}else{
topNavBar.classList.remove('sticky');
}

yyy();

}

function yyy(){
//滚动到对应位置 高亮对应标签
let specialTags = document.querySelectorAll('[data-x]');
//找到现在位置距离顶部最近的那个
let minIndex = 0;
for(let i=1;i<specialTags.length;i++){
//console.log(specialTags[i].offsetTop)
if(Math.abs(specialTags[i].offsetTop -window.scrollY) < Math.abs(specialTags[minIndex].offsetTop -window.scrollY)){
minIndex = i;
}
}

//每个出现的模块添加过渡效果 该元素就是离顶部最近的模块 移除偏移的样式
specialTags[minIndex].classList.remove('offset');

//选中当前模块对应的a标签
let id = specialTags[minIndex].id;
let aTag = document.querySelector('a[href="#'+id+'"]');
//找到aTag的爸爸 就是 li 给它加 active 同时去除其他li的active
let li = aTag.parentNode;
let aLi = li.parentNode.children;
for(var i=0;i<aLi.length;i++){
// aLi[i].classList.remove('active');
//操作两个 样式
aLi[i].classList.remove('highlight');
}
//li.classList.add('active');
li.classList.add('highlight');
// **解决bug的关键在于不要让两个不相干的动作操作同一个样式
//此时已经有bug了
// 当前位置高亮了 但是 鼠标移入移出后 就不高亮了 因为移入移出和 滚动都是操作一个active
//滚动到技能时候 虽然选中了 但是他的列表出现了
}


//这样找只有子菜单的li有 边框效果 ==》找所有li
// let liTags = document.getElementsByClassName('menuTigger');
let liTags = document.querySelectorAll('nav.menu > ul > li');
//监听含有子菜单的topNavBar menu
for(let i=0;i<liTags.length;i++){
liTags[i].onmouseenter = function(e){
let li = e.currentTarget;
li.classList.add('active');
}
liTags[i].onmouseleave = function(e){
let li = e.currentTarget;
li.classList.remove('active');
}
}

function animate(time) {
requestAnimationFrame(animate);
TWEEN.update(time);
}
requestAnimationFrame(animate);

//监听导航菜单定位到对应位置
let aTags = document.querySelectorAll('nav.menu > ul > li > a');
for(let i=0;i<aTags.length;i++){
aTags[i].onclick = function(e){
//阻止默认行为 就是a标签的 锚点
e.preventDefault();
let a = e.currentTarget;
let href = a.getAttribute('href');
let element = document.querySelector(href);

let top = element.offsetTop;
//当前位置
let currentTop = window.scrollY;
//目标点
let targetTop = top - 80;
//总路程
let s = targetTop - currentTop;
//总时间 因为路程可能是负数 但是 时间不能是负的
let t = Math.abs((s/100)*300);
t = t>500?500:t;
var coords = { x:currentTop};
var tween = new TWEEN.Tween(coords)
.to({ x: targetTop },t)
.easing(TWEEN.Easing.Quadratic.Out)
.onUpdate(function() {
window.scrollTo(0,coords.x);
})
.start();
}
}
</script>

模块化之后

1
2
3
4
5
6
<script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.1.0/js/swiper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.1.1/Tween.min.js"></script>
<script src="./js/init-swiper.js"></script>
<script src="./js/auto-slide-up.js"></script>
<script src="./js/sticky-topbar.js"></script>
<script src="./js/smoothly-navigation.js"></script>

代码链接

立即执行函数的使用

auto-slide-up.js

里有这样一个变量 specialTags 只要在其他js里重新赋值这个js里的代码就有问题了

1
2
3
let specialTags = document.querySelectorAll('[data-x]');

...

其他js文件里 specialTags = ‘我偏手贱改这个值’ 上面就废了

错误示范使用花括号包裹你的代码 ==>这是错的 js里没有块级作用域

1
2
3
4
5
{
var mySwiper = new Swiper ('.swiper-container', {
...
})
}

使用函数作用域

1
2
3
4
5
function xxx(){
模块的代码
}

xxx.call();

新的问题是 这个函数”xxx”又成了全局变量也是会被污染

匿名函数

1
2
3
4
// 这样会报错  这样会报错  这样会报错 
function(){
模块的代码
}.call()
1
2
3
!function(){
模块的代码
}.call()
  1. 我们不想要全局变量
  2. 我们要使用局部变量
  3. ES5里,只有函数有局部变量
  4. 于是我们声明了一个 function xxx,然后xxx.call()
  5. 这个时候xxx是全局变量(全局函数)
  6. 所以我们不能给这个函数名字
  7. function(){}.call()
  8. 但是chrome不支持直接这样调用会报错
  9. 试出来一个方法可以不报错

    推荐第一种

    • (1) !function(){}.call() 用来改变函数的返回值但是我们根本不在乎这个返回值
    • (2) (function(){}).call() 在函数外面加个()

      第二种有bug

    • (3) fn31231231231231212.call() 不推荐 随机数的方式

      1
      2
      3
      4
      5
      6
      7
      a  
      ()

      // 等价于 a()

      xxx
      (function(){}).call() 相当于调用xxx 报错 所以不推荐

立即执行函数的使用避免全局污染

闭包的使用

模块间通信 mod1想访问 mod2的变量

js引入

1
2
<script src="./js3/module_1.js"></script>
<script src="./js3/module_2.js"></script>

module_1.js

1
2
3
4
5
!function(){
var person = window.person = {
name:'hjx'
}
}.call()

module_2.js

1
2
3
4
!function(){
var person = window.person;
console.log(person);
}.call()

闭包的使用

module_1_1.js

1
2
3
4
5
6
7
8
9
10
!function(){
var person = window.person = {
name:'hjx',
age:18
}
window.personAgeGrowUp = function(){
person.age += 1;
return person.age;
}
}.call()

module_2_2.js

1
2
3
4
!function(){
var newAge = window.personAgeGrowUp();
console.log(newAge)
}.call()
  1. 立即执行函数使得 person 无法被外部访问
  2. 闭包使得匿名函数可以操作 person
  3. window.personAgeGrowUp 保存了匿名函数的地址
  4. 任何地方都可以使用 window.personAgeGrowUp
    =>任何地方都可以使用 window.personAgeGrowUp 操作person
    ,但是不能直接访问person

代码链接

MVC的V和C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--作品集的轮播   这就是view -->
<div id="mySlides">
<div class="swiper-container">
<!-- Additional required wrapper -->
<div class="swiper-wrapper">
<!-- Slides -->
<img src="./images/nav-page.jpg" class="swiper-slide" alt="">
<img src="./images/canvas.png" class="swiper-slide" alt="">
<img src="./images/slides.png" class="swiper-slide" alt="">
</div>
<!-- If we need pagination -->
<div class="swiper-pagination"></div>
</div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
!function(){
//提取view
var view = document.querySelector('#mySlides');
var mySwiper = new Swiper (view.querySelector('.swiper-container'), {
loop: true,
pagination: {
el: '.swiper-pagination',
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
})
}.call()

其他的模块同样提取view

1
2
3
4
5
6
7
8
9
10
!function(){
var view = document.querySelector('#topNavBar');
window.addEventListener('scroll',function(){
if(window.scrollY>0){
view.classList.add('sticky');
}else{
view.classList.remove('sticky');
}
})
}.call()

添加controller

1
2
3
4
5
6
7
8
9
10
11
12
13
!function(){
var view = document.querySelector('#topNavBar');
var controller = function(view){
window.addEventListener('scroll',function(){
if(window.scrollY>0){
view.classList.add('sticky');
}else{
view.classList.remove('sticky');
}
})
}
controller.call(null,view)
}.call()

其余模块同上

代码链接

sticky-topbar.js

初级版

1
2
3
4
5
6
7
8
9
10
11
12
13
!function(){
var view = document.querySelector('#topNavBar');
var controller = function(view){
window.addEventListener('scroll',function(){
if(window.scrollY>0){
view.classList.add('sticky');
}else{
view.classList.remove('sticky');
}
})
}
controller.call(null,view);
}.call()

进阶一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var controller = {
view:null,
init:function(view){
this.view = view;
window.addEventListener('scroll',function(){
if(window.scrollY>0){
view.classList.add('sticky');
}else{
view.classList.remove('sticky');
}
})
}
}
controller.init.call(null,view);

再次进阶一点点 bindEvents里面的this有问题传递的是window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var controller = {
view:null,
init:function(view){
this.view = view;
this.bindEvents();
//this.bindEvents.call(this)
},
bindEvents:function(){
var view = this.view;
window.addEventListener('scroll',function(){
if(window.scrollY>0){
view.classList.add('sticky');
}else{
view.classList.remove('sticky');
}
})
}
}
controller.init.call(null,view);

再次进阶一点点2 bindEvents里面的this有问题传递的是window

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
!function(){
var view = document.querySelector('#topNavBar');
var controller = {
view:null,
init:function(view){
this.view = view;
this.bindEvents();
//this.bindEvents.call(this)
},
bindEvents:function(){
var view = this.view;
window.addEventListener('scroll',function(){
console.log(this)
var view = this.view;
if(window.scrollY>0){
this.active();
}else{
this.deactive();
}
}).bind(this);
//箭头函数没有this
},
active:function(){
this.view.classList.add('sticky');
},
deactive:function(){
view.classList.remove('sticky');
}
}
controller.init(view);
}.call()

解决this问题 使用箭头函数

示例代码

1
2
3
4
var f = ()=> console.log(this);

f() //window
f.call({name:'hjx'}) // window

箭头函数内外this不变

1
2
3
4
5
6
7
8
9
10
11
bindEvents:function(){
var view = this.view;
window.addEventListener('scroll',()=>{
if(window.scrollY>0){
this.active();
}else{
this.deactive();
}
})
//箭头函数没有this
}

代码链接

自己实现AJAX

AJAX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let request = new XMLHttpRequest();

request.open('get','/xxx');

request.send();

request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
console.log(request.responseText);
}else{
console.log(request.status);
}
}
}

request.open(‘请求方式’,url,[可选参数]);

get

1
request.open('get','/xxx');

post

1
request.open('post','/xxx');

request.setHeaders(key,value);

自己设置请求第二部分

1
2
3
4
5
6
7
8
9
let request = new XMLHttpRequest();

request.open('get','/xxx');

request.setRequestHeader('name','hjx');
request.setRequestHeader('Content-Type','x-www-form-urlencoded');

request.send();
...

request.send(‘你设置的内容’);

设置请求的第四部分

注意:如果是get方式 第四部分是不可见的

1
2
3
4
5
6
7
let request = new XMLHttpRequest();

request.open('post','/xxx');
request.setRequestHeader('name','hjx');
request.setRequestHeader('Content-Type','x-www-form-urlencoded');

request.send('我偏要设置请求的第四部分');

客户端获取服务器的响应部分

1
2
3
4
5
6
7
8
9
10
11
12
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
console.log(request.status);
console.log(request.statusText);
console.log(request.getAllResponseHeaders());
console.log(request.responseText);
}else{
console.log(request.status);
}
}
}

问题

  1. JS可以任意设置请求 Header 吗?
    • 第一部分 request.open(‘get’,’/xxx’)
    • 第二部分 request.setRequestHeader(‘name’,’hjx’);
    • 第四部分 request.send(‘a=1&b=2’);
  2. JS可以获取响应的所有部分 吗?
    • 第一部分 request.status/request.statusText
    • 第二部分 request.getResponseHeader()/request.getAllResponseHeaders()
    • 第四部分 request.responseText

jQuery.ajax()

1
2
3
4
5
6
7
8
9
10
11
12
window.jQyery = function(nodeOrSelector){
let nodes = {};
/************省略非本次知识的重点*************/
}
return nodes;
}

window.jQuery.ajax = function(){

}

window.$ = window.jQuery;

先搞出ajax的函数体

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
window.jQuery.ajax = function(url,method,body,successFn,failFn){

let request = new XMLHttpRequest();

request.open(method,url);

request.send(body);

request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
successFn.call(undefined,request.responseText)
}else{
failFn.call(undefined,request)
}
}
}
}

btn.addEventListener('click',function(){
window.jQuery.ajax(
'/xxx',
'get',
'a=1&b=2',
(res)=>{console.log(res)},
(err)=>{console.log()}
);
})

你睡了一晚上,第二天基本ajax里的参数你基本就忘光了

优化一下

提取参数

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
window.jQuery.ajax = function(options){
let url = options.url;
let method = options.method;
let body = options.body;
let successFn = options.successFn;
let failFn = options.failFn;
let request = new XMLHttpRequest();
request.open(method,url);
request.send(body);
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
successFn.call(undefined,request.responseText)
}else{
failFn.call(undefined,request.status)
}
}
}
}

btn.addEventListener('click',function(){
window.jQuery.ajax({
url:'/xxx',
method:'get',
body:'a=1&b=2',
successFn:(res)=>{console.log(res)},
failFn:(err)=>{console.log()}
});
})

如果我想设置请求头咋办?

继续添加往options上挂载key就好了

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
window.jQuery.ajax = function(options){
let url = options.url;
let method = options.method;
//继续设置参数
let headers = options.headers;
let body = options.body;
let successFn = options.successFn;
let failFn = options.failFn;
let request = new XMLHttpRequest();
request.open(method,url);
for(let key in headers){
let value = headers[key];
request.setRequestHeader(key,value);
}
request.send(body);
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
successFn.call(undefined,request.responseText)
}else{
failFn.call(undefined,request.status)
}
}
}
}

btn.addEventListener('click',function(){
window.jQuery.ajax({
url:'/xxx',
method:'get',
headers:{
'Content-Type':'application/x-www-form-urlencoded',
'name':'hjx'
},
body:'a=1&b=2',
successFn:(res)=>{console.log(res)},
failFn:(err)=>{console.log()}
});
})

借鉴jQuery.ajax(url[,settings]) 接受一个或者两个参数

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
window.jQuery.ajax = function(options){
let url;
if(arguments.length===1){
url = options.url;

}else if(arguments.length===2){
url = arguments[0];
options = arguments[1];
}
let method = options.method;
let headers = options.headers;
let body = options.body;
let successFn = options.successFn;
let failFn = options.failFn;
......
}

btn.addEventListener('click',function(){
window.jQuery.ajax(
'/xxx',
{
method:'get',
headers:{
'Content-Type':'application/x-www-form-urlencoded',
'name':'hjx'
},
body:'a=1&b=2',
successFn:(res)=>{console.log(res)},
failFn:(err)=>{console.log()}
}
);
})

ES6的方式写以上代码

恢复只接收一个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
window.jQuery.ajax = function(options){
//ES6解构赋值
let {url,method,body,successFn,failFn,headers} = options;

let request = new XMLHttpRequest();
request.open(method,url);
for(let key in headers){
let value = headers[key];
request.setRequestHeader(key,value);
}
request.send(body);
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
successFn.call(undefined,request.responseText)
}else{
failFn.call(undefined,request.status)
}
}
}
}

更进一步的优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    //ES6解构赋值
window.jQuery.ajax = function( {url,method,body,successFn,failFn,headers}){
let request = new XMLHttpRequest();
request.open(method,url);
for(let key in headers){
let value = headers[key];
request.setRequestHeader(key,value);
}
request.send(body);
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
successFn.call(undefined,request.responseText)
}else{
failFn.call(undefined,request.status)
}
}
}
}

ES6方式交换a和b的值

1
2
3
4
5
6
7
8
9
10
//以前的方式
var a = 1;
var b = 2;
var temp = a;
a = b;
b = temp;
//ES6方式
var x = 1;
var y = 2;
[x,y] = [y,x];

ES6 把x的值当作key

1
2
3
4
5
6
7
8
9
10
//以前
var x = '???';
var o = {};
o[x]= true;

//ES6
var x = '???';
var o = {
[x]:true
}

Promise规范

jQuery已经实现的方式

1
2
3
4
5
6
7
8
9
$.ajax({
url:'/xxx',
method:'get'
}).then(
(res)=>{console.log(res) return '成功';},
(err)=>{console.log(err)} return '失败';})
.then(
(res)=>{console.log(res)}, //这里的res就是 如果上一个成功了 它的返回值
(err)=>{console.log(err)}) // 同上

Promise版本的ajax

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
window.jQuery.ajax = function({url,method,body,headers}){
return new Promise(function(resolve,reject){
//ES6解构赋值
let request = new XMLHttpRequest();
request.open(method,url);
for(let key in headers){
let value = headers[key];
request.setRequestHeader(key,value);
}
request.send(body);
request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
resolve.call(undefined,request.responseText)
}else{
reject.call(undefined,request.status)
}
}
}
});
}

btn.addEventListener('click',function(){
window.jQuery.ajax({
url:'/xxx',
method:'get',
headers:{
'Content-Type':'application/x-www-form-urlencoded',
'name':'hjx'
},
body:'a=1&b=2'
}).then(
(res)=>{console.log(res)},
(err)=>{console.log(err)}
)
})

AJAX是什么鬼

ajax

请先确保你有node 或自己开一个wamp服务器否则别往下看了 浪费时间

if(你有node)

1
2
3
4
安装请用
npm install -g http-server
卸载请用
npm uninstall -g http-server

1
2
3
mkdir demo 创建目录
cd demo 切换目录
touch index.html 创建一个html文件

如何发请求

form发请求

1
2
3
4
5
<form action="xxx" method="get" target="aa">
<input type="text" name="password" value="222">
<input type="submit" value="提交">
</form>
<iframe src="" name="aa" frameborder="0"></iframe>
  1. 开启你的服务器

    • 如果安装了 http-server 你就在demo目录 ! demo目录 ! demo目录 ! 打开终端

      1
      http-server -c-1
    • 如果是wamp 你就开启服务器就行了 在你指定的端口 demo要放在wamp指定的www文件夹里

  2. 把给你的url 在 chrome 里打开

  3. 请按F12 / 打开调试
  4. 点击 Network
  5. 点击提交按钮
  6. 你会看到出现如下一行内容 请点击

  7. 点击之后 会看到前后台联调必不可少的几个部分

  1. 分别展开列表

    点击view source

    点击view source

    点击view source

  1. get请求的四部分

  1. 修改form里的 method=”post” 我知道你不会改的 所以看代码吧!
1
2
3
4
5
<form action="xxx" method="post" target="aa">
<input type="text" name="password" value="22">
<input type="submit" value="提交">
</form>
<iframe src="" name="aa" frameborder="0"></iframe>

form发请求

1
2
3
4
5
<form action="xxx" method="get" target="aa">
<input type="text" name="password" value="222">
<input type="submit" value="提交">
</form>
<iframe src="" name="aa" frameborder="0"></iframe>

a标签发请求

1
2
3
4
<a id="link" href="/xxx"></a>
<script>
link.click();
</script>

img发请求

1
<img src="/xxx" alt="">

link发请求

1
2
3
4
5
6
<script>
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/xxx';
document.head.appendChild(link);
</script>

script发请求

1
2
3
4
5
<script>
var script = document.createElement('script');
script.src = '/xxx';
document.head.appendChild(script);
</script>
  • 用 form 发请求,但是会刷新页面或新开页面
  • 用 a 发 get 请求,但是也会刷新页面或新开页面
  • 用 img 发 get 请求,但是只能以图片的形式展示
  • 用 link 发 get 请求,但是只能以css/favicon的形式展示
  • 用 script 发 get 请求,但是只能以脚本的形式运行
有没有什么方式可以实现
  1. get / post / put / delete 请求都行
  2. 想以什么形式展示就以什么形式展示

微软的突破

IE 5 率先在 JS 中引入 ActiveX 对象(API),使得 JS 可以直接发起 HTTP 请求。
随后 Mozilla、 Safari、 Opera 也跟进(抄袭)了,取名 XMLHttpRequest,并被纳入 W3C 规范

IE6当时全球90%的市场份额于是做了愚蠢的决定:他以为自己的在浏览器已经天下无敌了没必要那么多人去维护了就留了两个人做日常维护,把其他人都开除了,然后chrome看准了这个时机异军突起,2年就占据了40%的市场份额,现在IE是人人喊打

前端的起源就是因为IE发明了ajax

AJAX

Jesse James Garrett 讲如下技术取名叫做 AJAX:async JavaScript and XML

  1. 使用 XMLHttpRequest 发请求
  2. 服务器返回 XML 格式的字符串
  3. JS 解析 XML,并更新局部页面

如何使用 XMLHttpRequest

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
myButton.addEventListener('click', (e)=>{
let request = new XMLHttpRequest()
request.open('get', '/xxx') // 配置request
request.send()
request.onreadystatechange = ()=>{
if(request.readyState === 4){
console.log('请求响应都完毕了')
console.log(request.status)
if(request.status >= 200 && request.status < 300){
console.log('说明请求成功')
console.log(typeof request.responseText) // 'string'
console.log(request.responseText)
let string = request.responseText
// 把符合 JSON 语法的字符串
// 转换成 JS 对应的值
let object = window.JSON.parse(string)
// JSON.parse 是浏览器提供的
console.log(typeof object)
console.log(object)
console.log('object.note')
console.log(object.note)

}else if(request.status >= 400){
console.log('说明请求失败')
}

}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 后端代码
if(path==='/xxx'){
response.statusCode = 200
response.setHeader('Content-Type', 'text/json;charset=utf-8')
response.write(`
{
"note":{
"to": "aaa",
"from": "hjx",
"heading": "打招呼",
"content": "hello"
}
}
`)
response.end()

注意readStaus有五种状态

  • 0 open()方法还未被调用.
  • 1 open()方法已经被调用.
  • 2 send()方法已经被调用, 响应头和响应状态已经返回.
  • 3 响应体下载中; responseText中已经获取了部分数据.
  • 4 整个请求过程已经完毕.

如何验证状态码经过了 01234 ? 请按以下代码顺序书写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let request = new XMLHttpRequest();
// open未调用
console.log(request.readyState)
request.onreadystatechange = function(){
console.log(request.readyState)
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
console.log(request.responseText);
}else{
console.log(request.status);
}
}
}
request.open('get','/xxx');
request.send();

考点:请使用原生JS实现AJAX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let request = new XMLHttpRequest();

request.open('get','/xxx');

request.send();

request.onreadystatechange = function(){
if(request.readyState === 4){
if(request.status >= 200 && request.status <300){
console.log(request.responseText);
}else{
console.log(request.status);
}
}
}

JSON 是一门语言 (抄袭的JS)

{name:’aa’}是对象 JS里这种方式叫做 对象字面量表达式

{“name”:”aa”} key必须是字符串 必须以 “ 开头 , 以 “ 结尾

JS VS JSON

JS JSON
undefined 没有
null null
[‘a’,’b’] [“a”,”b”]
function(){} 没有
{name:’hjx’} {“name”:”hjx”}
‘hjx’ “hjx”
var a = { }; a.self = a 搞不定没有变量
1
2
3
1. JSON没有抄袭 function和 undefined ,也没有抄袭symbol因为 symbol是最近出来的
2. JSON 的字符串必须是 "
3. obj.__proto__ 没有原型链

请看下面的后台返回

返回的是符合JSON对象类型语法的字符串 , 绝对不是JS的“对象”

你才返回”对象” ,你全家才返回”对象”

后台是无法返回”对象” 给前端的 ,他只能返回字符串

1
2
3
4
5
6
7
8
9
response.write(`
{
"note":{
"to": "aaa",
"from": "hjx",
"heading": "打招呼",
"content": "hello"
}
}

同源策略

为什么form表单提交没有跨域问题,但ajax提交有跨域问题?

  • 因为原页面用 form 提交到另一个域名之后,原页面的脚本无法获取新页面中的内容。所以浏览器认为这是安全的。
  • 而 AJAX 是可以读取响应内容的,因此浏览器不能允许你这样做。如果你细心的话你会发现,其实请求已经发送出去了, 你只是拿不到响应而已。
  • 所以浏览器这个策略的本质是,一个域名的JS,在未经允许的情况下,不得读取另一个域名的内容。但浏览器并不阻止你向另一个域名发送请求。

只有 协议+端口+域名 一模一样才允许发 AJAX 请求

一模一样!一模一样!一模一样!一模一样!一模一样!一模一样!一模一样!一模一样!一模一样!

http://12306.cnhttp://www.12306.cn 是不同域名

问题

http://baidu.com 可以向 http://www.baidu.com 发AJAX请求吗 ?

http://www.baidu.com 可以向 http://baidu.com 发AJAX请求吗 ?

如果没有同源策略的限制

任何一个网站都可以访问你支付宝的余额,QQ空间,你写的私密博客。

没有同源策略的浏览器就会让你的所有隐私泄露。

如果 A站的前端实在想访问B站的内容

可以 B站后台设置一个参数 Accept-Control-Allow-Origin

1
2
//我允许这个域名访问
response.setHeader('Accept-Control-Allow-Origin', 'http://A.com:8001')

浏览器必须保证

  • 协议+端口+域名 一模一样才允许发AJAX请求
  • CORS 可以告诉浏览器 我俩是一家的别阻止他

CORS 跨域资源共享

Cross-Origin Resource Sharing

谨记

  1. 你才返回”对象”,你全家才返回 “对象”
  2. JS是一门语言,JSON是另一门语言,JSON 这门语言抄袭了JS
  3. AJAX就是用JS发请求
  4. 响应的第四部分是字符串,可以用JSON语法表示一个对象,也可以用JSON语法表示一个数组,还可以用XML语法,还可以用HTML语法,还可以用CSS语法,还可以用JS语法,还可以用你自创的语法。

JSONP是什么鬼

什么是jsonp

从零开始构建

1
2
3
4
mkdir jsonp_demo  //创建目录 jsonp_demo
cd jsonp_demo //切换目录到 json_demo
touch server001.js
touch index001.html

版本一

server001.js

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
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]

if(!port){
console.log('请指定端口号好不啦?\nnode server.js 8888 这样不会吗?')
process.exit(1)
}

var server = http.createServer(function(request, response){
var parsedUrl = url.parse(request.url, true)
var pathWithQuery = request.url
var queryString = ''
if(pathWithQuery.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) }
var path = parsedUrl.pathname
var query = parsedUrl.query
var method = request.method

/******** 从这里开始看,上面不要看 ************/

console.log('被请求后:含查询字符串的路径\n' + pathWithQuery)

if(path=='/'){
var string = fs.readFileSync('./index001.html','utf8');
response.setHeader('Content-Type','text/html;charset=utf-8');
response.write(string);
response.end();
}else{
response.statusCode = 404;
response.end();
}

/******** 代码结束,下面不要看 ************/
})

server.listen(port)
console.log('监听 ' + port + ' 成功\n请在浏览器输入 http://localhost:' + port)

当前目录下 新建index.html main.js style.css (main.js/style.css内容为空暂时不需要)

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="./style.css">
<title>Document</title>
</head>
<body>
<h5>您的账户余额是 <span id="amount">100</span> </h5>
<button id="btn">付款1块钱</button>
<script src="./main.js"></script>
<script>
btn.addEventListener('click',()=>{
let n = amount.innerText;
let number = parseInt(n,10);
let newNumber = number - 1;
amount.innerText = newNumber;
})
</script>
</body>
</html>

运行 server001.js 所在目录 打开命令行运行

1
2
3
4
node server001.js 9999
//你会看到
监听 9999 成功
请在浏览器输入 http://localhost:9999

打开浏览器输入 http://localhost:9999

运行后

点击按钮后你会看到 余额变动如下

点击付款

问题1 每次点击按钮都会把余额减1 但是浏览器只要刷新就会重新显示100

如何长久的保存用户的数据呢?不可能你的余额永远是100吧 那岂不是有花不完的钱

长久的保存用户数据 保存到文件里

文件系统 将数据保存到服务器的文件里 这个文件专门为这个用户服务

1
2
3
touch db //创建 db 文件  

vi db //打开之后写入100

版本二

修改html里写死的100 换用一个‘匹配’字符替换 ==> “&&&amount&&&”

index002.html

1
2
3
4
5
//将这一行
<h5>您的账户余额是 <span id="amount">100</span> </h5>
//修改为
//自己定义替换的匹配字符串 "&&&amount&&&"
<h5>您的账户余额是 <span id="amount"> &&&amount&&&</span> </h5>

server002.js 修改如下内容 从db读取 用户余额

1
2
3
4
5
6
7
8
9
10
if(path=='/'){
var string = fs.readFileSync('./index002.html','utf8');
//读取db文件里用户余额
var amount = fs.readFileSync('./db','utf8'); //100
//替换 index002.html 里定义的 匹配字符 为 用户余额
string = string.replace('&&&amount&&&',amount);
response.setHeader('Content-Type','text/html;charset=utf-8');
response.write(string);
response.end();
}

这样做了之后你会发现 一刷新 数据还是100

解决长久保存

我们点击付款的时候 告诉服务器 请把数据库里的余额给我

所以我们点击付款的时候不能单独修改本地了 要去给服务器发 post请求

为什么不是get 付款啊!这么重要的操作你敢用get?

版本三

index003.html

1
2
3
4
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<form action="/pay" method="post">
<input type="submit" value="付款1块钱">
</form>

server003.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if(path=='/'){
var string = fs.readFileSync('./index003.html','utf8');
var amount = fs.readFileSync('./db','utf8'); //100
string = string.replace('&&&amount&&&',amount);
response.setHeader('Content-Type','text/html;charset=utf-8');
response.write(string);
response.end();
}else if(path=='/pay'&& method.toUpperCase() === 'POST'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1
fs.writeFileSync('./db',newAmount); //更新余额
response.write('success');
response.end();
}else{
response.statusCode = 404;
response.end();
}

再去开启你的服务器

1
2
node server003.js 9999
//在浏览器打开 http://localhost:9999

http://localhost:9999

  • 点击付款按钮
  • 跳到 http://localhost:9999/pay 页面显示 success
  • 点击浏览器返回 **注意:此时浏览器返回的是缓存页面 你要手动刷新一下
  • 用户余额减少了

你去打开db文件 发现余额改变了

以上就是最最简单、最最原始的数据库的展示

再次修改一下server003.js里的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
else if(path=='/pay'&& method.toUpperCase() === 'POST'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1
if(Math.random()>0.5){
fs.writeFileSync('./db',newAmount); //更新余额
response.write('success');
}else{
response.write('fail');
}

response.end();
}

以上是:旧时代的前后端配合

操作数据库成功返回success 失败返回fail

在一个2018年的今天

用户怎么可能忍受

  • 点击付款
  • 进入成功/失败页面
  • 点击返回
  • 点击刷新才看到你最新的数据

form表单一旦提交不管成功还是失败一定会刷新当前页面

古老方式-优化一下用户体验

用iframe承载这个form的提交 后的 success/fail响应 但是I现在已经没人用啦!

版本四

index004.html

1
2
3
4
5
6
7
<body>
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<form action="/pay" method="post" target="result">
<input type="submit" value="付款1块钱">
</form>
<iframe name="result" src="about:blank" frameborder="0"></iframe>
</body>

前端的高度洁癖 不用form向后台发送一个请求

版本五

index005.html

1
2
3
4
5
6
7
8
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<button id="btn">打钱</button>
<script>
btn.addEventListener('click',function(){
let img = document.createElement('img');
img.src = '/pay';
})
</script>

点击按钮的时候 会创建一个img标签 设置它的 src = ‘你的请求路径’ 就会触发请求

这样的缺陷就是 它无法post 它只能get

问题来了? 我们如何知道它成功or失败呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<button id="btn">打钱</button>
<script>
btn.addEventListener('click',function(){
let img = document.createElement('img');
img.src = '/pay';
//我们如何知道请求成功or失败呢?
img.onload = function(){
alert('打钱成功')
//我们帮用户刷新 但是会重绘整个页面
//window.location.reload();
//因为知道固定减1 所以我帮用户减1
amount.innerText = amount.innerText - 1;
}
img.onerror = function(){
alert('打钱失败')
}
})
</script>

server005.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
else if(path=='/pay'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1
if(Math.random()>0.5){
fs.writeFileSync('./db',newAmount); //更新余额
//设置响应头
response.setHeader('Content-Type','image/png');
//设置状态码
response.statusCode = 200;
//返回响应的图片 不然 会走入img.onerror 事件里
response.write(fs.readFileSync('./dog.jpg'));
}else{
response.statusCode = 400;
response.write('fail');
}

response.end();
}

这样我们就完成了 无刷新的发送请求 俗称 image发请求法

改良 script发请求法

注意动态创建script 同时设置src 必须把script添加到body里才会生效

版本六

server006.js

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
/******** 从这里开始看,上面不要看 ************/

console.log('被请求后:含查询字符串的路径\n' + pathWithQuery)

if(path=='/'){
var string = fs.readFileSync('./index006.html','utf8');
var amount = fs.readFileSync('./db','utf8'); //100
string = string.replace('&&&amount&&&',amount);
response.setHeader('Content-Type','text/html;charset=utf-8');
response.write(string);
response.end();
}else if(path=='/pay'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1
if(Math.random()>0.5){
fs.writeFileSync('./db',newAmount); //更新余额
//设置响应头
response.setHeader('Content-Type','application/javascript');
//设置状态码
response.statusCode = 200;
//返回响应内容
response.write('');
}else{
response.statusCode = 400;
response.write('fail');
}

response.end();
}else{
response.statusCode = 404;
response.end();
}

/******** 代码结束,下面不要看 ************/

index006.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<button id="btn">打钱</button>
<script>
btn.addEventListener('click',function(){
let script = document.createElement('script');
script.src = '/pay';
document.body.appendChild(script);
//我们如何知道请求成功or失败呢?
script.onload = function(){
alert('打钱成功')
}
script.onerror = function(){
alert('打钱失败')
}
})
</script>

这样每次点击会生成一个script

我们知道 script里的代码是会执行的

所以我们就不需要在 server006.js里写失败的情况 它会自动进入 script.onerror里

step1修改server006.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
else if(path=='/pay'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1

fs.writeFileSync('./db',newAmount); //更新余额
//设置响应头
response.setHeader('Content-Type','application/javascript');
//设置状态码
response.statusCode = 200;
//返回响应内容
response.write('alert("pay")');

response.end();
}

运行发现如果成功了 就会 弹出pay 然后再弹出‘打钱成功’

不用监听script.onload了

因为 成功自然会在server006.js 里 写入并执行 response.write(‘alert(“pay”)’);

版本七

index007.html

1
2
3
4
5
6
7
8
9
10
11
12
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<button id="btn">打钱</button>
<script>
btn.addEventListener('click',function(){
let script = document.createElement('script');
script.src = '/pay';
document.body.appendChild(script);
script.onerror = function(){
alert('打钱失败')
}
})
</script>

server007.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
else if(path=='/pay'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1

fs.writeFileSync('./db',newAmount); //更新余额
//设置响应头
response.setHeader('Content-Type','application/javascript');
//设置状态码
response.statusCode = 200;
//返回响应内容
response.write(
`
amount.innerText = amount.innerText - 1;
`
);

response.end();

优化上面运行后每次点击按钮页面都会多一个script标签

成功或者失败后干掉script

版本八

index008.html

注意:server里文件路径

所以我们还是得监听onload事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<button id="btn">打钱</button>
<script>
btn.addEventListener('click',function(){
let script = document.createElement('script');
script.src = '/pay';
document.body.appendChild(script);
script.onload = function(e){
alert('打钱成功')
e.currentTarget.remove();
}
script.onerror = function(e){
alert('打钱失败')
e.currentTarget.remove();
}
})
</script>

注意:不管成功还是失败script都会移除 但是script还在内存里只是从页面移除了

这个方案就叫 SRJ (Server rendered javascript)服务器返回的 javascript

无刷新局部更新页面内容的方案 在ajax之前

script是不受域名限制的 这跟跨域没关系

aa.com:9999的前端 是可以访问 bb.com:8888的后台接口的 这就是SRJ

JSONP

查看后端代码 发现– 后端要对前端代码非常了解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
else if(path=='/pay'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1

fs.writeFileSync('./db',newAmount); //更新余额
//设置响应头
response.setHeader('Content-Type','application/javascript');
//设置状态码
response.statusCode = 200;
//返回响应内容
response.write(
`
amount.innerText = amount.innerText - 1;
`
);

response.end();

bb.com:8888的后端居然要对 aa.com:9999的页面如此清楚

耦合

解耦

版本九

index009.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<button id="btn">打钱</button>
<script>
//某域名下的前端 bb.com:9999的前端
window.xxx = function(result){
alert(`得到了结果是${result}`);
}

btn.addEventListener('click',function(){
let script = document.createElement('script');
script.src = '/pay';
document.body.appendChild(script);
script.onload = function(e){
e.currentTarget.remove();
}
script.onerror = function(e){
alert('打钱失败')
e.currentTarget.remove();
}
})
</script>

server009.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
else if(path=='/pay'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1

fs.writeFileSync('./db',newAmount); //更新余额
//设置响应头
response.setHeader('Content-Type','application/javascript');
//设置状态码
response.statusCode = 200;
//返回响应内容
response.write(
`
xxx.call(undefined,'success') //执行xxx方法并返回 字符串
`
);

response.end();
}

这样的好处是什么

  • 后端不需要知道任何细节,只调用xxx.call()返回结果
  • 这样两个网站之间就可以无缝的沟通

xxx.call()是不是很丑?

  • 于是传递一个参数callback=xxx
  • callback =’你自己定义的方法名’

版本十

index010.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<button id="btn">打钱</button>
<script>
//某域名下的前端 bb.com:9999的前端
window.yyy = function(result){
alert(`得到了结果是${result}`);
}

btn.addEventListener('click',function(){
let script = document.createElement('script');
//传递自己定义的方法名
script.src = '/pay?callback=yyy';
document.body.appendChild(script);
script.onload = function(e){
e.currentTarget.remove();
}
script.onerror = function(e){
alert('打钱失败')
e.currentTarget.remove();
}
})
</script>

server010.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
else if(path=='/pay'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1

fs.writeFileSync('./db',newAmount); //更新余额
//设置响应头
response.setHeader('Content-Type','application/javascript');
//设置状态码
response.statusCode = 200;
//返回响应内容
response.write(
`
${query.callback}.call(undefined,'success')
`
);

response.end();
}

JSONP解决的问题是什么 两个网站之间如何交流

  • 因为script是不受域名限制的 ajax是受域名限制的
  • 由于不受域名限制,我就告诉对方的网站 我要请求一个数据,
  • 请你把数据给我
  • 你给我之后调用我的xxx
  • 然后把参数给我 xxx.call(undefined,参数)

传递的参数不仅仅是字符串还可以是json

1
2
3
4
5
6
7
8
response.write(
`
${query.callback}.call(undefined,{
"success":true,
"data":99
})
`
);

json

1
2
3
4
{
"success":true,
"data":99
}

json的左边是 左padding : ${query.callback}.call(undefined,

json的右边是 右padding : )

于是乎 JSON + padding = JSONP

jsonp

版本十一

index011.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
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<button id="btn">打钱</button>
<script>
btn.addEventListener('click',function(){
let script = document.createElement('script');

//生成无重复的方法名
let fnName = 'hjx'+parseInt(Math.random()*100000,10);
window[fnName] = function(result){
//根据接收的json结果做处理
if(result === 'success'){
amount.innerText = amount.innerText - 1;
}
}
//传递自己定义的方法名
script.src = '/pay?callback=' + fnName;
document.body.appendChild(script);
script.onload = function(e){
e.currentTarget.remove();
//不管成功失败都要删除生成的fnName属性
delete window[fnName];
}
script.onerror = function(e){
alert('打钱失败')
e.currentTarget.remove();
//不管成功失败都要删除生成的fnName属性
delete window[fnName];
}
})
</script>

server011.js

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
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]

if(!port){
console.log('请指定端口号好不啦?\nnode server.js 8888 这样不会吗?')
process.exit(1)
}

var server = http.createServer(function(request, response){
var parsedUrl = url.parse(request.url, true)
var pathWithQuery = request.url
var queryString = ''
if(pathWithQuery.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) }
var path = parsedUrl.pathname
var query = parsedUrl.query
var method = request.method

/******** 从这里开始看,上面不要看 ************/

console.log('被请求后:含查询字符串的路径\n' + pathWithQuery)

if(path=='/'){
var string = fs.readFileSync('./index011.html','utf8');
var amount = fs.readFileSync('./db','utf8'); //100
string = string.replace('&&&amount&&&',amount);
response.setHeader('Content-Type','text/html;charset=utf-8');
response.write(string);
response.end();
}else if(path=='/style.css'){
var string = fs.readFileSync('./style.css','utf8');
response.setHeader('Content-Type','text/css');
response.write(string);
response.end();
}else if(path=='/main.js'){
var string = fs.readFileSync('./main.js','utf8');
response.setHeader('Content-Type','text/javascript');
response.write(string);
response.end();
}else if(path=='/pay'){
//读取 db中用户余额
var amount = fs.readFileSync('./db','utf8');
var newAmount = amount - 1; //余额减1

fs.writeFileSync('./db',newAmount); //更新余额
//设置响应头
response.setHeader('Content-Type','application/javascript');
//设置状态码
response.statusCode = 200;
//返回响应内容
response.write(
`
${query.callback}.call(undefined,'success')
`
);

response.end();
}else{
response.statusCode = 404;
response.end();
}

/******** 代码结束,下面不要看 ************/
})

server.listen(port)
console.log('监听 ' + port + ' 成功\n请在浏览器输入 http://localhost:' + port)

写了那么多 其实你可以使用jQuery 几句话就能搞定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h5>您的账户余额是 <span id="amount">&&&amount&&&</span></h5>
<button id="btn">打钱</button>
<script src="//code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
btn.addEventListener('click',function(){
$.ajax({
url:"http://localhost:9999/pay",
dataType:"jsonp",
success:function(res){
console.log(res);
}
})
})
</script>

注意: $.ajax()只是把jsonp归纳到 jq里 而它不是ajax

考点1 :请问JSONP为什么不支持POST请求

  • 因为JSONP是通过动态创建script实现的
  • 我们动态创建script的时候只能用get 没有办法用post

考点2 : 什么是 JSONP?

  • 1请求方创建一个script标签src指向响应方,并传递callback =回调函数名
  • 2响应方根据callback查询参数构造一个 xxx.call(undefined,传递的内容)
  • 3浏览器接到响应,就会执行 xxx.call(undefined,传递的内容)
  • 4请求方得到他想要的数据

DOM事件

事件模型应用 点击显示浮层 点击其它地方关闭浮层

问题1 点击按钮浮层不出现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="wrapper" class="wrapper">
<button id="clickMe">点我</button>
<div id="popover" class="popover">
浮层
</div>
</div>
<script>
//点击显示浮层
clickMe.addEventListener('click', function(){
popover.style.display = 'block';
})

document.addEventListener('click', function(){
popover.style.display = 'none'
})
</script>

点击按钮的时候浮层确实出现了

但是addEventListener(‘事件’,fn,布尔值) 第三个参数不传代表false 代表冒泡

冒泡到document

document 发现事件队列里有 fn于是执行 popover.style.display = ‘none’

如何解决 ==> event.stopPropagation();

Prevents further propagation of the current event in the capturing and bubbling phases.

防止当前事件在捕获和冒泡阶段的进一步传播。

早一点的前端肯定会认为还有一个方法阻止冒泡 event.cancelBubble = true ;不好意思 该属性已废弃
1
2
3
4
5
6
7
8
9
//点击显示浮层 
clickMe.addEventListener('click', function(event){
popover.style.display = 'block';
event.stopPropagation(); //这样冒泡阶段就被禁止了
})

document.addEventListener('click', function(){
popover.style.display = 'none'
})

问题2 虽然解决了阻止冒泡 但是当你点击浮层时 浮层也会消失

这样就不能在按钮上阻止冒泡 因为点击浮层也会冒泡到wrapper

  • 因为btn会冒泡到 wrapper
  • popover会冒泡到 wrapper
  • 所以在它们的父节点做阻止冒泡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="wrapper" class="wrapper">
<button id="clickMe">点我</button>
<div id="popover" class="popover">
浮层
</div>
</div>
<script>
clickMe.addEventListener('click', function(e){
popover.style.display = 'block';
})
wrapper.addEventListener('click',function(e){
//解决点击浮层触发的冒泡
e.stopPropagation()
})

document.addEventListener('click', function(){
popover.style.display = 'none'
})
</script>

问题3 现在点击按钮浮层出现 点击浮层浮层浮层不消失 但是有性能问题

  • 100个浮层就是100个事件队列 10000个呢?

现在我们先换jq来实现刚刚的效果

jq提供了一个非常方便的方法 $(node).on(‘click’,false) 第二个参数代表 阻止默认事件 和阻止传播

又偶生出一个bug 就是.on(‘事件’,false) 你在checkbox外的任何一层 阻止默认事件 这个checkbox就无法选中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="wrapper" class="wrapper">
<button id="clickMe">点我</button>
<div id="popover" class="popover">
<input type="checkbox">浮层
</div>
</div>


$(clickMe).on('click',function(){
$(popover).show();
});
// 这样会再次出bug就是 如果浮层里有checkbox就无法选中 因为阻止了默认事件
$(wrapper).on('click',false)

$(document).on('click',function(){
$(popover).hide();
})

所以我们还是单独阻止传播

1
2
3
4
5
6
7
8
9
10
11
$(clickMe).on('click',function(){
$(popover).show();
});

$(wrapper).on('click',function(e){
e.stopPropagation(); //阻止冒泡
});

$(document).on('click',function(){
$(popover).hide();
})

解决问题3 性能问题

  • 点击按钮的时候显示浮层 同时添加一个document监听
  • 为什么监听不会触发 因为 wrapper哪里阻止了 冒泡
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $(clickMe).on('click',function(){
    $(popover).show();
    //只有显示的时候监听一次 但是不会触发 因为 wrapper上阻止了冒泡
    //这样我只监听一次 click 同时马上干掉这个监听事件
    $(document).one('click',function(){
    console.log('doc')
    $(popover).hide();
    })
    });

    //所以只能这样写 单独阻止它的冒泡 不阻止默认事件
    $(wrapper).on('click',function(e){
    e.stopPropagation(); //阻止冒泡
    });

问题4 如果不在wrapper上不阻止冒泡 点击btn为什么浮层不显示

1
2
3
4
5
6
7
8
9
$(clickMe).on('click',function(){
$(popover).show();
//只有显示的时候监听一次 但是不会触发 因为 wrapper上阻止了冒泡
//这样我只监听一次 click 同时马上干掉这个监听事件
$(document).one('click',function(){
console.log('doc')
$(popover).hide();
})
});
  • 在你点击btn的时候它会做两件事
  • 1 浮层显示出来
  • 2 document的事件队列里 添加一个函数
  • 3 事件开始冒泡
  • 4 开始发现 document的事件队列里有一个函数
  • 5 执行 document事件队列里的函数
  • 6 浮层隐藏了

DOM事件初识

DOM事件

如果你想深挖请google搜索「dom spec」 dom spec

  • Document Object Model (DOM) Level 1 只对基本的事件进行了汇总
  • Document Object Model (DOM) Level 2 增加了 Events 对事件的详细说明
  • Document Object Model (DOM) Level 3 并没有增加 Events 的相关修订
  • Document Object Model (DOM) Level 4 还在草案阶段

结论目前我们使用事件最新最广泛的标准是 dom level 2 (2000年11月3号)

dom 1 的一个经典问题

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
function print(){console.log('Hi')}
</script>

<button id="x" onclick="print">A</button>
<button id="y" onclick="print()">B</button>
<button id="z" onclick="print.call()">C</button>

<script>
x.onclick = print;
y.onclick = print();
z.onclick = print.call();
</script>

当把事件写在标签上的时候那个是正确调用 「B C」

1
2
3
写在标签上的事件
onclick="要执行的代码"
一旦用户点击,浏览器就eval("要执行的代码")

当把事件写在JS上的时候那个是正确调用 「x」

1
2
3
写在JS里的事件
onclick="函数"
一旦用户点击,浏览器就 x.onclick.call(x,函数)

dom2 事件队列

1
2
3
xxx.addEventListener('click',function(){
console.log(2)
})

你肯定问怎么比 onclick更麻烦了

  • onclick是唯一的
  • 出现两次onclick就会发生覆盖
1
2
3
4
5
6
7
xxx.onclick = function(){
console.log(1)
}
// 第一个onclick被覆盖了 只会打印2
xxx.onclick = function(){
console.log(2)
}

事件队列的好处 eventListeners

1
2
3
4
5
6
7
8
xxx.addEventListener('click',function(){
console.log(1)
})
xxx.addEventListener('click',function(){
console.log(2)
})

当xxx被点击 打印 1 和 2

事件的移除

1
2
3
4
5
6
7
8
9
10
11
12
function f1(){
console.log(1)
}
function f2(){
console.log(2)
}
xxx.addEventListener('click',f1)
xxx.addEventListener('click',f2)
//移除事件队列里的 f1
xxx.removeEventListener('click',f1)

点击xxx后 打印 2

如何实现只监听one一次的效果

1
2
3
4
5
6
function f1(){
console.log(1)
//移除事件队列里的 f1
xxx.removeEventListener('click',f1)
}
xxx.addEventListener('click',f1)

问题1 当我点击儿子的时候,我是否点击了 爸爸和爷爷 ==》yes

1
2
3
4
5
6
7
8
9
<div id="grand1">
爷爷
<div id="parent1">
爸爸
<div id="child1">
儿子
</div>
</div>
</div>

问题2 当我点击儿子的时候,三个函数是否调用 ==> yes

1
2
3
4
5
6
7
8
9
10
11
grand1.addEventListener('click',function(){
console.log('爷爷')
})

parent1.addEventListener('click',function(){
console.log('爸爸')
})

child1.addEventListener('click',function(){
console.log('儿子')
})

问题3 三个函数的执行顺序 ==> 1 2 3 or 3 2 1 答案是都可以

addEventListener(‘事件’,函数,可选参数)

第三个参数是 boolean值 不传代表false : 0 NaN undefined null false ‘’

false的话 顺序从内到外 儿子 爸爸 爷爷

true 的话 顺序从外到内 爷爷 爸爸 儿子

推荐

爷爷设置true 其他设置false

1
2
3
4
5
6
7
8
9
10
11
12
grand1.addEventListener('click',function(){
console.log('爷爷')
},true)

parent1.addEventListener('click',function(){
console.log('爸爸')
})

child1.addEventListener('click',function(){
console.log('儿子')
})
// 爷爷 儿子 爸爸

考点 如果 儿子节点既有捕获又有冒泡执行顺序是什么

1
2
3
4
5
6
7
8
9
10
child1.addEventListener('click',function(){
console.log('儿子捕获')
},true)

child1.addEventListener('click',function(){
console.log('儿子冒泡')
},false)

// 儿子捕获
// 儿子冒泡
1
2
3
4
5
6
7
8
9
10
11
child1.addEventListener('click',function(){
console.log('儿子冒泡')
},false)

child1.addEventListener('click',function(){
console.log('儿子捕获')
},true)


// 儿子冒泡
// 儿子捕获

答案是 谁写在前面谁先触发

如果触发在元素本身上是不区分捕获和冒泡的

前端的自我修养

HTML/CSS/JS 内容、样式和行为分离原则

如果你去面试可能会被问道如下问题

请你说一下你对前端内容样式行为分离的原则?

如果你初出茅庐可能会这样回答 html负责内容css负责样式js负责行为

但是这是大家都认可的真理,我为什么要解释原因呢!

正确做法!!! 你该这样用初中几何常用套路「反证法」

请看解题思路(如果你是程序员这就是「伪代码」)

1
2
问:人类为什么吃饭
答: 如果不吃饭人类就会死,所以人类需要吃饭

你该这样回答

  1. 如果html负责样式
  • 会让html变得很复杂
  • 很难区分这些标签的逻辑结构
1
2
3
4
5
<body bgcolor=red>
<center>
<font color=red>你好</font>
</center>
</body>
  1. 用css表示内容
  • 人类选不中
  • JS取不到
1
2
3
4
5
6
7
8
9
<style>
div::after{content:'你好你好你好'}
</style>
<body>
<div id="xxx"></div>
<script>
console.log(xxx.innerText) // 啥都没
</script>
</body>
  1. 用JS控制样式

你该避免使用jQuery的 show/hide方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<div id="xxx"></div>
<script src="jquery.js"></script>
<script>

$("#xxx").hide() // display:none; 隐藏没有任何问题

$("#xxx").show() // 但是 show就有问题了
/*
display:inline-block;
display:flex;
display:inline;
*/
</script>
</body>

show()之后你很难定位 div隐藏之前是什么 可能是flex也可能是 block

因为jQuery为了让你使用它的库,就特殊处理了

  • 在你hide的时候把display这个值存起来
  • 当你show的时候把display这个值赋回去

问题来了,如果div一开始display:none

show() 之后会是什么呢? 经过测试竟然是block
凭啥?凭啥?凭啥?

JS该这样使用

通过class样式 来表示不同的状态去切换

1
2
3
4
<div id="xxx"></div>

$("#xxx").addClass('active')
$("#xxx").removeClass('active')

人在江湖

有的时候需要你迫不得已的用JS去操作样式,那就只能违反原则,但是你不该总是违反原则

追溯到故事情节:天龙八部有一集阿紫被打伤乔峰各种救,去寻医,但是药店伙计告诉他没救了,此时突然一个地主家伙计来了说他家老爷不行了有没有办法,老掌柜的说快去拿“人参”,而此时纵是大侠气概的乔峰也「迫不得已」抢走了人参

基本的职业素养

不要让「img」变形

  • 你可以指定宽高,或者强行要求UI给你指定尺寸的图

关于「img」的坑

因为「img」是一个可替换元素,所以当图片下载失败的时候就会显示alt里的内容

1
<img src="xxx.jpg"  alt="图片1">

由于你没有指定宽高,假如是三个并排的img(不出意外是float或者flex-start)

由于第一张图加载速度慢,后面的img就不得不挤过来,直到图1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
正常加载
|--------| |--------||--------|
|--------| |--------||--------|
| 图1 | | 图2 || 图3 |
|--------| |--------||--------|

图1还没加载过来 没指定宽高导致后面的img补位
|图1||--------||--------|
|--------||--------|
| 图2 || 图3 |
|--------||--------|
图1回来了
|--------| |--------||--------|
|--------| |--------||--------|
| 图1 | | 图2 || 图3 |
|--------| |--------||--------|

就是因为你没有指定宽高所以在图片没回来的时候占据很小的空间,而当图1正常加载后 图2图3就必须给图1退出位置

这样的结果就会导致「重排」

重排是很耗性能的,页面性能优化的注意点就是

如果你已知图片的宽高你最好写在上面避免图片未加载的「让位」(腾地)

JS之使用局部变量

全局变量的危害

1
2
3
4
5
6
7
8
html里有一个id="parent"的div
<div id="xxx"></div>

<script>
var parent = document.getElementById('xxx');
//这句已经有问题了 你无意之间把window.parent覆盖了
console.log(parent) //现在parent已经是那个id="xxx"的div了
</script>

所以你应该避免使用全局变量,使用局部变量

局部变量

JS里的var声明只有在函数内才有块级作用域

1
2
3
4
5
6
function x(){
var parent = document.getElementById('xxx');
console.log(parent) //这里的是 id="xxx"的div
}
x.call();
console.log(parent) //这里的parent是window的属性

立即调用函数

如上节代码所示,立即调用函数就是声明一个函数然后立刻调用

因为避免使用全局变量所以你不该声明这个「x」的函数,因为这样已经产生了一个全局变量「x」

1
2
3
4
function(){
var parent = document.getElementById('xxx');
console.log(parent) //这里的是 id="xxx"的div
}()

但是这样会报错,原因是如果你直接声明一个「匿名函数 」并调用,浏览器里会直接报错说语法错误

我们可以使用如下小技巧

  1. 直接把匿名函数加调用的整体用 「括号」括起来

    1
    2
    3
    4
    5
    (
    function(){
    //相关处理
    }()
    )
  2. 直接把匿名函数用「括号」括起来,然后再调用

    1
    2
    3
    (function(){
    //相关处理
    })()
  3. 加减号或者取反(「+」「-」「!」「~」)

这些运算符是告诉浏览器我不是再声明一个函数而是在求值,但是这个值我不关心(爱是多少是多少)

1
2
3
4
5
6
7
+function(){}()

-function(){}()

!function(){}()

~function(){}()
  1. 因为这个局部变量问题的使用太过麻烦,ES6终于狠心给JS升级了
1
2
3
4
5
6
{
let parent = document.getElementById('xxx');
let a = 1;
}
console.log(parent) // 全局变量的window的parent属性
console.log(a) //报错 因为 a是局部变量

{ } 左大括号右大括号之内的内容代表块级作用域,但是你不能用var

1
2
3
4
{
var parent = 1;
}
console.log(parent) // 1 又把window.parent给覆盖了

因为var会变量提升,所以实际的代码是这个效果

1
2
3
4
5
var parent; //变量提升
{
parent = 1;
}
console.log(parent)

所以在ES6出来之前也就是2015年以前如果你想用局部变量就要这样

1
~function(){}()