1. 对象访问机制 (上)
/*
对象访问机制
+ 当你访问一个对象的成员的时候
+ 如果对象自己本身有, 直接返回结果给你, 停止查询
+ 如果对象自己本身没有, 会自动去 __proto__ 上访问
+ 有就给你返回结果, 停止查询
+ 如果还没有(未完待续)
利用 prototype 和 __proto__ 和 对象访问机制
+ 解决了构造函数的不合理
+ 属性直接写在 构造函数体内
+ 方法书写在 构造函数的 prototype 上
+ 使用构造函数创建一个 有属性 有方法 合理的 对象
+ prototype作用: 就是为了书写一些方法给该构造函数的实例对象使用
=> 因为这个构造函数的每一个实例都可以访问这个构造函数的 prototype
*/
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 使用 Person 创建一个对象
let p1 = new Person('Jack', 18)
console.log(p1)
// 当我访问 p1.name 的时候, 自己就有
console.log(p1.name)
// 当我访问 p1.sayHi 的时候, 自己没有
// 就去 p1.__proto__ 上找, 因为 p1.__proto__ === Person.prototype
// 实际上就是去 Person.prototype 上找
// 找到 sayHi
p1.sayHi()
// 再次创建一个实例化对象
let p2 = new Person('Rose', 20)
// 当我访问 p2.sayHi 的时候
// 因为自己没有, 去到 p2.__proto__ 上找
// 实际上也是去 Person.prototype 上找
p2.sayHi()
2. 构造函数相关的 this 指向
/*
构造函数相关的 this 指向
1. 构造函数体内的 this 指向
=> 因为和 new 关键字连用, this 指向当前实例
2. 构造函数原型上的方法里面的 this 指向
=> 因为方法是依靠实例对象在调用
=> this 指向当前实例
*/
function Person() {
this.name = 'Jack'
console.log('构造函数体内 : ', this)
}
Person.prototype.fn = function () {
console.log('我是 Person.prototype 上的 fn 方法')
console.log('Person.prototype.fn : ', this)
}
// 创建实例
let p1 = new Person()
console.log('====================')
console.log('====================')
console.log('====================')
// 调用方法
// fn 时 Person.prototype 上的方法
// 但是调用是依靠当前实例在调用
// 标准的对象调用方式
// p1.fn() this 点前面是谁就是谁
p1.fn() // this -> 当前实例
3. 对象访问机制
/*
对象访问机制
+ 当你访问一个对象的成员的时候, 会先在自己身上找
+ 自己没有去到 __proto__ 上找
+ 在没有, 再去 __proto__ 上找
+ 一直找到顶级对象的 __proto__ 都没有
+ 就返回 undefined
分析:
+ 定义1: 每一个对象都有 __proto__
+ 随便一个实例化对象的 __proto__ 是所属构造函数的 prototype
+ 定义2: 每一个函数都有一个 prototype, 他是一个对象
+ 每一个构造函数的 prototype 应该也有 __proto__, 指向了谁 ?
+ 构造函数也是函数, 函数也是一个对象
+ 函数应该也有一个 __proto__, 指向了谁 ?
定义:
1. 每一个函数天生自带一个属性叫做 prototype, 是一个对象
2. 每一个对象天生自带一个属性叫做 __proto__ 指向所属构造函数的 prototype
3. 当一个对象, 没有准确的构造函数来实例化的时候, 我们都看作是内置构造函数 Object 的实例
例子:
1. var arr = [] , Array 的实例
2. var obj = {} , Object 的实例
3. var p1 = new Person() , Person 的实例
4. var time = new Date() , Date 的实例
5. var fn = function () {} , Function 的实例
6. Person.prototype , Object 的实例
7. Array.prototype , Objec 的实例
结论:
+ 任何一个对象开始出发
+ **按照 __proto__ 开始向上查找**
+ 最终都能找到 Object.prototype
+ 我们管这个使用 __proto__ 串联起来的对象链状结构, 叫做原型链
+ 原型链作用: 为了对象访问机制服务
*/
function Person() {
this.name = 'Jack'
this.age = 18
this.gender = '男'
}
// 创建一个实例
let p1 = new Person()
4. 原型链
原型链
+ 从任何一个对象出发, 按照 __proto__ 串联起来的对象链状结构
+ 为了对象访问机制而存在
例子: 数组
+ 数组所属的构造函数是 Array
=> 数组.__proto__ === Array.prototype
=> Array.prototype.__proto__ === Object.prototype
=> 数组.__proto__.__proto__ === Object.prototype
如果我想给 数组 扩展一个方法
+ 写在: Array.prototype 上
如果我想给 函数 扩展一个方法
+ 写在: Function.prototype 上
原型
+ 每一个函数天生自带的一个叫做 prototype 的属性
+ 为了存放一些方法, 给这个构造函数的所有实例使用
constructor 属性(构造器)
+ 只有函数天生自带的那个 prototype 上才有
+ 表示我是哪一个构造函数所自带的 原型对象
+ 作用: 判断数据类型
*/
// function Person() {
// }
// console.log(Person.prototype)
// 使用 Perosn 构造函数创建一个对象
// let p1 = new Person()
// console.log(p1.constructor === Person)
// 当我使用 p1 去访问 constructor 属性的时候
// 自己没有, 去 p1.__proto__ 上找
// 就是去 Person.prototype 上找
// Person.prototype.constructor 是谁 ? Person
// p1.constructor 就是 Person
// console.log([].constructor === Array)
// 访问的是 数组的 constructor 属性
// 数组自己没有, 去到 数组.__protot__ 上找
// 就是 Array.prototype
// 找到的就是 Array.prototype.constructor ? Array
5. 判断数据类型
/*
判断数据类型
1. typeof
=> 准确的判断基本数据类型
=> 对于复杂数据类型并不准确
2. constructor
=> 利用原型的属性
=> 利用对象访问机制
3. instanceof
=> 语法: 对象 instanceof 构造函数
4. Object.prototype.toString.call()
=> 语法: Object.prototype.toString.call(你要检测的数据类型)
*/
// console.log(typeof 123)
// console.log(typeof 'hello')
// console.log(typeof true)
// console.log(typeof undefined)
// console.log(typeof null) // object
// console.log(typeof {}) // object
// console.log(typeof []) // object
// console.log(typeof new Date) // object
// console.log(typeof function () {})
// constructor
// var num = 123
// console.log(num.constructor)
// console.log('abc'.constructor)
// console.log(true.constructor)
// console.log([].constructor)
// console.log((function () {}).constructor)
// console.log((new Date()).constructor)
// console.log(null.constructor)
// console.log(undefined.constructor)
// instanceof
// console.log({} instanceof Object)
// console.log((new String()) instanceof String)
// console.log('' instanceof String)
// Object.prototype.toString.call()
// console.log(Object.prototype.toString.call(123))
// console.log(Object.prototype.toString.call(''))
// console.log(Object.prototype.toString.call(true))
// console.log(Object.prototype.toString.call(undefined))
// console.log(Object.prototype.toString.call(null))
// console.log(Object.prototype.toString.call(new Date()))
// console.log(Object.prototype.toString.call(function () {}))
// console.log(Object.prototype.toString.call([]))
// console.log(Object.prototype.toString.call({}))
6. 对象
<!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">
<title>Document</title>
</head>
<body>
<input type="text" id="username">
<input type="text" id="txt">
<script>
/*
对象
+ 我们数据类型的一种
+ 以键值对的形式存储数据
+ 因为 __proto__ 和 原型链 可以访问自己没有的属性
for in 循环
+ 专门遍历对象
+ 遍历对象身上的所有属性
+ 遍历对象身上所有的 可枚举 的属性(包括原型链上的所有 可枚举 属性)
=> 一种是自定义的属性
=> 我们可以设置为可枚举属性
对象自己的方法
1. hasOwnProperty()
=> 查看是不是自己的属性
=> 语法: 对象.hasOwnProperty('你要检测的属性名')
2. defineProperty() 数据劫持
=> 一种给对象添加属性的方法
=> 我可以给一个我设置的属性设置各种各样的行为状态
=> 语法: Object.defineProperty(给哪一个对象添加, key, {
添加的设置
})
*/
// function Person() {
// this.name = 'Jack'
// this.age = 18
// this.gender = '男'
// }
// Person.prototype.sayHi = function () { console.log('hello world') }
// let p1 = new Person()
// console.log(p1)
// for (let key in p1) {
// console.log('我执行了', key)
// }
// console.log(p1.hasOwnProperty('sayHi'))
// 数据劫持
let obj = {
name: 'Jack'
}
// 普通添加
obj.age = 18
var prop = {
firstName: '张',
lastName: '思锐'
}
// 数据劫持
Object.defineProperty(obj, 'username', {
// 对 obj.gender 属性进行一系列的配置
// value: '男', // 这个成员值
// enumerable: false, // 是否可枚举
get () {
return prop.lastName + prop.firstName
},
// 当你像修改劫持的数据的时候
// 就会触发这个 set 函数
// 接收一个形参, 就是你像修改的值
set (val) {
console.log('你想修改')
console.log('你想改成 : ', val)
// 通过在 set 里面修改 prop 对象里面的内容
// 来达到修改 username 属性
let a = val.slice(0, 1)
let b = val.slice(1)
prop.firstName = a
prop.lastName = b
let inp = document.querySelector('#username')
inp.value = obj.username
}
})
obj.name = 'rose'
obj.age = 20
// obj.username = '张三'
var inp = document.querySelector('#txt')
inp.addEventListener('input', () => {
obj.username = inp.value
})
console.log(obj)
</script>
</body>
</html>
7. ES6 的类
/*
ES6 的类
+ 我们在 ES5 以前, 我们就用 函数 来充当 构造函数(类)
+ ES6 引入了一个 类 的概念
=> 就是使用一个新的关键字来定义 构造函数(类)
=> 定义完毕以后, 就是一个类, 不能当作函数来调用
=> 只能通过 new 来得到一个对象
类的语法:
+ 语法: class 类名 {
// 构造器, 我这个类创造的对象里面有哪些属性
constructor () {
this.name = xxx
this.age = xxx
}
// 直接书写原型上的方法
init () {} 原型上的方法 init
}
=> 定义一个类
+ 注意: 一个 class 定义的 类 不能被当作普通函数执行
=> 不和 new 连用就报错了
*/
// function Person() {
// this.name = 'Jack'
// this.age = 18
// }
// let p1 = new Person()
// console.log(p1)
// 本质还是一个函数, 是一个函数就可以直接调用
// 当你将他当托普通函数来执行的时候,
// 没有了创造对象的能力, this 指向改变了
// Person() // this -> window
class Person {
// 构造器, 等价于我们的 构造函数题
constructor (name, age) {
this.name = name
this.age = age
this.init()
}
// 原型上的方法
init () {
console.log(this)
}
setScale () {
}
move () {
}
}
// 使用 Person 类去创建对象
let p1 = new Person('Jack', 18)
console.log(p1)
Person()
8.模块化开发
/*
模块化开发
+ 为什么需要模块化开发
=> 例子: 分页器, Pagination, creEle, setCss
-> creEle 和 setCss 其实在很多地方都可以用
-> 按照开发习惯, 应该把他们分开放
-> a.js, 里面放上 creEle 和 setCss
-> b.js, 里面放上 Pagination
=> 把一类方法放在一个单独的 js 文件里面
-> 为了方便使用的时候, 只引入这一类方法的文件
-> a.js 全都是封装的 操作 DOM 的方法
-> b.js 全都是封装的 格式化 时间对象 的方法
-> c.js 全都是封装的 和数字 相关的方法
=> 我们管这样一个封装好的 js 文件叫做一个 模块
+ 什么是模块化开发
=> 多个 js 文件之间的相互配合来实现效果
=> 一个 js 文件里面只封装一类内容
问题: 由多个 js 文件出现的
1. js 文件引入顺序
2. js 文件之间的相互依赖不清晰
3. js 文件内部的变量会污染全局
*/
9.模块化的发展
/*
模块化的发展
抽离一些功能
1. a.js
=> 里面是操作 时间 的各种方法
2. b.js
=> 里面是操作 DOM 的各种方法
3. c.js
=> 里面进行整合和组装
没有模块化
+ 按照顺序引入文件
IIFE 伪模块化标准
+ Immediaitely Invoked Function Expression
+ 自执行函数 (function () {})()
CommonJS 模块化标准
+ 2009 年, nodejs 出现了
+ 使用 JS 去做服务端语言
+ 伴生的是 CommonJS 模块化标准
+ 缺点: 只能在后端 JS 里面用
AMD 模块化标准 - Async Module Definition
+ 2011 出现的, 社区里面发起的
+ 因为非官方, 没有关键字, 大家书写了一套叫做 require.js 的第三方文件
+ 来实现 模块化标准
+ 把每一个 js 文件独立出来了
-> 使用了导入导出的语法来实现模块化
-> 在 JS 文件里面引入另一个 JS 文件
+ 定义模块
-> 调用 define 的方法
1. 独立模块定义
-> 每一个模块文件开始执行 define()
-> 我不依赖其他文件, 我就是一个单纯的模块
-> 向外暴露的内容, 直接 return 出去就好了
2. 依赖其他模块的模块
-> 我也是一个模块文件, 但是我依赖的以他模块的内容
-> 使用 define() 定义
-> 语法: define([ 依赖文件1, 依赖文件2, ... ], function (模块A, 模块B, ...) {})
3. 导入 其他模块
-> 我是一个模块整合文件
-> 我就直接使用 a.js 文件里面的方法
-> 使用这个方法 require()
-> 语法: require([ 依赖文件1, 依赖文件2, ... ], function (模块1, 模块2) {})
CMD - Common Module Defineion - 通用模块定义
+ 2011 左右, 社区里面出现的一个标准
+ 淘宝 "玉伯", 开发一了个 CMD 的模块化标准
+ 依赖于一个叫做 sea.js 的文件来实现的模块化标准
+ 使用: 文件需要引入一个 sea.js 的文件
1. 独立模块定义
-> define(function (require, exports, module) { })
-> require() 用来导入其他文件的
-> module.exports 是为了本文件导出内容的
-> exports 是 module.exports 的别名
-> var exports = module.exports
2. 依赖其他模块的模块
-> 你需要依赖其他文件模块
-> 在 define( function (require, exports, module) {
在你需要的位置使用 require() 方法来导入
var modA = require('地址')
})
3. 资源整合
-> 使用 seajs.use()
-> 语法: seajs.use(['你要依赖的模块'], function (模块A) {})
ES6 Module
+ 2015 年发布, ES6 语法里面自带了一个模块化标准
+ 各大浏览器厂商并不买账
+ 2016 年开始, Vue 出现了, 人家出现了一个脚手架(开发的大框架直接给你搭建好)
=> 搭建这个架子的时候, 内置了 ES6 模块化标准
+ 2018 年, 各大浏览器厂商开始原生支持 ES6 模块化标准
+ 2018 年中, Chrome 率先原生支持 ES6 模块化
+ 语法: 变成了 JS 的语法, 和关键字, 不需要任何第三方文件的引入
+ 特点: 页面必须在服务器上打开
=> live server 插件
=> 如果你想使用模块化语法, script 标签要加一个属性 type="module"
+ 使用:
1. 每一个文件都可以作为独立模块, 页都可以作为整合文件
2. 导出语法
2-1. export default 导出的内容
2-2. export var num = 200
3. 导入语法
3-1. 接收 export default 导出
+ import 变量 from '哪一个 JS 文件'
3-2. 接收 export 导出的恩日哦那个
+ import { 接收变量 } from '哪一个 JS 文件'
+ 2020 年
=> ES2020 发布新的标准
=> 多了一个 按需加载的模块化
=> 语法: import(你要加载的文件).then(function (res) {})
*/
10.http 传输协议
<!--
http 传输协议
+ 前后端交互的方式
+ 前端以什么样的形式发送数据给后端
+ 后端以什么样的形式返回数据给前端
传输协议
1. 必须经历四个步骤
1-1. 建立连接
1-2. 发送请求(前端给后端)
1-3. 返回响应(后端给前端)
1-4. 断开连接
2. 只能由前端发起
+ 不能由后端主动沟通前端
3. 一次只能说一个事情
+ 对于着一个事情你可以尽可能的描述的详细
+ 但是一次连接只能沟通一个事情
4. 前后端交互只能交互字符串
+ 所有其他数据类型都不可以
+ 中文会转成 url 编码
一个请求的四个步骤
1. 建立连接
+ 基于 TCP / IP 协议的三次握手
+ 浏览器和服务器做的
+ 目的: 为了保证通道的连接
1. 前端和后端说: "你在吗"
2. 后端和前端说: "我再"
-> 后端知道 前端正常发送
-> 后端知道 后端正常接收
3. 前端和后端说: "好的我知道了"
-> 前端知道 后端正常接收
-> 前端知道 前端正常发送
-> 前端知道 前端正常接收
-> 前端知道 后端正常发送
4. 后端和前端说: "你发送请求吧"
-> 后端知道 前端正常接收
-> 后端知道 后端正常发送
2. 发送请求
+ 前端发送请求给后端, 必须以 请求报文 的形式发送
+ 一个特殊格式的字符串文件(由浏览器进行组装)
+ 请求报文
=> 请求行
-> GET / POST: 请求方式(未完待续)
-> ./login.php: 请求地址
-> HTTP/1.1: 传输协议版本
=> 请求头
-> 对本次请求的描述信息
-> Host: 请求主机
-> Accapt: 期望的数据类型
-> UserAgent: 请求终端
-> Content-type: 请求体的数据格式
-> ...
-> Cookie: (未完待续)
=> 请求空行
-> 分隔请求体和请求头的
=> 请求体
-> 前端携带给后端的参数
-> 有的有, 有的没有
3. 接收响应
+ 每一个响应是由服务端接收到前端的请求以后, 给出的结果
+ 必须以响应报文的形式发送个前端
+ 响应报文
1. 状态行
=> 200: 响应状态码(未完待续)
=> ok: 对响应状态码的简单描述
=> HTTP/1.1 传输协议版本
2. 响应头
=> 对本次响应的描述信息
=> Date: 服务器时间(世界标准时间)
=> Server: 服务器信息
=> Content-Type: 响应体的数据格式
=> ...
3. 响应体
=> 后端给前端的数据
4. 断开连接
+ 基于 TCP / IP 协议的四次挥手
+ 为了保证断开连接
1. 前端给后端发一个消息: "响应体收到, 我要准备断开连接了"
2. 后端给前端发一个消息: "好的, 我知道你收到响应体了"
3. 后端在其给前端发一个消息: "我已经准备断开连接了, 当我再次收到你的消息的时候, 我就断了, 不会再次回覆"
4. 前端收到后端的第一个消息
5. 前端收到后端的第二个消息: "好的, 我断开了, 别回了"
留下三个内容
1. 响应状态码
2. 请求方式
3. cookie
-->
11.响应状态码
<!--
响应状态码
+ 以一个数字表示本次请求的响应状态
=> 成功: 创建成功, 删除成功, ...
=> 失败: 客户端失败, 服务端失败, ...
+ 是 100 ~ 599, 分成五类
=> 100 ~ 199: 表示连接继续
=> 200 ~ 299: 表示各种意义上的成功
=> 300 ~ 399: 表示重定向
=> 400 ~ 499: 表示客户端错误
=> 500 ~ 599: 表示服务端错误
常见状态码
+ 101 表示连接继续
+ 200 通用成功
+ 302 临时重定向
=> 本次请求临时使用 服务器 来决定浏览器跳转的页面
+ 301 永久重定向
=> 终身只要访问这个地址, 就会给重新切换到新的地址
+ 304 缓存
=> 当你访问过一遍这个页面以后
=> 浏览器会自动缓存
=> 当你在其访问同一个地址的时候, 不会像服务器发送请求了, 而是从缓存里面获取
+ 403 访问权限不够
+ 404 访问地址不存在
+ 500 通用服务端错误
+ 501 维护或者过载
-->
12.请求方式
<!--
请求方式
+ 前端和后端的交互手段
+ 最早: 不分 GET POST
=> 后来为了语义化, 做了一些区分
=> 本质是一样的
常见的请求方式
HTTP/1.0
1. GET : 偏向于获取的方式
+ 大部分都是给后端一些参数, 用来获取一些列数据
2. POST : 偏向于给服务器一些数据
+ 大部分都是登录, 给服务器一些信息, 你给我一个简单的结果
3. PUT : 偏向于给服务器一些信息, 但是是添加使用
+ 大部分做注册, 给服务器一些信息, 你把这个信息存起来
4. HEAD : 用来获取服务器头信息
HTTP/1.1
5. DELETE : 偏向于删除
+ 大部分是删除评论, 删除微博
6. CONNECT: 管道连接改变代理连接使用
7. PATCH : 偏向于给服务器一些信息, 偏向于修改一些信息
+ 大部分用于完善用户资料
8. OPTIONS: 用于获取服务器性能, 但是需要服务端同意
二阶段: 只用 GET 和 POST
GET 和 POST 请求方式的区别(重点!!!)
+ GET
1. 语义是获取
2. GET 携带参数的方式是 queryString, 在地址栏后面直接拼接, 不再请求体里面
3. GET 理论上携带数据无限, 但是因为浏览器地址栏有限, IE 2KB
4. GET 会被浏览器主动缓存
5. GET 明文发送
6. GET 只能发送 url 编码的数据(ASCII 码), 如果是中文会自动转码
+ POST
1. 语义是给
2. POST 携带载时是 requestBody, 在地址栏没有, 在请求体里面
3. POST 理论上携带的数据无限, 但是会被服务器限制
4. POST 请求不会被浏览器主动缓存, 除非手动设置
5. POST 暗文发送
6. POST 理论上可以发送任意格式的数据, 但是要和请求头里面得 content-type 配套
-->
13.Cookie
<!--
Cookie
+ 浏览器端的本地存储空间
+ 用来存储一些数据
cookie 的特点(记下来!!!)
1. 按照域名存储的
+ 哪一个域名存储的, 在当前域名下就可以访问
+ 换一个域名就不能访问
2. 按照文件路径存储
+ 你在 a 文件夹下存储的数据
+ 只能在 a 文件夹及子文件夹访问
+ 在 a 文件夹的上级目录不能访问
3. cookie 的存储时按照字符串的形式存储
+ 'key=value; key2=value2; key3=value3'
4. 存储大小
+ 4KB 左右
+ 50条左右
5. 时效性
+ 默认时会话级别的时效(关闭浏览器就没有了)
+ 我们可以手动设置 cookie 的时效(关闭浏览器以后依旧保存)
6. 操作权限
+ 前端可以操作
+ 后端可以操作
7. 请求自动携带
+ 只要 cookie 空间里面有数据
+ 那么你在发送任何一个请求的时候, 自动携带
-->
14.session
/*
session
+ 一个存在于 服务器 端的存储空间
+ 当他打开的那一瞬间, 就会生成一个 "密码"
+ 这个密码会自动存储到 cookie 里面
+ 等到返回前端的时候, 会自动把这个带回去
+ 只要你想向 session 空间里面存储内容或者获取内容
=> 必须要先开启, 后使用
开启 session 空间
+ session_start();
存储一些数据
+ PHP 有一个内置的 关联型数组就叫做 $_SESSION
*/
// 1. 开启 session 空间
/*
当我在浏览器打开 localhost/01_session.php 文件的时候
就会执行 session_start() 这段代码
后端就会开启一个存储空间, 同时生成一段 密码
同时把密码的一半放在 cookie 里面
前端访问 localhost/01_session.php 本身就是一个请求
此时后端是会给回一个响应
响应回到前端了, 那么此时看到 cookie 空间里面就应该有一个 session_id 的存在
*/