JS-108~161
this 关键字
- 每一个函数内部都有一个关键字是
this
- 可以让我们直接使用的
- 重点: 函数内部的 this 只和函数的调用方式有关系,和函数的定义方式没有关系
- 函数内部的 this 指向谁,取决于函数的调用方式
全局定义的函数直接调用,
this => window
function fn() {console.log(this) } fn() // 此时 this 指向 window
对象内部的方法调用,
this => 调用者
var obj = {fn: function () {console.log(this)} } obj.fn() // 此时 this 指向 obj
定时器的处理函数,
this => window
setTimeout(function () {console.log(this) }, 0) // 此时定时器处理函数里面的 this 指向 window
事件处理函数,
this => 事件源
div.onclick = function () {console.log(this) } // 当你点击 div 的时候,this 指向 div
自调用函数,
this => window
(function () {console.log(this) })() // 此时 this 指向 window
call 和 apply 和 bind
- 刚才我们说过的都是函数的基本调用方式里面的 this 指向
- 我们还有三个可以忽略函数本身的 this 指向转而指向别的地方
- 这三个方法就是 call / apply / bind
- 是强行改变 this 指向的方法
1、call
call
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向- 语法:
函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)
var obj = { name: 'Jack' }
function fn(a, b) {console.log(this)console.log(a)console.log(b)
}
fn(1, 2)
fn.call(obj, 1, 2)
fn()
的时候,函数内部的 this 指向 windowfn.call(obj, 1, 2)
的时候,函数内部的 this 就指向了 obj 这个对象- 使用 call 方法的时候
- 会立即执行函数
- 第一个参数是你要改变的函数内部的 this 指向
- 第二个参数开始,依次是向函数传递参数
2、apply
apply
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向- 语法:
函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])
var obj = { name: 'Jack' }
function fn(a, b) {console.log(this)console.log(a)console.log(b)
}
fn(1, 2)
fn.call(obj, [1, 2])
fn()
的时候,函数内部的 this 指向 windowfn.apply(obj, [1, 2])
的时候,函数内部的 this 就指向了 obj 这个对象- 使用 apply 方法的时候
- 会立即执行函数
- 第一个参数是你要改变的函数内部的 this 指向
- 第二个参数是一个 数组,数组里面的每一项依次是向函数传递的参数
3、bind
bind
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向- 和 call / apply 有一些不一样,就是不会立即执行函数,而是返回一个已经改变了 this 指向的函数
- 语法:
var newFn = 函数名.bind(要改变的 this 指向); newFn(传递参数)
var obj = { name: 'Jack' }
function fn(a, b) {console.log(this)console.log(a)console.log(b)
}
fn(1, 2)
var newFn = fn.bind(obj)
newFn(1, 2)
- bind 调用的时候,不会执行 fn 这个函数,而是返回一个新的函数
- 这个新的函数就是一个改变了 this 指向以后的 fn 函数
fn(1, 2)
的时候 this 指向 windownewFn(1, 2)
的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了 obj
ES6定义变量
- ES5 和 ES6 其实就是在 js 语法的发展过程中的一个版本而已
- ECMAScript 就是 js 的语法
1、以前的版本没有某些功能
2、在 ES5 这个版本的时候增加了一些功能
3、在 ES6 这个版本的时候增加了一些功能
4、因为浏览器是浏览器厂商生产的
ECMAScript 发布了新的功能以后,浏览器厂商需要让自己的浏览器支持这些功能
1、这个过程是需要时间的
2、所以到现在,基本上大部分浏览器都可以比较完善的支持了
3、只不过有些浏览器还是不能全部支持
4、这就出现了兼容性问题
5、所以我们写代码的时候就要考虑哪些方法是 ES5 或者 ES6 的,看看是不是浏览器都支持
let 和 const 关键字
- 以前都是使用
var
关键字来声明变量的- 在 ES6 的时候,多了两个关键字
let
和const
,也是用来声明变量的- 只不过和 var 有一些区别
let
和const
不允许重复声明变量// 使用 var 的时候重复声明变量是没问题的,只不过就是后面会把前面覆盖掉 var num = 100 var num = 200
// 使用 let 重复声明变量的时候就会报错了 let num = 100 let num = 200 // 这里就会报错了
// 使用 const 重复声明变量的时候就会报错 const num = 100 const num = 200 // 这里就会报错了
let
和const
声明的变量不会在预解析的时候解析(也就是没有变量提升)// 因为预解析(变量提升)的原因,在前面是有这个变量的,只不过没有赋值 console.log(num) // undefined var num = 100
// 因为 let 不会进行预解析(变量提升),所以直接报错了 console.log(num) // undefined let num = 100
// 因为 const 不会进行预解析(变量提升),所以直接报错了 console.log(num) // undefined const num = 100
let
和const
声明的变量会被所有代码块限制作用范围// var 声明的变量只有函数能限制其作用域,其他的不能限制 if (true) {var num = 100 } console.log(num) // 100
// let 声明的变量,除了函数可以限制,所有的代码块都可以限制其作用域(if/while/for/...) if (true) {let num = 100console.log(num) // 100 } console.log(num) // 报错
// const 声明的变量,除了函数可以限制,所有的代码块都可以限制其作用域(if/while/for/...) if (true) {const num = 100console.log(num) // 100 } console.log(num) // 报错
let
和const
的区别let
声明的变量的值可以改变,const
声明的变量的值不可以改变let num = 100 num = 200 console.log(num) // 200
const num = 100 num = 200 // 这里就会报错了,因为 const 声明的变量值不可以改变(我们也叫做常量)
let
声明的时候可以不赋值,const
声明的时候必须赋值let num num = 100 console.log(num) // 100
const num // 这里就会报错了,因为 const 声明的时候必须赋值
ES6箭头函数
- 箭头函数是 ES6 里面一个简写函数的语法方式
- 重点: 箭头函数只能简写函数表达式,不能简写声明式函数
function fn() {} // 不能简写
const fun = function () {} // 可以简写
const obj = {fn: function () {} // 可以简写
}
- 语法:
(函数的行参) => { 函数体内要执行的代码 }
const fn = function (a, b) {console.log(a)console.log(b)
}
// 可以使用箭头函数写成
const fun = (a, b) => {console.log(a)console.log(b)
}
const obj = {fn: function (a, b) {console.log(a)console.log(b)}
}
// 可以使用箭头函数写成
const obj2 = {fn: (a, b) => {console.log(a)console.log(b)}
}
箭头函数的特殊性
箭头函数内部没有 this,箭头函数的 this 是上下文的 this
// 在箭头函数定义的位置往上数,这一行是可以打印出 this 的 // 因为这里的 this 是 window // 所以箭头函数内部的 this 就是 window const obj = {fn: function () {console.log(this)},// 这个位置是箭头函数的上一行,但是不能打印出 thisfun: () => {// 箭头函数内部的 this 是书写箭头函数的上一行一个可以打印出 this 的位置console.log(this)} }obj.fn() obj.fun()
- 按照我们之前的 this 指向来判断,两个都应该指向 obj
- 但是 fun 因为是箭头函数,所以 this 不指向 obj,而是指向 fun 的外层,就是 window
箭头函数内部没有
arguments
这个参数集合const obj = {fn: function () {console.log(arguments)},fun: () => {console.log(arguments)} } obj.fn(1, 2, 3) // 会打印一个伪数组 [1, 2, 3] obj.fun(1, 2, 3) // 会直接报错
函数的行参只有一个的时候可以不写
()
其余情况必须写const obj = {fn: () => {console.log('没有参数,必须写小括号')},fn2: a => {console.log('一个行参,可以不写小括号')},fn3: (a, b) => {console.log('两个或两个以上参数,必须写小括号')} }
函数体只有一行代码的时候,可以不写
{}
,并且会自动 returnconst obj = {fn: a => {return a + 10},fun: a => a + 10 }console.log(fn(10)) // 20 console.log(fun(10)) // 20
函数传递参数的时候的默认值
我们在定义函数的时候,有的时候需要一个默认值出现
就是当我不传递参数的时候,使用默认值,传递参数了就使用传递的参数
function fn(a) {a = a || 10console.log(a) } fn() // 不传递参数的时候,函数内部的 a 就是 10 fn(20) // 传递了参数 20 的时候,函数内部的 a 就是 20
- 在 ES6 中我们可以直接把默认值写在函数的行参位置
function fn(a = 10) {console.log(a) } fn() // 不传递参数的时候,函数内部的 a 就是 10 fn(20) // 传递了参数 20 的时候,函数内部的 a 就是 20
- 这个默认值的方式箭头函数也可以使用
const fn = (a = 10) => {console.log(a) } fn() // 不传递参数的时候,函数内部的 a 就是 10 fn(20) // 传递了参数 20 的时候,函数内部的 a 就是 20
- 注意: 箭头函数如果你需要使用默认值的话,那么一个参数的时候也需要写 ()
ES6解构赋值
- 解构赋值,就是快速的从对象或者数组中取出成员的一个语法方式
解构对象
快速的从对象中获取成员
// ES5 的方法向得到对象中的成员 const obj = {name: 'Jack',age: 18,gender: '男' }let name = obj.name let age = obj.age let gender = obj.gender
// 解构赋值的方式从对象中获取成员 const obj = {name: 'Jack',age: 18,gender: '男' }// 前面的 {} 表示我要从 obj 这个对象中获取成员了 // name age gender 都得是 obj 中有的成员 // obj 必须是一个对象 let { name, age, gender } = obj
解构数组
快速的从数组中获取成员
// ES5 的方式从数组中获取成员 const arr = ['Jack', 'Rose', 'Tom'] let a = arr[0] let b = arr[1] let c = arr[2]
// 使用解构赋值的方式从数组中获取成员 const arr = ['Jack', 'Rose', 'Tom']// 前面的 [] 表示要从 arr 这个数组中获取成员了 // a b c 分别对应这数组中的索引 0 1 2 // arr 必须是一个数组 let [a, b, c] = arr
注意:
{}
是专门解构对象使用的[]
是专门解构数组使用的- 不能混用
模版字符串
ES5 中我们表示字符串的时候使用
''
或者""
在 ES6 中,我们还有一个东西可以表示字符串,就是 ``(反引号)
let str = `hello world` console.log(typeof str) // string
和单引号好友双引号的区别
反引号可以换行书写
// 这个单引号或者双引号不能换行,换行就会报错了 let str = 'hello world' // 下面这个就报错了 let str2 = 'hello world'
let str = `helloworld `console.log(str) // 是可以使用的
反引号可以直接在字符串里面拼接变量
// ES5 需要字符串拼接变量的时候 let num = 100 let str = 'hello' + num + 'world' + num console.log(str) // hello100world100// 直接写在字符串里面不好使 let str2 = 'hellonumworldnum' console.log(str2) // hellonumworldnum
// 模版字符串拼接变量 let num = 100 let str = `hello${num}world${num}` console.log(str) // hello100world100
- 在 `` 里面的
${}
就是用来书写变量的位置
- 在 `` 里面的
ES6展开运算符
ES6 里面号新添加了一个运算符
...
,叫做展开运算符作用是把数组展开
let arr = [1, 2, 3, 4, 5] console.log(...arr) // 1 2 3 4 5
合并数组的时候可以使用
let arr = [1, 2, 3, 4] let arr2 = [...arr, 5] console.log(arr2)
也可以合并对象使用
let obj = {name: 'Jack',age: 18 } let obj2 = {...obj,gender: '男' } console.log(obj2)
在函数传递参数的时候也可以使用
let arr = [1, 2, 3] function fn(a, b, c) {console.log(a)console.log(b)console.log(c) } fn(...arr) // 等价于 fn(1, 2, 3)
面向对象
- 首先,我们要明确,面向对象不是语法,是一个思想,是一种 编程模式
- 面向: 面(脸),向(朝着)
- 面向过程: 脸朝着过程 =》 关注着过程的编程模式
- 面向对象: 脸朝着对象 =》 关注着对象的编程模式
- 实现一个效果
- 在面向过程的时候,我们要关注每一个元素,每一个元素之间的关系,顺序,。。。
- 在面向过程的时候,我们要关注的就是找到一个对象来帮我做这个事情,我等待结果
- 我们以前的编程思想是,每一个功能,都按照需求一步一步的逐步完成
创建对象的方式
- 因为面向对象就是一个找到对象的过程
- 所以我们先要了解如何创建一个对象
调用系统内置的构造函数创建对象
js 给我们内置了一个 Object 构造函数
这个构造函数就是用来创造对象的
当 构造函数 和 new 关键字连用的时候,就可以为我们创造出一个对象
因为 js 是一个动态的语言,那么我们就可以动态的向对象中添加成员了
// 就能得到一个空对象 var o1 = new Object() // 正常操作对象 o1.name = 'Jack' o1.age = 18 o1.gender = '男'
字面量的方式创建一个对象
直接使用字面量的形式,也就是直接写
{}
可以在写的时候就添加好成员,也可以动态的添加
// 字面量方式创建对象 var o1 = {name: 'Jack',age: 18,gender: '男' }// 再来一个 var o2 = {} o2.name = 'Rose' o2.age = 20 o2.gender = '女'
使用工厂函数的方式创建对象
先书写一个工厂函数
这个工厂函数里面可以创造出一个对象,并且给对象添加一些属性,还能把对象返回
使用这个工厂函数创造对象
// 1. 先创建一个工厂函数 function createObj() {// 手动创建一个对象var obj = new Object()// 手动的向对象中添加成员obj.name = 'Jack'obj.age = 18obj.gender = '男'// 手动返回一个对象return obj }// 2. 使用这个工厂函数创建对象 var o1 = createObj() var o2 = createObj()
使用自定义构造函数创建对象
工厂函数需要经历三个步骤
- 手动创建对象
- 手动添加成员
- 手动返回对象
构造函数会比工厂函数简单一下
- 自动创建对象
- 手动添加成员
- 自动返回对象
先书写一个构造函数
在构造函数内向对象添加一些成员
使用这个构造函数创造一个对象(和 new 连用)
构造函数可以创建对象,并且创建一个带有属性和方法的对象
面向对象就是要想办法找到一个有属性和方法的对象
面向对象就是我们自己制造 构造函数 的过程
// 1. 先创造一个构造函数 function Person(name, gender) {this.age = 18this.name = namethis.gender = gender }// 2. 使用构造函数创建对象 var p1 = new Person('Jack', 'man') var p2 = new Person('Rose', 'woman')
构造函数详解
- 我们了解了对象的创建方式
- 我们的面向对象就是要么能直接得到一个对象
- 要么就弄出一个能创造对象的东西,我们自己创造对象
- 我们的构造函数就能创造对象,所以接下来我们就详细聊聊 构造函数
构造函数的基本使用
和普通函数一样,只不过 调用的时候要和 new 连用,不然就是一个普通函数调用
function Person() {} var o1 = new Person() // 能得到一个空对象 var o2 = Person() // 什么也得不到,这个就是普通函数调用
- 注意: 不写 new 的时候就是普通函数调用,没有创造对象的能力
首字母大写
function person() {} var o1 = new person() // 能得到一个对象function Person() {} var o2 = new Person() // 能得到一个对象
- 注意: 首字母不大写,只要和 new 连用,就有创造对象的能力
当调用的时候如果不需要传递参数可以不写
()
,建议都写上function Person() {} var o1 = new Person() // 能得到一个空对象 var o2 = new Person // 能得到一个空对象
- 注意: 如果不需要传递参数,那么可以不写 (),如果传递参数就必须写
构造函数内部的 this,由于和 new 连用的关系,是指向当前实例对象的
function Person() {console.log(this) } var o1 = new Person() // 本次调用的时候,this => o1 var o2 = new Person() // 本次调用的时候,this => o2
- 注意: 每次 new 的时候,函数内部的 this 都是指向当前这次的实例化对象
因为构造函数会自动返回一个对象,所以构造函数内部不要写 return
- 你如果 return 一个基本数据类型,那么写了没有意义
- 如果你 return 一个引用数据类型,那么构造函数本身的意义就没有了
使用构造函数创建一个对象
我们在使用构造函数的时候,可以通过一些代码和内容来向当前的对象中添加一些内容
function Person() {this.name = 'Jack'this.age = 18 }var o1 = new Person() var o2 = new Person()
- 我们得到的两个对象里面都有自己的成员 name 和 age
我们在写构造函数的时候,是不是也可以添加一些方法进去呢?
function Person() {this.name = 'Jack'this.age = 18this.sayHi = function () {console.log('hello constructor')} }var o1 = new Person() var o2 = new Person()
- 显然是可以的,我们的到的两个对象中都有
sayHi
这个函数 - 也都可以正常调用
- 显然是可以的,我们的到的两个对象中都有
但是这样好不好呢?缺点在哪里?
function Person() {this.name = 'Jack'this.age = 18this.sayHi = function () {console.log('hello constructor')} }// 第一次 new 的时候, Person 这个函数要执行一遍 // 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi var o1 = new Person()// 第二次 new 的时候, Person 这个函数要执行一遍 // 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi var o2 = new Person()
- 这样的话,那么我们两个对象内的
sayHi
函数就是一个代码一摸一样,功能一摸一样 - 但是是两个空间函数,占用两个内存空间
- 也就是说
o1.sayHi
是一个地址,o2.sayHi
是一个地址 - 所以我们执行
console.log(o1 === o2.sayHi)
的到的结果是false
- 缺点: 一摸一样的函数出现了两次,占用了两个空间地址
- 这样的话,那么我们两个对象内的
怎么解决这个问题呢?
- 就需要用到一个东西,叫做 原型
面向对象的原型
- 原型的出现,就是为了解决 构造函数的缺点
- 也就是给我们提供了一个给对象添加函数的方法
- 不然构造函数只能给对象添加属性,不能合理的添加函数就太 LOW 了
prototype
每一个函数天生自带一个成员,叫做 prototype,是一个对象空间
即然每一个函数都有,构造函数也是函数,构造函数也有这个对象空间
这个
prototype
对象空间可以由函数名来访问function Person() {}console.log(Person.prototype) // 是一个对象
- 即然是个对象,那么我们就可以向里面放入一些东西
function Person() {}Person.prototype.name = 'prototype' Person.prototype.sayHi = function () {}
我们发现了一个叫做
prototype
的空间是和函数有关联的并且可以向里面存储一些东西
重点: 在函数的 prototype 里面存储的内容,不是给函数使用的,是给函数的每一个实例化对象使用的
那实例化对象怎么使用能?
__proto__
每一个对象都天生自带一个成员,叫做
__proto__
,是一个对象空间即然每一个对象都有,实例化对象也是对象,那么每一个实例化对象也有这个成员
这个
__proto__
对象空间是给每一个对象使用的当你访问一个对象中的成员的时候
- 如果这个对象自己本身有这个成员,那么就会直接给你结果
- 如果没有,就会去
__proto__
这个对象空间里面找,里面有的话就给你结果 - 未完待续。。。
那么这个
__proto__
又指向哪里呢?- 这个对象是由哪个构造函数 new 出来的
- 那么这个对象的
__proto__
就指向这个构造函数的prototype
function Person() {}var p1 = new Person()console.log(p1.__proto__ === Person.prototype) // true
- 我们发现实例化对象的
__proto__
和所属的构造函数的prototype
是一个对象空间 - 我们可以通过构造函数名称来向
prototype
中添加成员 - 对象在访问的时候自己没有,可以自动去自己的
__proto__
中查找 - 那么,我们之前构造函数的缺点就可以解决了
- 我们可以把函数放在构造函数的
prototype
中 - 实例化对象访问的时候,自己没有,就会自动去
__proto__
中找 - 那么也可以使用了
- 我们可以把函数放在构造函数的
function Person() {}Person.prototype.sayHi = function () {console.log('hello Person') }var p1 = new Person() p1.sayHi()
p1
自己没有sayHi
方法,就会去自己的__proto__
中查找p1.__proto__
就是Person.prototype
- 我们又向
Person.prototype
中添加了sayHi
方法 - 所以
p1.sayHi
就可以执行了
到这里,当我们实例化多个对象的时候,每个对象里面都没有方法
- 都是去所属的构造函数的
protottype
中查找 - 那么每一个对象使用的函数,其实都是同一个函数
- 那么就解决了我们构造函数的缺点
function Person() {}Person.prototype.sayHi = function () {console.log('hello') }var p1 = new Person() var p2 = new Person()console.log(p1.sayHi === p2.sayHi)
p1
是Person
的一个实例p2
是Person
的一个实例- 也就是说
p1.__proto__
和p2.__proto__
指向的都是Person.prototype
- 当
p1
去调用sayHi
方法的时候是去Person.prototype
中找 - 当
p2
去调用sayHi
方法的时候是去Person.prototype
中找 - 那么两个实例化对象就是找到的一个方法,也是执行的一个方法
- 都是去所属的构造函数的
结论
- 当我们写构造函数的时候
- 属性我们直接写在构造函数体内
- 方法我们写在原型上
原型链
- 我们刚才聊过构造函数了,也聊了原型
- 那么问题出现了,我们说构造函数的
prototype
是一个对象 - 又说了每一个对象都天生自带一个
__proto__
属性 - 那么 构造函数的 prototype 里面的
__proto__
属性又指向哪里呢?
一个对象所属的构造函数
每一个对象都有一个自己所属的构造函数
比如: 数组
// 数组本身也是一个对象 var arr = [] var arr2 = new Array()
- 以上两种方式都是创造一个数组
- 我们就说数组所属的构造函数就是
Array
比如: 函数
// 函数本身也是一个对象 var fn = function () {} var fun = new Function()
- 以上两种方式都是创造一个函数
- 我们就说函数所属的构造函数就是
Function
constructor
- 对象的
__proto__
里面也有一个成员叫做constructor
- 这个属性就是指向当前这个对象所属的构造函数
链状结构
- 当一个对象我们不知道准确的是谁构造的时候,我们呢就把它看成
Object
的实例化对象 - 也就是说,我们的 构造函数 的 prototype 的
__proto__
指向的是Object.prototype
- 那么
Object.prototype
也是个对象,那么它的__proto__
又指向谁呢? - 因为
Object
的 js 中的顶级构造函数,我们有一句话叫 万物皆对象 - 所以
Object.prototype
就到顶了,Object.prototype
的__proto__
就是 null
原型链的访问原则
- 我们之前说过,访问一个对象的成员的时候,自己没有就会去
__proto__
中找 - 接下来就是,如果
__proto__
里面没有就再去__proto__
里面找 - 一直找到
Object.prototype
里面都没有,那么就会返回undefiend
对象的赋值
- 到这里,我们就会觉得,如果是赋值的话,那么也会按照原型链的规则来
- 但是: 并不是!并不是!并不是! 重要的事情说三遍
- 赋值的时候,就是直接给对象自己本身赋值
- 如果原先有就是修改
- 原先没有就是添加
- 不会和
__proto__
有关系
ES6继承
构造函数继承
function Student(name,age,classroom){Person.call(this,name,age)this.classroom = classroom
}
原型继承
Student.prototype = new Person()
组合继承
构造函数继承+原型继承
function Person(name,age){this.name = namethis.age = age
}Person.prototype.say = function(){console.log("hello")
}function Student(name,age,classroom){Person.call(this,name,age)this.classroom = classroom
}Student.prototype = new Person()var obj = new Student("kerwin",100,"1班")
AJAX
ajax
全名async javascript and XML
- 是前后台交互的能力
- 也就是我们客户端给服务端发送消息的工具,以及接受响应的工具
- 是一个 默认异步 执行机制的功能
AJAX 的优势
- 不需要插件的支持,原生 js 就可以使用
- 用户体验好(不需要刷新页面就可以更新数据)
- 减轻服务端和带宽的负担
- 缺点: 搜索引擎的支持度不够,因为数据都不在页面上,搜索引擎搜索不到
AJAX 的使用
- 在 js 中有内置的构造函数来创建 ajax 对象
- 创建 ajax 对象以后,我们就使用 ajax 对象的方法去发送请求和接受响应
创建一个 ajax 对象
// IE9及以上
const xhr = new XMLHttpRequest()// IE9以下
const xhr = new ActiveXObject('Mricosoft.XMLHTTP')
- 上面就是有了一个 ajax 对象
- 我们就可以使用这个
xhr
对象来发送 ajax 请求了
配置链接信息
const xhr = new XMLHttpRequest()// xhr 对象中的 open 方法是来配置请求信息的
// 第一个参数是本次请求的请求方式 get / post / put / ...
// 第二个参数是本次请求的 url
// 第三个参数是本次请求是否异步,默认 true 表示异步,false 表示同步
// xhr.open('请求方式', '请求地址', 是否异步)
xhr.open('get', './data.php')
- 上面的代码执行完毕以后,本次请求的基本配置信息就写完了
发送请求
const xhr = new XMLHttpRequest()
xhr.open('get', './data.php')// 使用 xhr 对象中的 send 方法来发送请求
xhr.send()
- 上面代码是把配置好信息的 ajax 对象发送到服务端
一个基本的 ajax 请求
- 一个最基本的 ajax 请求就是上面三步
- 但是光有上面的三个步骤,我们确实能把请求发送的到服务端
- 如果服务端正常的话,响应也能回到客户端
- 但是我们拿不到响应
- 如果想拿到响应,我们有两个前提条件
- 本次 HTTP 请求是成功的,也就是我们之前说的 http 状态码为 200 ~ 299
- ajax 对象也有自己的状态码,用来表示本次 ajax 请求中各个阶段
ajax 状态码
- ajax 状态码 -
xhr.readyState
- 是用来表示一个 ajax 请求的全部过程中的某一个状态
readyState === 0
: 表示未初始化完成,也就是open
方法还没有执行readyState === 1
: 表示配置信息已经完成,也就是执行完open
之后readyState === 2
: 表示send
方法已经执行完成readyState === 3
: 表示正在解析响应内容readyState === 4
: 表示响应内容已经解析完毕,可以在客户端使用了
- 这个时候我们就会发现,当一个 ajax 请求的全部过程中,只有当
readyState === 4
的时候,我们才可以正常使用服务端给我们的数据 - 所以,配合 http 状态码为 200 ~ 299
- 一个 ajax 对象中有一个成员叫做
xhr.status
- 这个成员就是记录本次请求的 http 状态码的
- 一个 ajax 对象中有一个成员叫做
- 两个条件都满足的时候,才是本次请求正常完成
readyStateChange
在 ajax 对象中有一个事件,叫做
readyStateChange
事件这个事件是专门用来监听 ajax 对象的
readyState
值改变的的行为也就是说只要
readyState
的值发生变化了,那么就会触发该事件所以我们就在这个事件中来监听 ajax 的
readyState
是不是到 4 了const xhr = new XMLHttpRequest() xhr.open('get', './data.php')xhr.send()xhr.onreadyStateChange = function () {// 每次 readyState 改变的时候都会触发该事件// 我们就在这里判断 readyState 的值是不是到 4// 并且 http 的状态码是不是 200 ~ 299if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {// 这里表示验证通过// 我们就可以获取服务端给我们响应的内容了} }
responseText
ajax 对象中的
responseText
成员就是用来记录服务端给我们的响应体内容的
所以我们就用这个成员来获取响应体内容就可以
const xhr = new XMLHttpRequest() xhr.open('get', './data.php')xhr.send()xhr.onreadyStateChange = function () {if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {// 我们在这里直接打印 xhr.responseText 来查看服务端给我们返回的内容console.log(xhr.responseText)} }
使用 ajax 发送请求时携带参数
- 我们使用 ajax 发送请求也是可以携带参数的
- 参数就是和后台交互的时候给他的一些信息
- 但是携带参数 get 和 post 两个方式还是有区别的
发送一个带有参数的 get 请求
get 请求的参数就直接在 url 后面进行拼接就可以
const xhr = new XMLHttpRequest() // 直接在地址后面加一个 ?,然后以 key=value 的形式传递 // 两个数据之间以 & 分割 xhr.open('get', './data.php?a=100&b=200')xhr.send()
- 这样服务端就能接受到两个参数
- 一个是 a,值是 100
- 一个是 b,值是 200
发送一个带有参数的 post 请求
post 请求的参数是携带在请求体中的,所以不需要再 url 后面拼接
const xhr = new XMLHttpRequest() xhr.open('get', './data.php')// 如果是用 ajax 对象发送 post 请求,必须要先设置一下请求头中的 content-type // 告诉一下服务端我给你的是一个什么样子的数据格式 xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')// 请求体直接再 send 的时候写在 () 里面就行 // 不需要问号,直接就是 'key=value&key=value' 的形式 xhr.send('a=100&b=200')
application/x-www-form-urlencoded
表示的数据格式就是key=value&key=value
ajax封装
/** @作者: kerwin* @公众号: 大前端私房菜*/
function queryStringify(obj) {let str = ''for (let k in obj) str += `${k}=${obj[k]}&`return str.slice(0, -1)
}// 封装 ajax
function ajax(options) {let defaultoptions = {url: "",method: "GET",async: true,data: {},headers: {},success: function () { },error: function () { }}let { url, method, async, data, headers, success, error } = {...defaultoptions,...options}if (typeof data === 'object' && headers["content-type"]?.indexOf("json") > -1) {data = JSON.stringify(data)}else {data = queryStringify(data)}// 如果是 get 请求, 并且有参数, 那么直接组装一下 url 信息if (/^get$/i.test(method) && data) url += '?' + data// 4. 发送请求const xhr = new XMLHttpRequest()xhr.open(method, url, async)xhr.onload = function () {if (!/^2\d{2}$/.test(xhr.status)) {error(`错误状态码:${xhr.status}`)return }// 执行解析try {let result = JSON.parse(xhr.responseText)success(result)} catch (err) {error('解析失败 ! 因为后端返回的结果不是 json 格式字符串')}}// 设置请求头内的信息for (let k in headers) xhr.setRequestHeader(k, headers[k])if (/^get$/i.test(method)) {xhr.send()} else {xhr.send(data)}
}
ajax({url:"http://localhost:3000/users",method:"GET",async:true,data:{username:"kerwin",password:"123"}, headers:{},success:function(res){console.log(res)},error:function(err){console.log(err)}
})
Promise
promise
是一个 ES6 的语法- 承诺的意思,是一个专门用来解决异步 回调地狱 的问题
回调地狱
当一个回调函数嵌套一个回调函数的时候
就会出现一个嵌套结构
当嵌套的多了就会出现回调地狱的情况
比如我们发送三个 ajax 请求
- 第一个正常发送
- 第二个请求需要第一个请求的结果中的某一个值作为参数
- 第三个请求需要第二个请求的结果中的某一个值作为参数
ajax({url: '我是第一个请求',success (res) {// 现在发送第二个请求ajax({url: '我是第二个请求',data: { a: res.a, b: res.b },success (res2) {// 进行第三个请求ajax({url: '我是第三个请求',data: { a: res2.a, b: res2.b },success (res3) { console.log(res3) }})}})} })
回调地狱,其实就是回调函数嵌套过多导致的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x4va9QRF-1674289720227)(%E7%AC%94%E8%AE%B0.assets/%E5%9B%9E%E8%B0%83%E5%9C%B0%E7%8B%B1.jpeg)]
- 当代码成为这个结构以后,已经没有维护的可能了
- 所以我们要把代码写的更加的艺术一些
PROMISE基础语法
为了解决回调地狱
我们就要使用 promise 语法
语法:
new Promise(function (resolve, reject) {// resolve 表示成功的回调// reject 表示失败的回调 }).then(function (res) {// 成功的函数 }).catch(function (err) {// 失败的函数 })
promise 就是一个语法
- 我们的每一个异步事件,在执行的时候
- 都会有三个状态,执行中 / 成功 / 失败
因为它包含了成功的回调函数
所以我们就可以使用 promise 来解决多个 ajax 发送的问题
new Promise(function (resolve, reject) {ajax({url: '第一个请求',success (res) {resolve(res)}}) }).then(function (res) {// 准备发送第二个请求return new Promise(function (resolve, reject) {ajax({url: '第二个请求',data: { a: res.a, b: res.b },success (res) {resolve(res)}})}) }).then(function (res) {ajax({url: '第三个请求',data: { a: res.a, b: res.b },success (res) {console.log(res)}}) })
ASYNC/AWAIT
async/await
是一个 es7 的语法这个语法是 回调地狱的终极解决方案
语法:
async function fn() {const res = await promise对象 }
这个是一个特殊的函数方式
可以 await 一个 promise 对象
可以把异步代码写的看起来像同步代码
只要是一个 promiser 对象,那么我们就可以使用
async/await
来书写async function fn() {const res = new Promise(function (resolve, reject) {ajax({url: '第一个地址',success (res) {resolve(res)}})})// res 就可以得到请求的结果const res2 = new Promise(function (resolve, reject) {ajax({url: '第二个地址',data: { a: res.a, b: res.b },success (res) {resolve(res)}})})const res3 = new Promise(function (resolve, reject) {ajax({url: '第三个地址',data: { a: res2.a, b: res2.b },success (res) {resolve(res)}})})// res3 就是我们要的结果console.log(res3) }
- 这样的异步代码写的就看起来像一个同步代码了
fetch
XMLHttpRequest 是一个设计粗糙的 API,配置和调用方式非常混乱, 而且基于事件的异步模型写起来不友好。
兼容性不好 polyfill: https://github.com/camsong/fetch-ie8
fetch("http://localhost:3000/users").then(res=>res.json()).then(res=>{console.log(res)})fetch("http://localhost:3000/users",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({username:"kerwin",password:"123"})}).then(res=>res.json()).then(res=>{console.log(res)})fetch("http://localhost:3000/users/5",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({username:"kerwin",password:"456"})}).then(res=>res.json()).then(res=>{console.log(res)})fetch("http://localhost:3000/users/5",{method:"DELETE"}).then(res=>res.json()).then(res=>{console.log(res)})
//错误处理
fetch("http://localhost:3000/users1").then(res=>{if(res.ok){return res.json()}else{return Promise.reject({status:res.status,statusText:res.statusText})}}).then(res=>{console.log(res)}).catch(err=>{console.log(err)})
cookie
cookie的特点
- 只能存储文本
- 单条存储有大小限制4KB左右
数量限制(一般浏览器,限制大概在50条左右)- 读取有域名限制:不可跨域读取,只能由来自 写入cookie的 同一域名 的网页可进行读取。简单的讲就是,哪个服务器发给你的cookie,只有哪个服务器有权利读取
- 时效限制:每个cookie都有时效,默认的有效期是,会话级别:就是当浏览器关闭,那么cookie立即销毁,但是我们也可以在存储的时候手动设置cookie的过期时间
- 路径限制:存cookie时候可以指定路径,只允许子路径读取外层cookie,外层不能读取内层。
jsonp
- Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
- 为什么我们从不同的域(网站)访问数据需要一个特殊的技术( JSONP )呢?这是因为同源策略。
const script = document.createElement('script')
script.src = './kerwin.txt'
document.body.appendChild(script)
闭包
- 函数内部返回一个函数,被外界所引用。
- 这个内部函数就不会被销毁回收。
- 内部函数所用到的外部函数的变量也不会被销毁。
- 优点:让临时变量永驻内存
- 缺点:内存泄漏
func = null
- 闭包应用:函数柯里化
//1. 记住列表的索引
var oli = document.querySelectorAll("li")
// for(let i=0;i<oli.length;i++){// oli[i].onclick =function(){// console.log(i)
// }
// }for(var i=0;i<oli.length;i++){oli[i].onclick = (function(index){return function(){console.log(11111,index)
}
})(i) //匿名自执行函数
} //2. jsonp案例优化--函数防抖
JS-108~161相关推荐
- Web应用程序系统的多用户权限控制设计及实现-首页模块【5】
首页模块就是展示不同权限的最终结果了,在阅读这章之前若有些不明白,可看看后续的单独的权限模块,用户模块,目录模块后从整体上再看首页模块. 阅读该模块需要一定或者是比较熟练的js知识,EasyUI Ta ...
- selenium firefox 提取qq空间相册链接
环境: selenium-java 3.9.1 firefox 57.0 geckodriver 0.19.1 1.大概的思路就是模拟用户点击行为,关于滚动条的问题,我是模拟下拉箭头,否则只能每个相册 ...
- 跟着黑马程序员pink老师学习的笔记及小破站学习的笔记
网页 1.网站是指在因特网上根据一定的规则,使用HTML等制作的用于展示特定内容相关的网页集合 2.什么是网页? 网页是网站中的一"页",通常是HTML格式的文件,它要通过浏览器来 ...
- 百度AI人脸识别的学习总结
本文主要分以下几个模块进行总结分析 项目要求:运用百度AI(人脸识别)通过本地与外网之间的信息交互(MQService),从而通过刷脸实现登陆.签字.会议签到等: 1.准备工作: 内网:单击事件按钮- ...
- Bootstrap 快速人门案例——前端最火的插件
今天,我给小白们分享一下比较流行的Bootstrap框架,它在工作中得到许多公司的青睐,因此对于升职和加薪很重要.同时,我们可以快速完成开发任务,减少发开周期,有不对的地方望大家指正. 如果你想走的更 ...
- 从Authy中导出账户和secret
本文转载于我的博客从Authy中导出账户和secret 前言 因为最近买了CanoKey,所以多算试一下CanoKey的TOTP功能,但是之前一直用的Authy并且它默认不支持导出功能 在网上找了一些 ...
- Authy TOTP Token 导出
Authy 为了安全考虑,本身未提供 TOTP Token 导出入口,所以当需要更换其它2FA验证工具的时候,可能不得不重新进行绑定,一两个账号还好,如果账号过多可能就很麻烦了.本文提供两种 Toke ...
- ueditor php 附件,ueditor单独调用上传附件和图片的功能
第一步, 引入文件 第二步 html元素 调用的页面: 上传图片 上传文件 第三步 编写js代码 var _editor; $(function() { //重新实例化一个编辑器,防止在上面的edit ...
- Python统计网站访问日志log中的IP信息,并排序。。
Python统计网站访问日志log中的IP信息,并排序,打印排名靠前的IP及访问量.示例代码如下: #!/usr/bin/env pythonipdict = {}file = open(" ...
- Paper:《Multimodal Machine Learning: A Survey and Taxonomy,多模态机器学习:综述与分类》翻译与解读
Paper:<Multimodal Machine Learning: A Survey and Taxonomy,多模态机器学习:综述与分类>翻译与解读 目录 <Multimoda ...
最新文章
- 红帽企业版Linux成为Linux下的.NET Core的参考平台
- nginx自签SSL证书和Symantec签发证书使用
- UA OPTI512R 傅立叶光学导论15 2-D Fourier变换与Hankel变换
- mysql select db 废弃_php 项目放服务器显示mysql_connect 已经废弃 ?
- Vulhub搭建小记
- s6-4 TCP 数据段
- pr抖动插件_2020最全的8000多款PR插件合集,一键安装
- java学习(43):值参数传递
- mysql动态函数库_mysql自定义函数与动态查询
- [SQL提数]函数的灵活使用
- Java 网络爬虫,就是这么的简单
- 2022-2028年中国环保减速机行业运行动态及投资机会分析报告
- lldp协议代码阅读_软件实现LLDP协议HaneWin LLDP Service
- JS 实现右击菜单功能
- oracle校验统一社会信用代码函数
- [学者笔谈]史占中:大国崛起:从中国制造到中国智造
- 【Go语言实战】(4) 简简单单的几十行代码实现 TCP 通信
- 【AHK】在Obsidian中以选定日期生成链接
- MySQL死锁解决之道
- 2021-05-02 收心继续