本篇文章属于知识总结型,归纳出许多比较零散的知识点,都是干货噢~
如果你是小白那么这篇文章正好适合你,如果你是老手那么不妨巩固一下看看还有哪些边角料没补!
建议:适合有js基础的小伙伴观看,篇幅较长,建议先收藏再慢慢浏览
整整花了一周时间总结了一些比较重点也有些比较偏的知识,希望各位小伙伴慢慢品尝,如果有不对的地方或者是需要优化的地方望请告知,尽量给大家呈现最有价值的文章。个人水平有限,还请各位大佬指点迷津。希望各位看了这篇文章能有自己的想法,在前端道路上还很漫长,与我一同探索吧!
一、 变量类型
二、 深拷贝与浅拷贝
三、 原型与原型链
四、 实现class与extends
五、 继承与实现
六、 作用域、执行上下文与闭包
七、 this
八、 apply、call、bind实现
九、 同步与异步
十、 AMD、CMD、CommonJS与ES6模块化
十一、 script标签之async与defer
十二、 改变数组本身的api
十三、 window之location、navigator
十四、 ajax与fetch
十五、 WebSocket
十六、 短轮询、长轮询与WebSocket
十七、 长连接与短连接
十八、 存储
十九、 跨域
二十、 setTimeout与setInterval
二十一、 requestAnimationFrame
二十二、 事件
二十三、 总结
二十四、 其他文章
对于==的判断
比较情况
// 以下结果都为true
console.log([5]==5,['5']==5)
console.log({name:'5'}=='[object Object]')
console.log('5'==5,true==1,false==0)
console.log(undefined==null)
console.log([5,6]=='5,6',['5','6']=='5,6')
大白话:优先比较类型,同类型,比大小,非原始,调ToPrimitive,为对象调valueOf,还非原始调toString,最后还非原始则报错,如果为原始则进行类型对比,如果不同类型再转换,之后对比大小。
所谓==比较就是要转换成同类型比较,如果无法转成同类型就报错
优先比类型,再比null与undefined,再比string和number,再比boolean与any,再比object与string、number、symbol;以上如果转为原始类型比较,则进行类型转换,直到类型相同再比较值的大小。这就是==的隐式转换对比,比较绕,给个图就清晰了!
如下为判断步骤
思考?如何判断此表达式(注意==!与!==) []==![]
虽然过程复杂,记住判断的思路即可,非对象之间,先类型转换再比大小,对象比较则调用获取原始值方法再进一步比较。
如下为toString与valueOf转换
对于===的判断
const a=[]
const b=a
a===b //true
---------------
const a=[]
const b=[]
a===b //false
7大原始类型与Object类型
类型判断
原始类型判断
非原始类型判断(以及null)
判断数组
Array.isArray()
判断数组[] instanceof Array
判断是否在Array的原型链上,即可判断是否为数组[].constructor === Array
通过其构造函数判断是否为数组Object.prototype.toString.call([])
判断值是否为'[object Array]'来判断数组判断对象
Object.prototype.toString.call({})
结果为'[object Object]'则为对象{} instanceof Object
判断是否在Object的原型链上,即可判断是否为对象{}.constructor === Object
通过其构造函数判断是否为对象判断函数
func typeof function
判断func是否为函数func instanceof Function
判断func是否为函数func.constructor === Function
判断是否为函数Object.prototype.toString.call(func)
判断值是否为'[object Function]'来判断func判断null
null===null
来判断是否为null(!a && typeof (a) != 'undefined' && a != 0 && a==a)
判断a是否为nullObject.prototype.__proto__===a
判断a是否为原始对象原型的原型即nulltypeof (a) == 'object' && !a
通过typeof判断null为对象,且对象类型只有null转换为Boolean为false判断是否为NaN
isNaN(any)
直接调用此方法判断是否为非数值一些其他判断
Object.is(a,b)
判断a与b是否完全相等,与===基本相同,不同点在于Object.is判断+0不等于-0
,NaN等于自身
prototypeObj.isPrototypeOf(object)
判断object的原型是否为prototypeObj,不同于instanceof,此方法直接判断原型,而非instanceof 判断的是右边的原型链一个简单的类型验证函数
function isWho(x) {
// null
if (x === null) return 'null'
const primitive = ['number', 'string', 'undefined',
'symbol', 'bigint', 'boolean', 'function'
]
let type = typeof x
//原始类型以及函数
if (primitive.includes(type)) return type
//对象类型
if (Array.isArray(x)) return 'array'
if (Object.prototype.toString.call(x) === '[object Object]') return 'object'
if (x.hasOwnProperty('constructor')) return x.constructor.name
const proto = Object.getPrototypeOf(x)
if (proto) return proto.constructor.name
// 无法判断
return "can't get this type"
}
在项目中有许多地方需要数据克隆,特别是引用类型对象,我们无法使用普通的赋值方式克隆,虽然我们一般使用第三方库如lodash来实现深拷贝,但是我们也需要知道一些其中的原理
Object.assign({},obj)
浅拷贝objectobj1={...obj2}
通过spread展开运算符浅拷贝obj2Object.fromEntries(Object.entries(obj))
通过生成迭代器再通过迭代器生成对象Object.create({},Object.getOwnPropertyDescriptors(obj))
浅拷贝objObject.defineProperties({},Object.getOwnPropertyDescriptors(obj))
浅拷贝obj简单实现浅拷贝
// a原拷贝对象,b新对象
for (const key in a) {
b[key] = a[key]
}
------------------------------------------
for (const key of Object.keys(a)) {
b[key] = a[key]
}
浅拷贝只拷贝一层属性对于引用类型无法拷贝
JSON.parse(JSON.stringify(obj))
通过JSON的2次转换深拷贝obj,不过无法拷贝undefined与symbol属性,无法拷贝循环引用对象简单深拷贝
//简单版深拷贝,只能拷贝基本原始类型和普通对象与数组,无法拷贝循环引用
function simpleDeepClone(a) {
const b=Array.isArray(a) ? [] : {}
for (const key of Object.keys(a)) {
const type = typeof a[key]
if (type !== 'object' || a[key] === null) {
b[key] = a[key]
} else {
b[key] = simpleDeepClone(a[key])
}
}
return b
}
//精简版深拷贝只能拷贝基本原始类型和普通对象与数组,可以拷贝循环引用
function deepClone(a, weakMap = new WeakMap()) {
if (typeof a !== 'object' || a === null) return a
if (s = weakMap.get(a)) return s
const b = Array.isArray(a) ? [] : {}
weakMap.set(a, b)
for (const key of Object.keys(a)) b[key] = clone(a[key], weakMap)
return b
}
//js原生深拷贝,无法拷贝Symbol、null、循环引用
function JSdeepClone(data) {
if (!data || !(data instanceof Object) || (typeof data == "function")) {
return data || undefined;
}
const constructor = data.constructor;
const result = new constructor();
for (const key in data) {
if (data.hasOwnProperty(key)) {
result[key] = deepClone(data[key]);
}
}
return result;
}
比较完善的深拷贝
//深拷贝具体版,非完全,但大部分都可以
function deepClonePlus(a, weakMap = new WeakMap()) {
const type = typeof a
if (a === null || type !== 'object') return a
if (s = weakMap.get(a)) return s
const allKeys = Reflect.ownKeys(a)
const newObj = Array.isArray(a) ? [] : {}
weakMap.set(a, newObj)
for (const key of allKeys) {
const value = a[key]
const T = typeof value
if (value === null || T !== 'object') {
newObj[key] = value
continue
}
const objT = Object.prototype.toString.call(value)
if (objT === '[object Object]' || objT === '[object Array]') {
newObj[key] = deepClonePlus(value, weakMap)
continue
}
if (objT === '[object Set]' || objT === '[object Map]') {
if (objT === '[object Set]') {
newObj[key] = new Set()
value.forEach(v => newObj[key].add(deepClonePlus(v, weakMap)))
} else {
newObj[key] = new Map()
value.forEach((v, i) => newObj[key].set(i, deepClonePlus(v, weakMap)))
}
continue
}
if (objT === '[object Symbol]') {
newObj[key] = Object(Symbol.prototype.valueOf.call(value))
continue
}
newObj[key] = new a[key].constructor(value)
}
return newObj
}
刨析深拷贝(个人思路)
Reflect.OwnKeys(obj)
取出对象自身所有的键,包括Symbol的键也能取出Object.prototype.toString.call(obj)
来进行对象具体类型的判断以上就是本人在实现过程中的思路,可能讲的比较啰嗦,但是我还是希望使用通俗的话让各位明白,表达能力有限,望谅解。
接下来让我们看看WeakMap的好处
let obj = {
name: {
age: [{
who: 'me'
}]
}
}
let wm = new WeakMap()
deepClonePlus(obj, wm)
obj=null
console.dir(wm) // No properties 即为空
从上面可以看出如果原拷贝对象被清空那么WeakMap保存的拷贝表也将被清空,总的来说方便一点,总比麻烦一点好
看看这种情况
const obj = {
name: {
age: [{
who: 'me'
}]
}
}
let wm = new WeakMap()
console.time('start')
for (let i = 0; i < 1000000; i++) {
deepClonePlus(obj, wm) // wm为手动传入的weakmap
// 此处为了与下面对比,这里故意重置weakmap存储的拷贝值
wm = new WeakMap()
}
console.timeEnd('start') // 耗时2645ms
------------------------------------------------
let wm = new WeakMap()
let m
console.time('start')
for (let i = 0; i < 1000000; i++) {
deepClonePlus(obj, wm)
// 此次为对照组,也执行创建WeakMap但是不重置之前拷贝的wm
m = new WeakMap()
}
console.timeEnd('start') // 耗时73ms
从以上对比可以看出如果是多次拷贝同一对象,最好使用WeakMap来存储拷贝表,那么之后的每次拷贝只需从拷贝表中取出值即可,由于是浅拷贝所以时间较短(注意:不过这种直接从WeakMap中取出的值属于浅拷贝,使用同一个wm对象拷贝出来的都是浅拷贝,如果每个都需要深拷贝那么只能每次重新创建WeakMap)
为了方便后续讲解,这里先介绍几个知识点:
一、__proto__
属性
对象的
__proto__
属性并非ECMAScript标准,由于早期无法获取对象原型即对象内部[[Prototype]]属性,各大浏览器厂家对Object.prototype通过访问描述符实现__proto__
的getter与setter来达到访问调用对象的[[Prototype]],[[Prototype]]属性属于对象内部属性无法直接访问,此属性指向对象原型。
__proto__
大致实现
Object.defineProperty(Object.prototype,'__proto__',{
get: function(){
return Object.getPrototypeOf(this) // 获取引用对象的[[Prototype]]
},
set: function(o){
Object.setPrototypeOf(this,o) // 设置引用对象[[Prototype]]属性关联的原型为o
return o
}
})
所以本质上是通过访问器属性来获取与设置对象关联的原型,可以理解通过__proto__
能获取与设置原型的引用
这里先把普通对象的__proto__
属性就称呼为对象原型,以便接下来的讲解
二、函数的prototype属性
所有函数都有的prototype属性,js中函数也属于对象的子类型,所以函数也具备对象的
__proto__
与普通对象类似都指向其原型。而这里的prototype属性,是函数独有的。当函数使用new关键字修饰时,我们可以理解为此函数被当作构造函数使用也就是构造器。当函数被用作构造函数调用时,其prototype发挥了作用,使得由构造器new出来对象的__proto__
指向构造函数的prototype。
以下演示函数prototype属性在实例化时的作用
function Foo(){} // 定义构造函数
console.dir(Foo.prototype) // 定义Foo构造函数时,自动创建的“干净的实例原型”,在原型链第二幅图的左下角有体现
const obj = new Foo() //创建一个实例对象
console.dir(obj.__proto__===Foo.prototype) // true,表名实例关联的原型即为构造函数的prototype指向的原型对象
为了便于讲解,这里把函数的prototype称呼为构造器原型,以便接下来的讲解。这里要区分函数的__proto__
属性是作为对象时,关联的原型(即对象原型),函数的prototype
作为构造函数调用时关联的原型(即构造器原型),这里要先弄清楚其中的区别,以便接下来的讲解
三、各类方法与属性的统称
构造函数中定义的方法,我们统称为静态方法,构造函数中定义的属性我们统称为静态属性。在原型中定义的属性,我们统称为原型属性,在原型中定义的方法,我们统称为原型方法。实例中的属性以及方法,我们也就称呼为实例属性/方法。当然方法也属于属性,只是我们通常把定义在对象中的函数称为方法
__proto__
属性,此属性其实是个访问器属性,并不是真实存在的属性,或者可以使用es6的Reflect.getPrototypeOf(obj)
和Object.getPrototypeOf(obj)
方法获取对象的原型,其关系Reflect.getPrototypeOf({}) === Object.getPrototypeOf({}) === {}.__proto__
__proto__
(与普通对象类似),还有一个是函数专有的prototype
属性,因为函数有双重身份,即可以是实例也可以是构造器,所以关系比较特殊Object.prototype
的原型Object.prototype.__proto__
就指向null,字典对象的原型也为null(把对象的__proto__
设置为null,或者使用Object.create(null)
创建一个没有原型的字典对象,但是这个对象还是属于对象类型),所以原始对象原型(Object.prototype)就是最原始的原型,其他对象类型都要继承自它。这里会详细介绍原型、原型链、实例、构造器的关系 先看最原始的关系
由如上关系可以验证console.log(Function.prototype.__proto__.constructor.__proto__.constructor === Function) //true
__proto__
就是Function,但是构造器的prototype属性指向的原型,是此构造器实例化出来的实例所指向的原型;简单说构造器的prototype就是作为它的实例的原型看看函数的原型链
const Class = (function () {
function Constructor(name) {
this.name = name
}
//添加原型方法
Constructor.prototype.getName = function name(name) {
console.log('原型方法getName:' + this.name);
}
//添加原型属性
Constructor.prototype.age = '原型属性age'
//添加静态方法
Constructor.log = function log() {
console.log('我是构造器的静态方法log');
}
//添加静态属性
Constructor.isWho = '构造器静态属性isWho'
return Constructor
})()
const i = new Class('我是实例')
实现class语法糖,只需封装一层函数。
以上只实现class的定义,接下来要实现能够兼容继承的写法
//父类
const Parent = (function () {
function Constructor(age) {
this.age = age
}
Constructor.prototype.getName = function () {
console.log(this.name);
}
return Constructor
})()
//子类
const Class = (function (_Parent = null) {
if (_Parent) {
Constructor.prototype = Object.create(_Parent.prototype, {
constructor: {
value: Constructor,
enumerable: false,
writable: true,
configurable: true
}
})
Constructor.__proto__ = _Parent
}
function Constructor(name, age) {
_Parent ? _Parent.call(this, age) : this
this.name = name
}
Constructor.prototype.getAge = function () {
console.log(this.age);
}
return Constructor
})(Parent)
__proto__
function Parent(){} // 定义父类构造器
function Children(){} // 定义子类构造器
let ChildPrototype = Children.prototype // 构造器原型
let ChildPrototypeProto = Children.prototype.__proto__ // 构造器原型的对象原型
// 方法一
ChildPrototypeProto = Parent.prototype // 父类构造器原型作为子类构造器原型(ChildPrototype)的对象原型(ChildPrototypeProto)
//方法二
ChildPrototype = Object.create(Parent.prototype) // Object.create返回一个对象,其__proto__指向传入的参数,也就实现返回的对象继承参数对象
//方法三
Object.setPrototypeOf(ChildPrototype, Parent.prototype) // 直接设置参数1的原型(__proto__)为参数2
以上仅实现了原型之间的继承
prototype
与__proto__
,其中prototype
是给实例用的,而__proto__
是给自己用的。__proto__
),来实现静态属性继承function Parent() {} // 定义父构造函数
function Children() {} //定义子构造函数
// 定义父构造函数的静态方法
Parent.foo = function () {
console.log(this.name)
}
// 方法一
Children.__proto__ = Parent // 子构造函数的对象原型指向父构造函数,也就实现继承
// 方法二
Object.setPrototypeOf(Children, Parent) // 同原型继承
console.log(Children.foo) // function(){ console.log(this.name) } ,实现继承
以上即为构造函数之间通过对象原型继承静态属性,注:函数也是对象
// 定义父构造函数
function Parent(name) {
this.name = name
}
//定义子构造函数
function Children(name,age) {
Parent.call(this,name) // 这里调用父构造器,实现实例属性继承
this.age = age
}
const obj = new Children('tom', 5)
console.log(obj) // {name: 'tom', age: 5} ,实现实例属性继承
通过实例属性继承,可以把父构造器中默认生成的实例属性追加到子构造器实例化出来的对象上
综合以上继承,现在实现真正的继承
// 定义父构造函数,功能:初始化实例name属性
function Parent(name) {
'use strict'
this.name = name
}
// 定义父构造函数的静态方法,功能:设置调用对象的name属性
Parent.setName = function setName(obj, name) {
obj.name = name
}
// 定义父构造器原型(prototype)的方法,功能:获取调用对象的name属性
Parent.prototype.getName = function getName() {
return this.name
}
/*-----以上已定义父类的原型方法(获取name),父类静态方法(设置name),以及构造器默认初始化的属性name------*/
// 定义子构造函数,功能:初始化实例age属性,以及通过父构造器初始化实例name属性
function Children(name, age) {
'use strict'
Parent.call(this, name) // 调用父构造器,初始化name属性
this.age = age // 子构造器初始化age属性
}
// 定义子构造函数的静态方法,功能:设置调用对象的age属性
Children.setAge = function setAge(obj, age) {
obj.age = age
}
// 原型继承
// 设置Children.prototype['[[Prototype]]']= Parent.prototype,此处的'[[Prototype]]'与设置__proto__相同
Children.prototype = Object.create(Parent.prototype)
// 注意此处原型继承之后,不带有constructor属性,应该手动指明为Children
Object.defineProperty(Children.prototype, 'constructor', {
value: Children,
writable: true, // 可写
enumerable: false, // 不可枚举
configurable: true, // 可配置
})
//以上2句可以直接写成一句
/*
Children.prototype = Object.create(Parent.prototype, {
constructor: {
value: Children,
writable: true, // 可写
enumerable: false, // 不可枚举
configurable: true, // 可配置
}
})
*/
// 由于子构造器原型方法必须在继承之后再定义,否则会被继承覆盖
// 定义子构造器原型(prototype)的方法,功能:获取调用对象的age属性
Children.prototype.getAge = function getAge() {
return this.age
}
// 构造函数(继承静态属性)继承
// 设置Children.__proto__ = Parent,注意此处不能使用Children = Object.create(Parent),因为Object.create返回的是一个对象不能替换构造函数
Object.setPrototypeOf(Children, Parent)
// 测试父级
const obj = new Parent('tom') // 实例化父级实例
console.log(obj.getName()) // tom
Parent.setName(obj, 'jerry') // 通过父级静态方法设置name
console.log(obj.getName()) // jerry
console.log(obj instanceof Parent) // true
// 测试子级
const obj1 = new Children(null, 5) // 实例化子级实例
console.log(obj1.getAge()) // 5
Children.setAge(obj1, 8) // 通过子级静态方法设置age
console.log(obj1.getAge()) // 8
console.log(obj1 instanceof Parent) // true
console.log(obj1 instanceof Children) // true
// 完整测试继承
const test = new Children('tom', 5) // 实例化子级实例,name='tom',age=5
console.log(test.getName()) // tom
Parent.setName(test, 'jerry') // 通过父级静态方法设置name=jerry
console.log(test.getName()) // jerry
console.log(test.getAge()) // 5
Children.setAge(test, 8) // 通过子级静态方法设置age=8
console.log(test.getAge()) // 8
class P {
constructor(name) {
this.name = name
}
static setName(obj, name) {
obj.name = name
}
getName() {
return this.name
}
}
class C extends P {
constructor(name, age) {
super(name)
this.age = age
}
static setAge(obj, age) {
obj.age = age
}
getAge() {
return this.age
}
}
// 这里就不带测试了,可以自行验证,比对一下有什么区别
console.dir(Children)
console.dir(C)
实现继承,需要对原型、构造器、实例属性都加以实现继承
作用域
a=1 // 隐式全局变量 严格模式报错
var b=2 // 显式全局变量
console.log(a,b) //1 2
delete a // 严格模式报错
delete b // 严格模式报错
console.log(b,a) // 2 a is not defined
// 以下语句使用let声明不报错,说明为不同作用域
for (let i = 0; i < 5; i++) {
let i = 5
}
--------------------------------------------
// 此语句报错,说明循环体为条件语句块的子作用域
// for循环执行顺序为:条件语句块1->条件语句块2->循环体->条件语句块3->条件语句块2 依次类推
for (let i = 0; i < 5; i=x) { // x is not defined
let x = 5
}
作用域链
b = 1
function a() {
// 定义b,找到
const b = 2
function s() {
// 使用到b,当前作用域并没有,向上找
console.log(b);
}
return s
}
const s = a()
var b = 3
s() // 2
这里了解一下函数、变量提升
console.dir(foo) // foo(){}
function foo() {}
var foo = 5
/*
console.dir(foo) // undefined
var foo = 5
*/
------------------------------
var foo = 5
function foo() {}
console.dir(foo) // 5
从以上代码结果可以得出结论:
function fn1() {
var name = 'hi';
function fn2() {
console.log(name);
}
return fn2
}
fn1()() // hi
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
这里看个有趣的东西
function foo(){
let a={name:'me'}
let b={who:'isMe'}
let wm=new WeakMap()
function bar(){
console.log(a) // a被闭包保留
wm.set(b,1) // 弱引用b对象
return wm //wm被闭包保留
}
return bar
}
const wm=foo()()
console.dir(wm) // No properties 即为空
-------------------------------------------
function foo(){
let a={name:'me'}
let wm=new WeakMap()
function bar(){
console.log(a)
wm.set(a,1)
return wm
}
return bar
}
const wm=foo()()
console.dir(wm) // 保存了对象a与其值1
先看一张图
function foo(){
console.dir(this) // window ,严格下undefined
}
foo()
-----------------------------------------------
function foo(){
console.dir(this) //非严格Number对象,严格模式 5
}
foo.call(5)
严格与非严格模式下的this指向是不同的,非严格总是指向一个对象,严格模式可以为任意值
执行前
执行后
以上2图可以使用chrome开发工具来进行查看程序执行时的相关数据,可以看到严格模式下简单调用的函数内部的this指向undefined
直接调用
在没有明确调用者情况下函数内部this指向window,严格模式下都为undefined,除非绑定函数的this指向,才会改变this
// 直接调用函数
function foo() {
console.dir(this) //window,严格下 undefined
function boo(){
console.dir(this) //window,严格下 undefined
}
boo()
}
----------------------------------------------
// 取出对象中的函数,再进行调用
const obj = {
foo: function foo() {
console.dir(this) //window,严格下 undefined
function boo() {
console.dir(this) //window,严格下 undefined
}
return boo
}
}
const foo = obj.foo
foo()()
----------------------------------------------
// 直接通过对象调用函数,再调用返回的函数,可以看出this的指向随调用对象改变
const obj = {
foo: function foo() {
console.dir(this) //obj,严格下 obj
function boo() {
console.dir(this) //window,严格下 undefined
}
return boo
}
}
const foo = obj.foo()
foo()
----------------------------------------------
// 基于回调函数也是如此
function foo(func) {
console.dir(this) // window ,严格下 undefined
func()
}
foo(function () {
console.dir(this) // window ,严格下 undefined
})
基于调用者以及不同调用方式
函数调用也就是在函数名后面加个(),表示调用,如果函数名前没有加任何东西,那么默认为简单调用,在严格与非严格环境下,简单调用的函数内部this指向undefined与window,但是全局环境下的this永远为window
基于对象
当函数作为对象的方法调用时,不受函数定义方式或者位置影响
// 函数this指向调用者对象
const obj = {
foo: function () {
console.dir(this) // obj1,严格下 obj1
function boo() {
console.dir(this) // window,严格下 undefined
}
boo()
return boo
}
}
const obj1 = {}
obj1.boo = obj.foo
obj1.boo()
----------------------------------------------
// 不同调用对象时,this指向调用者
const obj = {
foo: function () {
console.dir(this) // obj,严格下 obj
function boo() {
console.dir(this)
}
boo() // window,严格下 undefined
return boo
}
}
const obj1 = {}
obj1.boo = obj.foo()
obj1.boo() // obj1,严格下 obj1
----------------------------------------------
// this指向最近的调用者
const obj = {
name: 'obj',
obj1: {
name: 'obj1',
foo: function () {
console.dir(this.name) // obj1
}
}
}
obj.obj1.foo()
基于new关键字
// 基于new关键字调用的函数内部this指向实例
function foo() {
console.dir(this) // foo实例
console.log(this instanceof foo) //true
console.log(foo.prototype.isPrototypeOf(this)) //true
that = this
}
var that
const f = new foo()
console.log(that === f) // true
----------------------------------------------
// 嵌套函数内部this与调用函数所在环境的this无关
function foo() {
console.dir(this) // foo实例
function boo() {
console.dir(this) //window,严格下undefined
}
boo()
}
const f = new foo()
基于定时器与微任务
微任务中的简单调用的函数this指向window严格下指向undefined,而定时器中的回调函数不管在严格还是非严格环境下this永远指向window,说明一点,调用window对象的方法时this指向window也就是全局对象,换句话说,简单调用的函数如果属于window本身自带的方法那么这个方法的this指向window
// 异步任务中简单调用的函数都是进入队列,最后由全局环境调用
const id = setInterval(function () {
console.dir(this) // window ,严格下 window
setTimeout(() => {
console.dir(this) // window ,严格下 window
clearInterval(id)
});
})
----------------------------------------------
new Promise(function (resolve, reject) {
console.dir(this) // window ,严格下 undefined
resolve()
}).then(function (res) {
console.dir(this) // window ,严格下 undefined
});
----------------------------------------------
(async function foo() {
function boo() {
console.dir(this) // window ,严格下 undefined
}
await boo()
console.dir(this) // window ,严格下 undefined
})()
----------------------------------------------
// 定时器的回调最终都会被作为简单函数被执行,定时器属于window对象的方法
function foo(){
setTimeout(function (){
console.log(this) //window ,严格下window
})
}
foo.call(5)
----------------------------------------------
// 函数内部的this就是指向调用者,并且可以看出简单调用的回调函数中的this也指向window
const obj = {
foo(callback) {
callback()
console.log(this.foo === obj.foo) // true
console.log(this === obj) // true
}
}
obj.foo(function () {
console.log(this) //window ,严格下undefined
})
----------------------------------------------
// 通过arguments调用的回调函数中的this指向调用者,注意严格与非严格下的arguments对象有所不同
const obj = {
foo(callback) {
arguments[0]()
console.log(this.foo === obj.foo) // true
console.log(this === obj) // true
}
}
obj.foo(function () {
console.log(this) //arguments对象 ,严格下 arguments对象
})
es6引入的箭头函数,是不具有this绑定,不过在其函数体中可以使用this,而这个this指向的是箭头函数当前所处的词法环境中的this对象,可以理解为,this在箭头函数中是透明的,箭头函数包不住this,所以函数内部与外部的this为同一值
// 可以看出箭头函数中的this就是其所在环境的this,箭头函数无法固定this,由其环境决定
const foo = () => {
console.dir(this) //window ,严格下还是window
}
foo()
----------------------------------------------
// 可见对象中的this指向window,箭头函数中的this指向对象中的this。由于只有创建执行上下文才会绑定this指向,而除了全局上下文,只有函数作用域才会创建上下文环境从而绑定this,创建对象不会绑定this,所以还是全局this
const obj={
this:this,
foo:()=>{
console.dir(this) //window ,严格下 window
}
}
console.dir(obj.this) //window ,严格下 window
obj.foo()
---------------------------------------------
// 对象方法内部嵌套箭头函数,则此箭头函数的this属于外部非箭头函数this。当调用obj.foo时foo函数创建的执行上下文中的this绑定对象obj,而箭头函数并不会绑定this,所以其this属于foo下的this,即对象obj
const obj = {
foo: function () {
return () => {
console.dir(this) //obj ,严格下 obj
}
}
}
obj.foo()()
最简单的方法通过apply、call、bind来给函数绑定this
// 通过变量保留父级this,进行对_this变量修改也就达到修改原this的效果
const obj = {
name: 'obj',
foo: function () {
let _this = this
function boo() {
_this.name = 'OBJ'
console.dir(obj.name) // OBJ
}
return boo
}
}
obj.foo()()
这3者的实现其实差不多,bind实现可能会有点不一样,都要实现this的改变
Function.prototype.Apply = function (thisArg, args = Symbol.for('args')) {
console.dir(this) //this为这个方法的调用者=>foo函数
const fn = Symbol('fn') //生成一个不重复的键
thisArg[fn] = this || window //把foo函数作为传入this的一个方法
args === Symbol.for('args')
? thisArg[fn]()
: thisArg[fn](...args) //调用这方法,传参
delete thisArg[fn] //使用完删除
}
var name = 'foo'
var age = 5
function foo(age,height) {
console.log(this.name) // obj
console.log(age) // 3
console.log(height) // null
}
const obj = {
name: 'obj',
age: 3
}
foo.Apply(obj,[obj.age,null])
基本思路同apply,就是传参形式改变一下,这里通过arguments获取参数列表
Function.prototype.Call = function (thisArg) {
console.dir(this) //this为这个方法的调用者=>foo函数
const fn = Symbol('fn') //生成一个不重复的键
thisArg[fn] = this || window //把foo函数作为传入this的一个方法
const args = Array.from(arguments).slice(1)
args.length ? thisArg[fn](...args) : thisArg[fn]() //调用这方法,传参
delete thisArg[fn] //使用完删除
}
bind函数要能够返回严格绑定this与参数后的函数,调用这个返回的函数时有可能还会传入参数,那么需要拼接参数
Function.prototype.Bind = function (thisArg) {
const fn = Symbol('fn') //生成一个不重复的键
thisArg[fn] = this || window //把foo函数作为传入this的一个方法
const f = thisArg[fn] // 负责一份函数
delete thisArg[fn] //删除原来对象上的函数,但是保留了this指向
const args = Array.from(arguments).slice(1)
return function () {
const arg = args.concat(...arguments)
f(...arg)
}
}
var name = 'foo'
var age = 5
var height = 4
function foo(age, height) {
console.log(this.name) // obj
console.log(age) // 3
console.log(height) // 2
}
const obj = {
name: 'obj',
age: 3
}
foo.Bind(obj, obj.age)(2)
异步、单线程与EventLoop
先看一张图,有个大体架构
微任务
宏任务
js单线程
EventLoop
以下展示的是事件循环大致流程
以下为主线程判断逻辑
// test.html(主线程)
const w= new Worker('postMessage.js')
w.onmessage=function(e){
console.log(e.data);
}
w.postMessage('b') // b is cat
w.terminate() // 手动关闭子线程
----------------------------------------------
// postMessage.js(worker线程)
this.addEventListener('message', (e) => {
switch (e.data) {
case 'a': this.postMessage(e.data+' is tom')
break;
case 'b': this.postMessage(e.data + ' is cat')
break;
default: this.postMessage(e.data + " i don't know")
this.close() // 自身关闭
break;
}
})
模块化的引入主要是用于解决命名冲突、代码复用、代码可读性、依赖管理等
define(id?,dependencies?,factory)
定义模块,id可选,为定义模块的标识,默认为模块文件名不包括后缀,dependencies可选,是当前模块依赖的模块路径数组,factory为工厂方法,初始化模块的函数或者对象,如果为函数将会只执行一次,如果是对象将作为模块的输出require(dependencies,factory)
导入模块,其中dependencies为需要导入的模块路径数组,factory为当模块导入之后的回调函数,此函数的参数列表为对应导入的模块require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min", //实际路径为js/lib/jquery.min.js
"underscore": "underscore.min",
}
})
define(id?,deps?,factory)
,id同AMD,deps一般不在其中写依赖,而是在factory中在需要使用的时候引入模块,factory函数接收3各参数,参数一require方法,用来内部引入模块的时候调用,参数二exports是一个对象,用来向外部提供模块接口,参数三module也是一个对象上面存储了与当前模块相关联的一些属性和方法seajs.use(deps,func)
加载模块,deps为引入到模块路径数组,func为加载完成后的回调函数AMD、CMD的主要区别在于
AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块 CMD推崇就近依赖,只有在用到某个模块的时候再去require
module.exports
(不推荐exports)导出模块对象,通过require(模块路径)加载模块__dirname
代表当前模块文件所在的文件夹路径,__filename
代表当前模块文件夹路径+文件名pop()
尾部弹出一个元素push()
尾部插入一个元素shift()
头部弹出一个元素unshift()
头部插入一个元素sort([func])
对数组进行排序,func有2各参数,其返回值小于0,那么参数1被排列到参数2之前,反之参数2排在参数1之前reverse()
原位反转数组中的元素splice(pos,deleteCount,...item)
返回修改后的数组,从pos开始删除deleteCount个元素,并在当前位置插入itemscopyWithin(pos[, start[, end]])
复制从start到end(不包括end)的元素,到pos开始的索引,返回改变后的数组,浅拷贝arr.fill(value[, start[, end]])
从start到end默认到数组最后一个位置,不包括end,填充val,返回填充后的数组其他数组api不改变原数组
window.location===document.location
,其中的属性都是可读写的,但是只有修改href和hash才有意义,href会重新定位到一个URL,hash会跳到当前页面中的anchor名字的标记(如果有),而且页面不会被重新加载// 这行代码将会使当前页面重定向到http://www.baidu.com
window.location.href = 'http://www.baidu.com'
----------------------------------------------
// 如果使用hash并且配合input输入框,那么当页面刷新之后,鼠标将会自动聚焦到对应id的input输入框,
先看下其拥有的属性
这里补充一个origin属性,返回URL协议+服务器名称+端口号 (location.origin == location.protocol + '//' + location.host)
window.addEventListener("hashchange", funcRef);
// 或者
window.onhashchange = funcRef;
location方法
assign(url)
,通过调用window.location.assign
方法来打开指定url的新页面window.location.assign('http://www.baidu.com')
在当前页面打开百度,可回退replace(url)
,在当前页面打开指定url,不可回退reload([Boolean])
,调用此方法将会重新加载当前页面,如果参数为false或者不填,则会以最优的方式重新加载页面,可能从缓存中取资源,如果参数为true则会从服务器重新请求加载资源window.navigator
对象包含有关浏览器的信息,可以用它来查询一些关于运行当前脚本的应用程序的相关信息document.write("浏览器的代码名:" + navigator.appCodeName + "
");
document.write("浏览器的名称:" + navigator.appName + "
");
document.write("当前浏览器的语言:" + navigator.browserLanguage + "
");
document.write("浏览器的平台和版本信息:" + navigator.appVersion + "
");
document.write("浏览器中是否启用 cookie :" + navigator.cookieEnabled + "
");
document.write("运行浏览器的操作系统平台 :" + navigator.platform + "
");
navigator.appCodeName
只读,任何浏览器中,总是返回 'Gecko'。该属性仅仅是为了保持兼容性。navigator.appName
只读,返回浏览器的官方名称。不要指望该属性返回正确的值。navigator.appVersion
只读,返回一个字符串,表示浏览器的版本。不要指望该属性返回正确的值。navigator.platform
只读,返回一个字符串,表示浏览器的所在系统平台。navigator.product
只读,返回当前浏览器的产品名称(如,"Gecko")。navigator.userAgent
只读,返回当前浏览器的用户代理字符串(user agent string)如下在不同浏览器打印的信息
/*
chrome:
Mozilla/5.0
(Macintosh; Intel Mac OS X 10_12_6)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/61.0.3163.91 Safari/537.36
safari:
Mozilla/5.0
(Macintosh; Intel Mac OS X 10_12_6)
AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0
Safari/604.1.38
ios11刘海X:
Mozilla/5.0
(iPhone; CPU iPhone OS 11_0 like Mac OS X)
AppleWebKit/604.1.38 (KHTML, like Gecko)
Version/11.0 Mobile/15A372 Safari/604.1
ipad:
Mozilla/5.0
(iPad; CPU OS 9_1 like Mac OS X)
AppleWebKit/601.1.46 (KHTML, like Gecko)
Version/9.0 Mobile/13B143 Safari/601.1
galxy sansum:
Mozilla/5.0
(Linux; Android 5.0; SM-G900P Build/LRX21T)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/61.0.3163.91 Mobile Safari/537.36
安装uc浏览器:
Mozilla/5.0
(Linux; U; Android 6.0.1; zh-CN; Mi Note 2 Build/MXB48T)
AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0
Chrome/40.0.2214.89 UCBrowser/11.4.9.941 Mobile Safari/537.36
winphone:
Mozilla/5.0
(Linux; Android 5.1.1; Nexus 6 Build/LYZ28E)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/61.0.3163.91 Mobile Safari/537.36
hybrid方法的可能:
Mozilla/5.0
(iPhone; CPU iPhone OS 11_0 like Mac OS X)
AppleWebKit/604.1.38 (KHTML, like Gecko)
Mobile/15A372 weibo/80011134
*/
window.XMLHttpRequest
构造器实例化一个网络请求对象const XHR = new XMLHttpRequest()
XHR.open(method, url, [ async, [ user, [ password]]])
此方法用来发送一个请求,method为请求方法,url为请求地址,async为boolean值默认为true即使用异步请求,user和password在请求需要用户和密码的时候使用XHR.send(body)
参数为发生请求主体内容,其格式可以为FormData、ArrayBuffer、Document、序列化字符串,在收到响应后,响应的数据会自动填充XHR对象的属性XHR.setRequestHeader(header,value)
设置请求头的类型与值,当以post方式发起请求就用设置XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
此请求头,值可更改XHR.onreadystatechange = function () { }
XHR.responseText
属性为此次响应的数据,为字符串,可能是JSON格式需要JSON.parse解析XHR.responseXML
属性为xml形式的数据,可以通过XHR.responseType = 'document'
和XHR.overrideMimeType('text/xml')
来解析为XMLXHR.withCredentials
属性设置为boolean值,通过此属性来设置是否使用cookies、authorization等凭证字段XHR.timeout
通过此属性来设置请求超时时间XHR.ontimeout
通过此属性来设置请求超时的回调函数,函数的参数为事件对象XHR.abort()
此方法用来终止网络请求XHR.getAllResponseHeaders()
此方法用来获取所有的响应头XHR.getResponseHeader(name)
此方法用来获取指定的响应头XHR.addEventListener(eventname,callback)
方法添加对应的事件监听,其回调函数接收一个事件对象参数简单的发起一次请求
// 最简单的发起一个请求
const XHR = new XMLHttpRequest()
XHR.open('get','http://127.0.0.1:3000/test?key=value')
XHR.send()
XHR.addEventListener('load',(e)=>{
// 服务端返回的是查询参数
console.log(XHR.response) // {"key":"value"}
})
基于XMLHttpRequest封装一个请求方法
// 发送的数据
const data = {
name: 'tom'
}
// 请求配置
const config = {
type: "post",
url: "http://127.0.0.1:3000/test",
data: data,
dataType: 'application/json',
success: function (res) {
console.log(res);
},
error: function (e) {
console.log(e);
}
}
// 请求构造器
function Ajax(conf) {
this.type = conf.type || 'get'
this.url = conf.url || ''
this.data = conf.data || {}
this.dataType = conf.dataType || ''
this.success = conf.success || null
this.error = conf.error || null
}
// send方法
Ajax.prototype.send = function () {
if (this.url === '') return
const XHR = new XMLHttpRequest()
XHR.addEventListener('load', () => {
if (XHR.status >= 200 && XHR.status < 300 || XHR.status == 304) {
typeof this.success === 'function' && this.success(XHR.response)
}
})
XHR.addEventListener('error', (e) => {
typeof this.error === 'function' && this.error(e)
})
if (this.type.toLowerCase() === 'get') {
XHR.open('get', this.url)
XHR.send(null)
} else {
XHR.open(this.type, this.url)
XHR.setRequestHeader('Content-Type', this.dataType || 'application/x-www-form-urlencoded')
let data = this.data
if (this.dataType === 'application/json') {
data = JSON.stringify(this.data)
}
XHR.send(data)
}
}
// 发送请求
const ajax = new Ajax(config).send()
由于网络请求模块封装较繁琐,这里就简单封装了一下,仅供参考(。^▽^)
// fetch方法返回一个Promise对象,可用then方法接收结果,用catch方法捕获异常,同Promise使用
// 配置对象具体配置
const config = {
method: 'GET', // 请求方法
headers: { // 头信息
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
},
body: JSON.stringify({ // 请求的 body 信息,Blob, FormData 等
data: 1
}),
mode: 'cors', // 请求的模式,cors、 no-cors 或 same-origin
credentials: 'include', // omit、same-origin 或 include。为了在当前域名内自动发送 cookie, 必须提供这个选项
cache: 'no-cache', // default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached
redirect: 'follow', // 可用的 redirect 模式: follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误), 或者 manual (手动处理重定向).
referrer: 'no-referrer', // no-referrer、client或一个 URL。默认是 client。
referrerPolicy: 'no-referrer', // 指定 referer HTTP头
integrity: 'sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=', // 包括请求的 subresource integrity 值
}
// 发起请求
fetch('http://biadu.com' [, config])
then的回调函数接受一个Response对象参数,其对象拥有9个属性,8个方法
9个属性
8个方法
WebSocket(url[, protocols])
创建实例,参数1 对方绝对url其url以ws://
或者wss://(加密)
开头,参数2 protocols是单协议或者包含协议的字符串数组// 必须传入绝对URL,可以是任何网站
const s = new WebSocket('ws://www.baidu.com')
s.readyState // 0 建立连接 1 已经建立 2 正在关闭 3 连接已关闭或者没有链接成功
s.send('hello') // 发送的数据必须是纯文本
s.onopen = function () {}
s.onerror = function () {}
s.onmessage = function (event) {
// 当接收到消息时
console.log(event.data) // 数据是纯字符
}
s.close() // 关闭连接
s.onclose = function (event) {
/*
* event.wasClean 是否明确的关闭
* event.code 服务器返回的数值状态码
* event.reason 字符串,服务器返回的消息
*/
}
简单演示
const xhr = new XMLHttpRequest()
// 每秒发送一次短轮询
const id = setInterval(() => {
xhr.open('GET', 'http://127.0.0.1:3000/test?key=value')
xhr.addEventListener('load', (e) => {
if (xhr.status == 200) {
// 处理数据
console.log(xhr.response)
// 如果不需要可以关闭
clearInterval(id)
}
})
xhr.send()
}, 1000)
简单演示
function ajax() {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:3000/test?key=value');
xhr.addEventListener('load', (e) => {
if (xhr.status == 200) {
// 处理数据
console.log(xhr.response)
// 如果不需要可以关闭
if (xhr.response != '') return
ajax()
}
})
xhr.send();
}
Connection:keep-alive
Connection:keep-alive
来决定使用,而是否轮询,是根据服务端的处理方式来决定的,与客户端没有关系
https://localhost:80/
和http://localhost:8080/
的Cookie是共享的,可以通过domain设置域,path设置域下的共享路径.baidu.com
,则a.baidu.com
可访问其上级域名的cookie
localStorage
基本使用
localStorage.setItem("b", "isaac"); //设置b为"isaac"
localStorage.getItem("b"); //获取b的值,为"isaac"
localStorage.key(0); //获取第0个数据项的键名,此处即为“b”
localStorage.removeItem("b"); //清除c的值
localStorage.clear(); //清除当前域名下的所有localStorage数据
window.addEventListener("storage", function(e){}
设置localStorage事件监听,当存储区域的内容发生改变时,将会调用回调sessionStorage
sessionStorage.setItem(name, num); //存储数据
sessionStorage.setItem('value2', 119);
sessionStorage.valueOf(); //获取全部数据
sessionStorage.getItem(name); //获取指定键名数据
sessionStorage.sessionData; //sessionStorage是js对象,也可以使用key的方式来获取值
sessionStorage.removeItem(name); //删除指定键名数据
sessionStorage.clear();
区别
来实现一下吧
// 前端准备
// 定义回调函数
function fn(arg) {
// arg为服务端传来的数据
console.log(`客户端获取的数据:${arg}`)
}
// 创建script标签
const s = document.createElement('script')
// 给script标签的src属性赋值,值为请求url,查询参数callback,需与后端对应
// fn为前端回调函数名
s.src = `http://127.0.0.1:3000/test?callback=fn`
// 向html添加此标签,添加完成之后浏览器自动请求script的src对应的网址
document.getElementsByTagName('head')[0].appendChild(s);
// 等待浏览器收到响应之后,将会自动执行响应内容的代码
----------------------------------------------
// 后端准备
// nestjs(ts)处理
@Controller('test') //api
export class TestController {
@Get() //get方式请求
//取url中的查询参数,即?之后的键值对,键与值对应query对象参数的键与值
callback(@Query() query) {
// 返回的数据
const data = '我是服务端返回的数据';
// 取查询参数,这里的callback要与前端?之后的键名一致,fn即fn函数名
const fn = query.callback;
// 返回结果,格式:函数名(服务器的数据),注意这里需要序列化成字符串,如果参数本身是字符串那么要加引号,前端并不知道data是字符串
return `${fn}('${data}')`;
}
}
// express(js)处理,同上
router.get('/test', async (req, res) => {
const data = '我是服务器返回的数据'
// req.query为查询参数列表
const fn = req.query.callback
// 返回数据
res.send(`${fn}('${data}')`)
})
响应内容
Access-Control-Allow-Origin:*
即可开启cors允许跨域请求,使用通配符*表示允许所有不同域的源访问资源,也可单独设置指定允许的源域名postMessage(data,origin[, source])
data为发送的数据只能发送字符串信息,origin发送目标源,指定哪些窗口能接收到消息事件,如果origin设置为*则表示无限制,source为发送消息窗口的window对象引用,
----------------------------------------------
复制代码
window.name
属于全局属性,在html中的iframe加载新页面(可以是跨域),通过iframe设置的src指向的源中更改name的值,同时主页面中的name也随之更改,但是需要给iframe中的window设置为about:blank
或者同源页面即可
// 封装应该用于获取数据的函数
function foo(url, func) {
let isFirst = true
const ifr = document.createElement('iframe')
loadFunc = () => {
if (isFirst) {
// 设置为同源
ifr.contentWindow.location = 'about:blank'
isFirst = false
} else {
func(ifr.contentWindow.name)
ifr.contentWindow.close()
document.body.removeChild(ifr)
}
}
ifr.src = url
ifr.style.display = 'none'
document.body.appendChild(ifr)
// 加载之后的回调
ifr.onload = loadFunc
}
foo(`http://127.0.0.1:5501/name.html`, (data) => {
console.log(data) //
})
----------------------------------------------
const obj = { name: "iframe" }
// 修改name的值,必须为string类型
window.name = JSON.stringify(obj);
document.domain
的值对应当前页面的域名http://a.baidu.com
与http://b.baidu.com
这2个子域名之间才能使用domain跨域,一般用于子域名之间的跨域访问
----------------------------------------------
// nginx.conf配置
server {
listen 80; // 监听端口
server_name www.baidu.com; // 匹配来源
location / { //匹配路径
// 反向代理到http://127.0.0.1:3000
proxy_pass http://127.0.0.1:3000;
// 默认入口文件
index index.html index.htm index.jsp;
}
setTimeout(func|code, [delay], [arg1], [arg2], ...)
参数1为想要执行的函数或代码字符串,参数2为延迟执行时间,单位毫秒默认0,参数3及之后的参数为参数1为函数时传入的参数,调用之后会返回一个定时器idclearTimeout(id)
清除定时器来取消回调
也就是说setTimeout是在上一次回调执行之后才开启的定时
setInterval(func|code, [delay], [arg1], [arg2], ...)
,参数列表同setTimeout,参数2为每次循环时间
由于这2个api都属于异步宏任务,在执行的时候都会进入任务队列,如果队列前的任务执行时间较长,那么也会影响到定时器的执行时机
在浏览器中alert、confirm、prompt都会阻塞js主线程执行,直到弹窗消失,但是定时器还会继续执行;定时器并不能达到0延迟,最小延迟限制在4ms
requestAnimationFrame(callback)
的参数就是每次渲染前需要执行的动画更新函数,当浏览器将要重绘画面时就会执行这个回调函数,这个回调函数接受一个参数,即从当前页面加载之后到现在所经过的毫秒数
document.getElementById("btn").onclick = function () {}
----------------------------------------------
复制代码
addEventListener(eventName,callback,isCapturing)
方法为元素设置事件监听器,参数1为注册事件名不带on开头的string类型,参数2为触发事件的回调函数,接受一个事件对象参数,参数3为是否在捕获阶段触发,默认为falseremoveEventListener(eventName,callback,isCapturing)
方法移除指定事件名、回调、是否捕获的事件,匿名回调无法删除
- a
- b
- c
----------------------------------------------
- a
-
b
-
c
作者:*5102
链接:https://juejin.cn/post/6844904136161361933
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。