R.Y


  • 首页

  • 归档

AJAX/同源策略/跨域

发表于 2019-03-18

AJAX (Async JavaScript And XML) 异步JavaScript和XML

Jesse James Garrett 讲如下技术取名叫做 AJAX:异步的 JavaScript 和 XML

  1. 使用 XMLHttpRequest 发请求
  2. 服务器返回 XML 格式的字符串
  3. JS 解析 XML,并更新局部页面
    ⚠️(现在服务器都是返回 JSON 格式)

如何发请求

1.用 form 可以发GET或POST请求,但是会刷新页面或新开页面

发 get 请求

发 get 请求

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<form action="/xxx" method="get">
<input type="password" name="password">
<input type="submit">
</form>
</body>
</html>

输入111 点击提交
输入111

可以在控制台中看到 Request Headers(点view source)
点view source

发 post 请求

发 post 请求

1
<form action="/xxx" method="post">

输入123 点击提交
同样在控制台查看,可以看到 post 后面没有查询参数(点view source)
点view source

可以在 第四部分 Form Data 看到
点view source

2.用 a 可以发 get 请求,但是也会刷新页面或新开页面

用 a 发 get 请求
1
2
3
4
5
<a href="/xxx">
<script>
x.click()
</script>
</a>

点view source

3.用 img 可以发 get 请求,但是只能以图片的形式展示

用 img 可以发 get 请求
1
2
var img = document.createElement('img')
img.src = '/xxx'

点view source

4.用 link 可以发 get 请求,但是只能以 CSS、favicon 的形式展示

用 link 可以发 get 请求

点view source

5.用 script 可以发 get 请求,但是只能以脚本的形式运行

用 script 可以发 get 请求

点view source


有没有什么方式可以实现 get、post、put、delete 请求都行
想以什么形式展示就以什么形式展示

有!用 AJAX 发请求

XMLHttpRequest

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

原生js发AJAX请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let request = new XMLHttpRequest() // 1.产生这个对象
request.open('GET', '/xxx') //2.初始化 request
request.send() // 3.发送这个对象
request.onreadystatechange = ()=>{ // 4.监听这个对象 readystate的变化
if(request.readyState === 4){ //请求相应完毕
if(request.status >= 200 && request.status <= 3 00){
console.log('请求成功')
let string = request.responseText // 后端返回的信息(字符串类型)
let object = window.JSON.parse(string) // 将 JSON 字符串 转换成 JS 对应的类型(字符串,数组,对象。。。)
}else if(request.status >= 400){
console.log('请求失败')
}
}
}

CORS 跨源资源共享 (Cross-Origin Resource Sharing)

同源策略

如果你的 js 不是 baidu.com 里面的,那你就不能向 baidu.com 发送任何 AJAX 请求

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

突破同源策略 === CORS跨域

如果浏览器没有 同源策略 那么网络就没隐私可言,
如果一个网页前端 非要访问另一个网页的后端,就可以让另一个网页的后端设置这一段代码

使用方法如下:

Access-Control-Allow-Origin是HTML5中定义的一种解决资源跨域的策略。
点view source

总结:

用 CORS 还是 JSONP?
JSONP 和 CORS 都可以发请求,不同在于,
JSONP 只能发 GET 请求,
CORS 可以选择发 POST / GET 请求,(要用 POST ,工作中打电话给 后端 要一个 CORS 头)

AJAX 很安全,因为有 同源策略

注册登录与cookie

发表于 2018-12-14

准备工作

首先建立一个 server.js 和 一个 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
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

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('得到 HTTP 路径\n' + path)

if(path === '/'){
var string = fs.readFileSync('./index.html','utf8')
response.statusCode = 200
response.setHeader('Content-Type','text/html; charset=utf-8')
response.write(string)
response.end()
}else if(path === '/main.js'){
var string = fs.readFileSync('./main.js','utf8')
response.statusCode = 200
response.setHeader('Content-Type','text/javascript; charset=utf-8')
response.write(string)
response.end()
}else if(path === '/jQuery-ajax.js'){
var string = fs.readFileSync('./jQuery-ajax.js','utf8')
response.statusCode = 200
response.setHeader('Content-Type','text/javascript; charset=utf-8')
response.write(string)
response.end()
}else if(path === '/promise.js'){
var string = fs.readFileSync('./promise.js','utf8')
response.statusCode = 200
response.setHeader('Content-Type','text/javascript; charset=utf-8')
response.write(string)
response.end()
}else{
response.statusCode = 404
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(`
{
"error": "not found"
}
`)
response.end()
}

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

server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度然后用电饭煲打开 http://localhost:' + port)

我们要加入一个 注册 页面的路由,创建 sign_up.html 文件

sign_up.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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
<style type="text/css">
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
border: 1px solid red;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.form-wrapper{
min-width: 350px;
padding: 20px;
border: 1px solid #ddd;
}
.form-wrapper .row{
margin: 10px 0;
}
.form-wrapper .row>label{
display: inline-block;
min-width: 4em;

}
</style>
</head>
<body>
<div class="form-wrapper">
<h1>注册</h1>
<form id="signUpForm">
<div class="row">
<label>邮箱</label>
<input type="text" name="email">
<span class="error"></span>
</div>
<div class="row">
<label>密码</label>
<input type="password" name="password">
<span class="error"></span>
</div>
<div class="row">
<label>确认密码</label>
<input type="password" name="password_confirmation">
<span class="error"></span>
</div>
<div class="row">
<input type="submit" value="注册">
</div>
</form>
</div>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
let $form = $('#signUpForm')
$form.on('submit', (e) => {
e.preventDefault() //阻止自己提交
let hash = {}
let need = ['email','password','password_confirmation']
need.forEach((name) => {
let value = $form.find(`[name=${name}]`).val() // 分别获得,我们在邮箱,密码,确认密码中输入的值
hash[name] = value
})
$form.find('.error').each((index, span) => {
$(span).text('')
})
if(hash['email'] === ''){
$form.find('[name="email"]').siblings('.error').text('请输入邮箱').css({"color":"red"})
return
}
if(hash['password'] === ''){
$form.find('[name="password"]').siblings('.error').text('请输入密码')
return
}
if(hash['password_confirmation'] === ''){
$form.find('[name="password_confirmation"]').siblings('.error').text('请确认密码')
return
}
if(hash['password'] !== hash['password_confirmation']){
$form.find('[name="password_confirmation"]').siblings('.error').text('密码不一致').css({"color":"red"})
return
}

$.post('/sign_up', hash) //发送注册请求之后
.then((response) => {
console.log(response)
}, (request) => {
// console.log(request.statusText, request.status)
let {errors} = request.responseJSON //{errors} 邮箱有误的对象的信息
if(errors.email && errors.email === 'invalid'){
$form.find('[name=email]').siblings('.error').text('邮箱格式有误').css({"color":"red"})
}
})
})
</script>
</body>
</html>

将以下代码添加进 server.js 文件中

1
2
3
4
5
6
7
else if(path === '/sign_up' && method === 'GET'){ //当打开 sign_up,并且 method === 'GET' 时
var string = fs.readFileSync('sign_up.html','utf8')
response.statusCode = 200
response.setHeader('Content-Type','text/html; charset=utf-8')
response.write(string)
response.end()
}

现在我们可以通过 node server 8080 启动

node server 8080

然后在浏览器地址栏 打开 http://localhost:8080/sign_up

http://localhost:8080/sign_up

将以下代码添加进 server.js 文件中(封装一个函数,用来读取第四部分 Form Data 里面的内容)

1
2
3
4
5
6
7
8
9
10
11
12
//node http get post data
function readBody(request){
let body = [] //
return new Promise((resolve, reject) => {
request.on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
resolve(body)
})
})
}

将以下代码添加进 server.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
else if(path === '/sign_up' && method === 'POST'){//当打开 sign_up,并且 method === '' 时
readBody(request).then((body) => { //读取输入的数据(email=1%40&password=1&password_confirmation=1)
let strings = body.split('&') //用 split 分离(分别输入1@,1,1 得到[ 'email=1%40', 'password=1', 'password_confirmation=1' ])
let hash = {}
strings.forEach((string) => {
let parts = string.split('=') //[ 'email', '1%40' ][ 'password', '1' ][ 'password_confirmation', '1' ]
let key = parts[0] //email password password_confirmation
let value = parts[1] //1%40 1 1
hash[key] = decodeURIComponent(value) //decodeURIComponent 用于将@ 转换(hash['email']='1@')
})

let {email, password, password_confirmation} = hash
if(email.indexOf('@') === -1){ //如果 email 里面没有@
response.statusCode = 400 //那么就 400
response.setHeader('Content-Type','application/json; charset=utf-8') //此处标明为 JSON语法
// 以下代码是后端的
response.write(`{
"errors":{
"email": "invalid"
}
}`)
}else if(password !== password_confirmation){ //如果 密码 与 确认密码不一样
response.statusCode = 400
response.write('password is not same')
}else{
var users = fs.readFileSync('./db/users', 'utf8') //users
// 如果 有问题 就 清空
try{
users = JSON.parse(users) //[]
}catch(exception){
users = []
}

// 用户是否已存在
let inUse = false //先默认没有注册
for(let i=0;i<users.length;i++){
let user = users[i]
if(user.email === email){
inUse = true //如果被占用
break; //阻止注册
}
}
if(inUse){ //如果用户被占用
response.statusCode = 400 //提示错误400
response.write('email in use') //并提示 'email in use'
}else{ //如果没有被占用
users.push({email: email, password: password})
var usersString = JSON.stringify(users)
fs.writeFileSync('./db/users', usersString)
response.statusCode = 200
}
}
response.end()
})
}

注:前后端的代码都应该验证,后端一定要验,前端不一定。黑客可以跨过前端JS通过 curl 发请求,所以一定要确保后端代码。
curl
粘贴

再新建一个 db 文件夹,加入 users 文件,将 users 作为我们的数据库

users

等以上设置好了,注册用户,回到 user 文件,则可以看到 注册的用户信息。

现在我们要做一个 登录 页面,创建 sign_in.html 文件

sign_in.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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<style type="text/css">
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
border: 1px solid red;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.form-wrapper{
min-width: 350px;
padding: 20px;
border: 1px solid #ddd;
}
.form-wrapper .row{
margin: 10px 0;
}
.form-wrapper .row>label{
display: inline-block;
min-width: 4em;

}
</style>
</head>
<body>
<div class="form-wrapper">
<h1>登录</h1>
<form id="signInForm">
<div class="row">
<label>邮箱</label>
<input type="text" name="email">
<span class="error"></span>
</div>
<div class="row">
<label>密码</label>
<input type="password" name="password">
<span class="error"></span>
</div>
<div class="row">
<input type="submit" value="登录">
</div>
</form>
</div>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
let $form = $('#signInForm')
$form.on('submit', (e) => {
e.preventDefault()
let hash = {}
let need = ['email','password','password_confirmation']
need.forEach((name) => {
let value = $form.find(`[name=${name}]`).val()
hash[name] = value
})
$form.find('.error').each((index, span) => {
$(span).text('')
})
if(hash['email'] === ''){
$form.find('[name="email"]').siblings('.error').text('请输入邮箱').css({"color":"red"})
return
}
if(hash['password'] === ''){
$form.find('[name="password"]').siblings('.error').text('请输入密码')
return
}
$.post('/sign_in', hash)
.then((response) => {
window.location.href = '/'
}, (request) => {
alert('失败')
})
})
</script>
</body>
</html>

将以下代码添加进 server.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
else if(path === '/sign_in' && method === 'GET'){
var string = fs.readFileSync('sign_in.html','utf8')
response.statusCode = 200
response.setHeader('Content-Type','text/html; charset=utf-8')
response.write(string)
response.end()
}else if(path === '/sign_in' && method === 'POST'){
readBody(request).then((body) => {
let strings = body.split('&')
let hash = {}
strings.forEach((string) => {
let parts = string.split('=')
let key = parts[0]
let value = parts[1]
hash[key] = decodeURIComponent(value) //decodeURIComponent 用于将@ 转换
})
let {email, password} = hash
var users = fs.readFileSync('./db/users', 'utf8')
try{
users = JSON.parse(users) //[]
}catch(exception){
users = []
}
let found
for(let i=0;i<users.length;i++){
if(users[i].email === email && users[i].password === password){
found = true
break;
}
}
if(found){
response.setHeader('Set-Cookie', `sign_in_email=${email}; HttpOnly`) // HttpOnly,禁止用户擅自修改cookie
response.statusCode = 200
}else{
response.statusCode = 401
}
response.end()
})
}

Set-Cookie 的用法
Set-Cookie

现在登录一个已经注册过的用户,在点击 登录 之前,钩上 Preserve log
Preserve log
可以看到sign_in, Response Headers 里面的 Set-Cookie 和 localhost, Request Headers 里面的 Cookie 内容一致。

sign_in
localhost

只要set-cookie, 以后给的请求,里面都会带上这个 cookie。

Cookie 特点

  1. 服务器通过 Set-Cookie 响应头设置Cookie
  2. 浏览器得到 Cookie 之后,每次请求都要带上 Cookie
  3. 服务器读取 Cookie 就知道登录用户的信息(email)

完整代码(https://github.com/ryyoyo/cookie)

原生js发AJAX请求

发表于 2018-09-25

XMLHttpRequest是Ajax的核心,通过调用XMLHttpRequest对象的属性和方法可以实现在客户端和浏览器之间进行数据的异步传输,从而实现页面的无刷新效果。

XMLHttpRequest对象的使用需要四个步骤:

(1) 初始化XMLHttpRequest对象

(2) 指定响应处理函数

(3) 发送HTTP请求

(4) 处理服务器返回的信息(服务器===后端,返回字符串)

XMLHttpRequest对象的常用属性:

onreadystatechange:指定当readyState属性值改变时的事件处理句柄(只写);

readyState:返回当前请求的状态(只读);

responseText:将相应信息作为字符串返回(只读);

XMLHttpRequest对象的常用方法:

open():创建一个新的HTTP请求,并制定此请求的方法,URL以及验证信息(用户名/密码);

send():发送请求到HTTP服务器并接受回应。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
myBtn.addEventListener('click',(e)=>{
//(1) 初始化XMLHttpRequest对象(产生对象)
let request = new XMLHttpRequest()
//(2) 指定响应处理函数(配置对象)
request.open('GET','http://king.com:8002/xxx')
//(3) 发送HTTP请求(发送对象)
request.send()
//(4) 处理服务器返回的信息(监听对象,readyState 的变化)
request.onreadystatechange = ()=>{
if(request.readyState === 4){
if(request.status >= 200 && request.status < 300){
//把符合 JSON 语法的字符串
let string = request.responseText
//转换成 JS 对应的值
let object = window.JSON.parse(string)
}else if(request.status >= 400){
console.log('请求失败')
}
}
}
})

readyState属性用来表示请求的状态,有5个可取值,分别是:

“0”:表示未初始化,即对象已经建立,但是尚未初始化(尚未调用open()方法);

“1”:表示正在加载,此时对象已建立,尚未调用send()方法;

“2”:表示请求已发送,即send()方法已调用;

“3”:表示请求处理中;

“4”:表示请求已完成,即数据接收完毕。

JS 操作请求和响应

  1. JS 可以设置任意 请求 header 吗?
    第一部分 request.open('GET','./xxx')
    第二部分 request.setRequestHeader('Content-type','x-www-form-urlencoded')
    第四部分 request.send('a=1&b=2')
  2. JS 可以设置任意 响应 header 吗?
    第一部分 request.status/request.statusText
    第二部分 request.getResponseHeader()/request.getAllResponseHeaders()
    第四部分 request.responseText

自己实现 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
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

window.jQuery = function (nodeOrSelector) {
let nodes = {}
nodes.addClass = function () {
}
return nodes
}

window.$ = window.jQuery


window.jQuery.ajax = function ({method,url,headers,body,successFn,failFn}) {
/*let method = options.method
let url
if (arguments === 1) {
url = options.url
} else if (arguments === 2) {
url = arguments[0]
options = arguments[1]
}
let headers = options.headers
let body = options.body
let successFn = options.successFn
let failFn = options.failFn*/

// ES6 解构赋值
// let {method,headers,body.successFn,failFn} = options

let request = new XMLHttpRequest()
request.open(method, url) // 设置第一部分
for (key in headers) {
let value = headers[key]
request.setRequestHeader(key, value) // 设置第二部分
}
// 要在 request.open()和 request.send() 之间设置
// request.setRequestHeader('Content-type','x-www-form-urlencoded')

request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status <= 300) {
successFn.call(undefined, request.responseText)
} else if (request >= 400) {
failFn.call(undefined, request)
}
}
}
request.send(body) // 设置第四部分
}

window.jQuery.ajax({
method: 'post',
url: './xxx',
headers: {
'content-type': 'application/x-www-form-urlencoded',
'ry': '18'
},
body: 'a=1&b=2',
successFn: function () {
}, //回调函数 callback === funtion
failFn: function () {
},
})
第二版,增加 Promise 规则
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
window.jQuery = function (nodeOr) {
let nodes = {}
nodes.addClass = function () {}
return nodes
}
window.$ = window.jQuery

window.Promise = function (fn) {
return {
then: function () {
}
}
}

window.jQuery.ajax = function ({method, url, headers, body}) {
return new Promise(function (resolve, reject) {
let request = new XMLHttpRequest()
request.open(method, url) // 设置第一部分
for (key in headers) {
let value = headers[key]
request.setRequestHeader(key, value) // 设置第二部分
}
// 要在 request.open()和 request.send() 之间设置
// request.setRequestHeader('Content-type','x-www-form-urlencoded')

request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status >= 200 && request.status <= 300) {
resolve.call(undefined, request.responseText)
} else if (request >= 400) {
reject.call(undefined, request)
}
}
}
request.send(body) // 设置第四部分
})

}

window.jQuery.ajax({
method: 'post',
url: './xxx',
headers: {
'content-type': 'application/x-www-form-urlencoded',
'ry': '18'
}
}).then(
(text) => {
console.log(text)
},
(request) => {
console.log(request)
}
)

jQuery.ajax

1
2
3
4
5
6
7
8
9
10
$.ajax({
url: './xxx',
type: 'post',
}).then(
() => {},
() => {}
).then(
()=>{},
()=>{}
)

JSONP请求

发表于 2018-09-19

form img(只能发get请求) 都可以发请求

什么是JSONP

JSONP(JSON with Padding) 是 json 的一种使用模式,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

请求方:ry.com 这个网站的前端程序员(浏览器)
响应方:king.com 这个网站的后端程序员(服务器)

  1. 请求方 创建 script,src 指向 响应方,同时传一个查询参数 ?callbackName=xxx
  2. 响应方 根据 查询参数 callbackName,构造形如
    1. xxx.call(undefined,’你要的数据’)
    2. xxx(‘你要的数据’)
      这样的响应
  3. 浏览器接收到响应,就会执行 xxx.call(undefined,’你要的数据’)
  4. 那么 请求方就知道了他要的数据

这就是 JSONP

行业约定

  1. callbackName -> callback
  2. xxx -> 随机数 ry13242345325325

引用代码如下

1
2
3
4
5
6
7
8
9
$.ajax({
url: 'http://king.com:8002/pay',
dataType: 'jsonp',
success: function (response){
if(response === 'heihei'){
amount.innerText = amount.innerText - 1
}
}
});

以下是一个 【付款】 网站

首先,将 index.js,index.html,db 文件里面的内容复制

index.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
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('./index.html','utf8')
var amount = fs.readFileSync('./db','utf8')
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; charset=utf-8')
response.write(string)
response.end()
}else if(path === '/main.js'){
var string = fs.readFileSync('./main.js','utf8')
response.setHeader('Content-Type','application/javascript;')
response.write(string)
response.end()
}else if(path === '/pay'){
var amount = fs.readFileSync('./db','utf8')
var newAmount = amount - 1
fs.writeFileSync('./db',newAmount)
response.setHeader('Content-Type','application/javascript;')
response.statusCode = 200
response.write(`
${query.callback}.call(undefined,'heihei')
`)
response.end()
}else{
response.statusCode = 404
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write('找不到对应的路径,你需要自行修改 index.js')
response.end()
}

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

server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度然后用电饭煲打开 http://localhost:' + port)

JSONP 精髓

1
${query.callback}.call(undefined,'heihei')

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
24
25
26
<!DOCTYPE html>
<html>
<head>
<title>付款</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js
"></script>
</head>
<body>
<h5>余额<span id="amount">&&&amount&&&</span></h5>
<button id="button">付款</button>
<script type="text/javascript">
$(button).on('click',function(){
$.ajax({
url : "http://king.com:8002/pay",
dataType : "jsonp",//数据类型为jsonp
success : function(response){
if(response === 'heihei'){
amount.innerText = amount.innerText - 1
}
}
});
})
</script>
</body>
</html>

db (此表示代码中的金额)

1
1000

接着,找到电脑中的 hosts 文件 (shift+command+G),输入 /etc/hosts 前往

shift+command+G
将 hosts 文件复制出来
找到hosts文件,复制出来
在 副本 hosts 文件,添加这两行。

1
2
127.0.0.1 ry.com
127.0.0.1 king.com

在副本最后添加
保存并替换以前 hosts 文件(放到以前 hosts 文件的位置)

我们在后台,分别运行以下代码(打开两个)

1
node index.js 8001 //程序参数
1
PORT=8001 node index.js //环境变量

node index.js 8001

1
node index.js 8002
1
PORT=8002 node index.js

node index.js 8002

在浏览器地址栏,输入 king.com:8002 即可

king.com:8002

为什么 JSONP 不支持 POST 请求?

  1. 因为 JSONP 是通过动态创建 script 实现的。
  2. 但是,动态创建 script 只能用 GET,不能用 POST 。

小总结:

1.form 得要个 submit 按钮,表单提交会刷新页面。  
2.那我们就用 img ,但是 img 只能知道成功和失败,不能知道更多的数据。  
3.那我们就用 script ,script 要让后端知道我们要执行的代码,那我们就要加 callback 参数,
后台就根据 callback 参数,构造出一个函数调用,然后把数据放到 第一个参数 里面,call 一下就好了。

script + callback参数 = JSONP

DOM (冒泡、捕获、点击隐藏浮层)

发表于 2018-08-20

Document Object Model(文档对象模型)

DOM的主要功能:

页面中的节点(Node),通过构造函数(Document,Element,Text,Comment…等构造函数),构造出对应的对象。
Node也是一个构造函数, 派生于 Object,所以 Node 构造出来的函数,也有 Object 的公有属性。

Node 构造了 Document DocumentType Elment Text Comment DocumentFragment… 等类型。

Document 构造了<html>标签。
Element 构造了<head> <body> <meta> <title> <script> <h1>…标签。
Text构造了页面中 文本。
Comment构造了页面中 注释。


Node 接口

Node 属性:查阅MDN

childNode,fristChild,innerText,lastChild,nextSibling,nodeName,nodeType,nodeValue,outerValue,ownDocument,parentElement,parentNode,previousSibling,textContent

  • 1.innerText 和 textContent 区别
  • 2.nodeType 1 和 3 分别代表什么

Node 方法:查阅MDN

  • appendChild()添加子元素
  • cloneNode()克隆节点(分true深拷贝、false浅拷贝)
  • contains()是否包含另一个元素
  • hasChildNodes()是否有子元素,返回一个布尔值
  • insertBefore()把…插到…前面
  • isEqualNode()是否是相等的节点
  • isSameNode()是否是相同的节点(同一个 ‘===’)
  • removeChild()移除子元素(还是存在于内存中)
  • replaceChild()替换子元素()
  • normalize()使…正常

Document 接口

查看document属性 console.dir(document)
Document MDN

Document 属性 查询MDN

body,characterSet,childElementCount,children,doctype,documentElement,domain,fullscreen,head,hidden,images,
links,location,onxxxxxx,origin,plugins,readState,referrer,scripts,scrollingElement,styleSheets,title,visibilityState

Document 方法 查询MDN

  • close()
  • createAttribute
  • createElement

DOM事件 看一下

事件监听
btn.onclick.call(this,arguments)  //this = btn,arguments = 参数

DOM Level 0

onclick onerror onload onmouseenter

html 写法 要加括号

1
onclick = fn()   //fn 类型为字符串

js 写法 不能加括号,调用是时候才加

1
X.onclick = fn    //fn 类型为函数对象

DOM Level 1


DOM Level 2 (最广泛)

(2000/11/13)

  • addEventListener removeEventListener
  • 事件冒泡,事件捕获

addEventListener 可以添加多个 click 方法(队列,先进先出)

1
2
3
4
5
6
7
8
9
// 先打印1,再打印2
xx.addEventListener('click',function(){
console.log(1)
})
xx.addEventListener('click',function(){
console.log(2)
})
// 1
// 2

onclick,属性,唯一(工作中,可能别人也用了onclick,可能会把别人的覆盖)

1
2
3
4
5
6
7
8
// 只打印2,后面`onclick`的把前面的覆盖了
xx.onclick = function(){
console.log(1)
}
xx.onclick = function(){
console.log(2)
}
// 2

DOM Events(先事件捕获,绑定同一对象按代码先后顺序,最后事件冒泡)

注:jQuery 不支持设置 true或false

事件冒泡(从里到外)

false 或者 不填

1
2
3
4
5
6
7
8
9
10
11
12
13
// 点击 里
外.addEventListener('click',function() {
console.log("外")
})
中.addEventListener('click',function() {
console.log("中")
})
里.addEventListener('click',function() {
console.log("里")
})
// 里
// 中
// 外

事件捕获(从外到里)

多加了一个参数 true

1
2
3
4
5
6
7
8
9
10
11
12
13
// 点击 里 
外.addEventListener('click',function() {
console.log("外")
},true)
中.addEventListener('click',function() {
console.log("中")
},true)
里.addEventListener('click',function() {
console.log("里")
},true)
// 外
// 中
// 里

如果同一个事件,绑定了2次,那么就按 先后顺序执行,true,false 失效

先事件捕获,再绑定同一对象按代码先后顺序,最后事件冒泡


点击别处关闭浮层

  • stopPropagation() 阻止传播
  • one('click',function..) 只监听一次

JS(document 随时被监听,如果 popover 有很多,则不节省内存)

查看完整代码

1
2
3
4
5
6
7
8
9
10
11
12
btn.addEventListener('click', function () {
console.log('点击btn')
popover.style.display = 'block'
})
wrapper.addEventListener('click', function (e) {
console.log('点击wrapper')
e.stopPropagation() // 阻止外部传播进来,所以在 document 被点击的时候,wrapper 里面不受影响
})
document.addEventListener('click', function () {
console.log('点击document')
popover.style.display = 'none'
})

JQ(仅仅需要在 popover show() 出来后监听一次,节省内存)

stopPropagation()和 setTimeout() 这两种方法实现
查看stopPropagation()方法完整代码
查看setTimeout()方法完整代码

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
//方法一 stopPropagation
// 点击 btn,先后打印 '点击btn' '点击wrapper',同时 popover 也会出现,
// 这时候就开始监听一次 document 的点击事件,点击别处,popover 会隐藏。
{
$(btn).on('click',function () {
console.log('点击btn')
$(popover).show()
$(document).one('click',function () { //只执行一次
console.log('点击document')
$(popover).hide()
})
})
$(wrapper).on('click',function (e) {
console.log('点击wrapper')
e.stopPropagation()
})
}

// 方法二 用 setTimeout,就不需要 stopPropagation 了;
// 点击 btn,打印 '点击btn',同时 popover 也会出现,
// 这时候就开始监听一次 document 的点击事件,点击别处,popover 会隐藏。
{
$(btn).on('click',function () {
console.log('点击btn')
$(popover).show()
setTimeout(function() {
$(document).one('click',function () {
console.log('点击document')
$(popover).hide()
})
},0)
})
}

HTML常用标签

发表于 2018-08-18

常见标签

1.iframe 标签

HTML内联框架元素 <iframe> 表示嵌套的浏览上下文,有效地将另一个HTML页面嵌入到当前页面中。
iframe 需要和 a 元素一起用,iframe 的 name,和 a 里面的 target 是同一名字。
iframe 自带一个 border ,如果不需要的话,需要设置 frameborder = “0” 。
iframe

2.a 标签 (跳转页面,HTTP GET 请求)

a
a
a
blank
self
parent
top

download 属性

可以在 a 标签中加上 download 属性,或者把 content-type 设置为

1
content-type: application/octet-stream

download
download

href 属性

如果 <a href="qq.com">qq</a> 这样写,将不会跳转到 qq.com,因为这是一个相对地址,打开的其实是一个文件
qq.com
qq.com

1.a 标签的无协议绝对地址

//qq.com
//qq.com

http-server 使用

一般我们不用 file 协议,
我们可以上传到 github 预览
或者下载一个工具,输入命令

1
npm i -g http-server

然后

1
http-server -c-1

http-server
http://127.0.0.1:8080

2.#ddd 锚点不发请求(<a href="#"></a>,会跳到页面顶部,<a href=""></a>,会刷新页面)

#ddd.html
#ddd.html

3.?ddd 其他的都会发起请求

?ddd.html
?ddd.html

4.javascript:; 伪协议(<a href="javascript:;"></a>,)

javascript:; 点击却不进行操作

javascript: alert(1);
javascript: alert(1);点击弹窗

3.form 标签 (跳转页面,HTTP POST 请求)

form 标签里面如果没有 <input type="submit"> 则无法提交,除非 <button></button>不设置 type ,那么 button 按钮会自动升级为 submit 。

POST

现在我们来提交一些东西(如果是中文的话,都会被转义为 utf-8 ,http 是明文上传,https 则会被加密)

出现第四部分

总结

GET 会默认把请求放进 查询参数 ,POST 会默认把请求放入 第四部分(form data),我们可以通过 ?sss,让 POST 也有 查询参数  ,但是,我们没办法让 GET 请求有 第四部分。

4.input 标签

input 详情

button

<button></button>不设置 type ,则 button 会被自动升级为 submit 。

checkbox 多选

加上 label 标签,那么我们可以点击 “哈哈” 就能选中。(input 的 id ,需要跟 label 的 for 名字相对应)

1
2
喜欢的水果
<label for="xxx">苹果</label><input type="checkbox" value="apple" id="xxx" name="fruit">

还可以更简单,直接用 label 标签包裹,如下(可以不写 id 和 for,但是要写 name)

1
2
<label>橘子<input type="checkbox" value="orange" name="fruit"></label>
<label>香蕉<input type="checkbox" value="banana" name="fruit"></label>

同样适用于 type=”text”

1
<label for="yyy">哈哈</label><input type="text" id="yyy" name="y">

checkbox
checkbox

radio 单选

1
2
<label>喜欢<input type="checkbox" value="like" name="loveme"></label>
<label>不喜欢<input type="checkbox" value="dislike" name="loveme"></label>

radio

5.select 下拉栏

select 里面 multiple 表示多选,可以按住 ctrl 进行多选,option 里面 disabled 表示不可选,selected 表示默认选择。

1
2
3
4
5
6
<select name="group" multiple>
<option value="1">足球</option>
<option value="2">篮球</option>
<option value="3" disabled>乒乓球</option>
<option value="4" selected></select>>羽毛球</option>
</select>

select

6.textarea

resize:none 表示不能拉伸区域了,除了 width ,height 可以设置大小,还可以用 col row 设置。

1
<textarea style="resize: none; width: 200px; height: 200px"></textarea>

7.table

可以用 colgroup 里面的 col width 设置每列的 宽度

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
<table border="1">
<colgroup>
<col width="100">
<col width="200">
<col width="100">
<col width="70">
</colgroup>
<thead>
<tr>
<th>科目</th><th>姓名</th><th>班级</th><th>分数</th>
</tr>
</thead>
<tbody>
<tr>
<th>语文</th><td>小明</td><td>1</td><td>90</td>
</tr>
<tr>
<th>语文</th><td>小红</td><td>2</td><td>100</td>
</tr>
<tr>
<th>总分</th><td></td><td></td><td>190</td>
</tr>
<tr>
<th>平均分</th><td></td><td></td><td>95</td>
</tr>
</tbody>
<tfoot>
</tfoot>
</table>

table

实现一个jQuery的API

发表于 2018-08-05

可以把它看做是一个对象,然后用里面的方法

接受一个旧的 节点(字符串或节点/选择器),返回一个新的 拥有很多方法的对象

所以jQuery`实质上是一个构造函数,接受一个参数,这个参数可能是节点,然后返回一个方法对象去操作这个节点。

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
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<style type="text/css">
.red{
color: red;
}
.blue{
color: blue;
}
</style>

<body>
<ul>
<li id="item1">1</li>
<li id="item2">2</li>
<li id="item3">3</li>
<li id="item4">4</li>
<li id="item5">5</li>
</ul>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script type="text/javascript" src="test.js"></script>
</body>
</html>

JS内容

实现添加 class,还有 设置 或者 获取text 内容的功能

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 = function(nodeOrSelector){ //先用一个函数将以下内容包裹,防止污染
let nodes = {} //声明一个数组
if(typeof nodeOrSelector === 'string'){ //如果 nodeOrSelector 是一个 `string`
let temp = document.querySelectorAll(nodeOrSelector) //伪数组
for(var i=0;i<temp.length;i++){ //遍历这个伪数组
nodes[i] = temp[i]
}
nodes.length = temp.length //使得该伪数组按顺序排序
}else if(nodeOrSelector instanceof Node){ //如果 nodeOrSelector 是一个 `Node`
nodes = {0:nodeOrSelector,length:1} //我们要让该 Node 和上面的格式保持一致
}
//实现添加 class
nodes.addClass = function(classes){
classes.forEach((value) => {
for(var i=0;i<nodes.length;i++){
nodes[i].classList.add(value)
}
})
}
//设置 或者 获取text 内容
nodes.text = function(text){
if(text === undefined){ //如果 text 没有给参数,那就直接输出 text 原有的值
var texts = []
for(var i=0;i<nodes.length;i++){
texts.push(nodes[i].textContent)
}
return texts
}else{
for(var i=0;i<nodes.length;i++){
nodes[i].textContent = text //否则就输出给的值
}
}
}
return nodes
}

var node3 = jQuery('ul > li') //获取li
node3.addClass(['blue']) //给 li 添加 class->blue
node3.text() //输出li的text
  • .addClass(className)
  • removeClass(className)
  • toggleClass(className) 开关

js零碎笔记

发表于 2018-07-24

setTimeout()

setTimeout() 是一个定时器,设置时间后,定时器到期就执行一个函数或者指定的一段代码。

html代码

1
<button id="btn">点击</button>

js代码

1
2
3
4
var btn = document.getElementById('btn')
btn.onclick = function(){
setTimeout("alert('3 seconds!')",3000)
}
效果如下

setTimeout(),点击3秒后弹窗

js的原型和原型链

发表于 2018-07-19

原型和原型链

原型是Javascript中的继承的基础,JavaScript的继承就是基于原型的继承。

我用一些简单的公式图来理解下

Number()
__proto__
obj()
obj()

Objtct.prototype ,里面的 prototype 就是 Object 的 共有属性(也就是原型)。

Number, String, Boolean

n 的 toString 和 valueOf 属性在 n.__proto__.__proto__里面(如图2中的红圈和蓝圈)。在第一层的 __proto__ 中,toFixed,toLocaleString,toString是 Number 的私有属性; 而公共属性toString 和 valueOf,在 Number 的 第二层 __proto__ 中;所以想要获取公共属性toString 和 valueOf,则n.__proto__.__proto__.toString(如图1);除了 Number,还有String,Boolean 也有二层 __proto__ 中。

Object

而 obj 的 toString 和 valueOf 属性在 obj.__proto__ 里面(如图3中的红圈),也可以直接用 obj.toString 表示 obj.__proto__.toString 。

原型链是实现继承的主要方法。其基本思想是:利用原型让一个引用类型继承另一个应用类型的属性和方法。

如下图
总结

js里的数据类型转换

发表于 2018-07-16

类型转换

任意类型 转 字符串

1、String(x)

String(x)

2、x.toString()

x.toString()

3、x+''

x+''

任意类型 转 数字

1、Number(x)
2、parseInt(x, 10)MDN
3、parseFloat(x)MDN
4、x - 0
5、+x

任意类型转布尔

1、Boolean(x)
2、!!x

内存图

1
2
3
4
var a = 1
var b = a
b = 2
请问 a 显示是几?

a=1

1
2
3
4
var a = {name: 'a'}
var b = a
b = {name: 'b'}
请问现在 a.name 是多少?

a.name = 'a'

1
2
3
4
var a = {name: 'a'}
var b = a
b.name = 'b'
请问现在 a.name 是多少?

a.name = 'b'

1
2
3
4
var a = {name: 'a'}
var b = a
b = null
请问现在 a 是什么?

a = {name: 'a'}

深复制 VS 浅复制

1
2
3
4
5
var a = 1
var b = a
b = 2 //这个时候改变 b
//a 完全不受 b 的影响
//那么我们就说这是一个深复制

对于简单类型的数据来说,赋值就是深拷贝。
对于复杂类型的数据(对象)来说,才要区分浅拷贝和深拷贝。

这是一个浅拷贝的例子

1
2
3
4
var a = {name: 'frank'}
var b = a
b.name = 'b'
a.name === 'b' // true

因为我们对 b 操作后,a 也变了

什么是深拷贝了,就是对 Heap 内存进行完全的拷贝。

1
2
3
4
var a = {name: 'frank'}
var b = deepClone(a) // deepClone 还不知道怎么实现
b.name = 'b'
a.name === 'a' // true
123
R.Y

R.Y

25 日志
2 标签
© 2019 R.Y
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4