JS进阶
Part1、
1.闭包
简单理解:闭包 = 内层函数 + 外层函数的变量
作用:封闭数据,实现数据私有,外部也可以访问函数内部的变量
问题:可能导致内存泄漏
2.变量提升
JS允许变量先被访问后被声明,但只有var声明可以这样(变量被先访问时,值为undefined),let和const不行(它们会报语法错误),且只能出现在同一作用域。实际开发不推荐。
3.函数提升
函数声明前即可调用,也是同一作用域,但函数表达式不存在这种情况
4.函数传参(参数个数不定)
(1)arguments 动态参数:是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
(2)剩余参数:是个真数组,用于获取多余的实参,语法符号“ ... ”,开发推荐
5.展开运算符
展开运算符(…),将一个数组进行展开
let arr1 = [1, 2, 3]
let arr2 = [1, 2, 3]
console.log(Math.min(...arr1)) // 1
console.log(Math.max(...arr2)) // 3
console.log([...arr1,...arr2]) // [1,2,3,1,2,3]
6.箭头函数
6.1 语法
(1)替换函数表达式中匿名函数
(2)只有一个参数可以省略小括号
(3)如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值
(4)加括号的函数体返回对象字面量表达式
6.2 注意
(1)箭头函数没有arguments参数,只有剩余参数
(2)箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this
7.解构赋值
7.1 数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
let a = 1
let b = 2;
[a,b] = [b,a]
console.log(a) // 2
console.log(b) // 1
注意:
在以数组开头的,特别是前面有语句的要加分号,立即执行函数前或后也要加分号;
变量的数量大于单元值数量时,多余的变量将被赋值为undefined;
const [a,b,c] = [1,2]
console.log(b) // 2
console.log(c) // undefined
变量的数量小于单元值数量时,剩余参数... 获取剩余单元值,但只能置于最末位
const [a, ...others] = [1,2,2,3,3]
console.log(a) // 1
console.log(others) // [2,2,3,3]
进阶用法
(1)防止有undefined传递单元值的情况,可以设置默认值:
(2)按需导入,忽略某些返回值
(3)支持多维数组的结构
7.2 对象解构
(1)将对象属性和方法快速批量赋值给一系列变量
const pig = { name: '佩奇',age: 6 }
const {name,age}=pig
console.log(name,age) // 佩奇 6
(2)修改对象属性名,将对象属性和方法快速批量赋值给一系列变量
(3)数组对象解构
(4)多级对象解构
8. filter方法
currentValue 必须写, index 可选
Part2、
1.创建对象之构造函数
创建对象的三种方式:
- 直接创建
- new 一个Object对象
- 构造函数:规范【它们的命名以大写字母开头,只能由 "new" 操作符来执行,不用写return】
2.实例成员&静态成员
实例成员就是this相关的属性和方法
静态成员是构造函数的属性和方法
3.内置构造函数
含有构造函数的数据类型(和Java有点像)
- 引用类型:Object,Array,RegExp,Date 等
- 包装类型:String,Number,Boolean 等
3.1
Object
三个静态方法
(1)Object.keys 静态方法获取对象中所有属性(键),返回的是一个数组
(2)Object.values 静态方法获取对象中所有属性值,返回的是一个数组
(3)Object. assign 静态方法常用于对象拷贝,或给对象添加属性
3.2
Array
reduce
Part3、
原型
1 概述
- JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
- 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
- 构造函数和原型对象中的this 都指向 实例化的对象
- 找到实例对象(若一个实例对象为obj)的原型对象的方法:
- 推荐:Object.getPrototypeOf(obj)
- 不推荐:obj.__proto__
2 constructor 属性
每个原型对象里面都有个constructor 属性(constructor 构造函数)
作用:该属性指向该原型对象的构造函数
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
function People (name){
this.name = name
}
//原型对象的构造函数指向People
console.log(People.prototype.constructor)
///原型对象的构造函数指向People
People.prototype.run = function () {
console.log('跑')
}
console.log(People.prototype.constructor)
//原型对象的构造函数指向Object
People.prototype = {
eat: function () { console.log('吃饭') },
sleep: function () { console.log('睡觉') }
}
console.log(People.prototype.constructor)
//将原型对象的构造函数指向People
People.prototype = {
constructor: People,
eat: function () { console.log('吃饭') },
sleep: function () { console.log('睡觉') }
}
console.log(People.prototype.constructor) // 指向People
可以看出来当对象形式赋值,constructor属性指定为构造函数,原型对象的constructor属性才会指向构造函数
3 对象原型
实例对象都会有一个属性_proto_ 指向构造函数的 prototype 原型对象,之所以我们实例对象可以使用构造函数 prototype原型对象的属性和方法,就是因为对象有 _proto_ 原型的存在。
注意:
- __proto__ 是JS非标准属性
- [[prototype]]和__proto__意义相同
- 用来表明当前实例对象指向哪个原型对象prototype
- __proto__ 对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
个人理解:
构造函数的prototype指向原型对象
实例对象继承构造函数属性和方法,实例对象的__proto__属性指向原型对象,由此继承原型对象的方法
原型对象的constructor属性指向构造函数(若用对象函数方式给prototype赋值,constructor属性得指定为构造函数)
实例对象的constructor属性指向构造函数
原型对象和构造函数的this都指向实例对象
4. 原型继承
function People(){
this.name = name
this.eat = function(){}
}
function Man() {}
Man.prototype = People
//注意让原型里面的constructor重新指回Man---------这种写法固定
Man.prototype.constructor = Man
//Man添加smoking方法
Man.prototype.smoking=function(){
console.log("抽烟")
}
function Woman(){
this.baby = function(){}
}
//Woman此时也继承People,但也有了smoking方法,我们并不想这样
Woman.prototype = People
//注意让原型里面的constructor重新指回Woman-----------固定写法
Woman.prototype.constructor = Woman
new Woman().smoking()
原因:Man 和 Woman 的prototype对象都指向同一个对象People的地址
解决:分别构造People对象
Man.prototype = new People()
Woman.prototype = new People() // Woman没有smoking方法了
5. 原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
function Star(){}
const ldh = new Star()
// ldh对象的__protype__属性指向Star()的prototype对象,Star()的prototype对象的constructor属性指向Star()构造函数
//Star()的prototype对象的__proto__属性指向Object的prototype对象,其prototype的constructor属性指向Object()构造函数
//Object的prototype对象的_proto__属性指向null
原型链-查找规则:
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。 ② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象) ③ 如果还没有就查找原型对象的原型(Object的原型对象) ④ 依此类推一直找到 Object 为止(null) ⑤ __proto__(对象原1型)的意义就在于为对象成员查找机制提供一个方向,或者说一条路线 ⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
Part4、
1. 深浅拷贝
1.1 浅拷贝
-
浅拷贝和深拷贝只针对引用类型
-
浅拷贝:拷贝的是地址
-
API :
-
拷贝数组(使用同拷贝对象API):
- Array.prototype.concat()
- [...arr]
-
拷贝对象:
-
Object.assgin()
-
展开运算符 {...obj}
-
const obj = { name:'green'} const o1 = {} Object.assign(o1,obj) //o1拷贝obj const o2 = {...obj} //o2拷贝obj
-
-
-
问题:拷贝的是单层对象,没问题,如果有多层就有问题,多层意味着引用数据类型,引用数据类型拷贝的是地址
let obj1 = { name: 'zs', age: 20, height: 180, info: { sex: '男', weight: 70 } } let obj2 = {} // 写了这一行,已经表示 obj2 和 obj1 是两个不同的对象了 // 循环遍历 obj1,循环一次,取obj1里面的一个属性,然后给obj2加上 for (let key in obj1) { obj2[key] = obj1[key] } // 尝试修改其中一个对象 obj1.age = 10000 // 尝试修改对象的一个引用类型的属性 obj1.info.sex = '女' //由下图看出引用数据类型info里面的属性两个对象都改了,而简单数据类型age并未修改 console.log(obj1) console.log(obj2)
1.1 深拷贝
(1)深拷贝:拷贝的是对象,不是地址
(2)常用方法
-
递归实现深拷贝
function deepCopy(newObj,oldObj){ for(let k in oldObj){ if (oldObj[k] instanceof Object){ newObj[k] = [] deepCopy(newObj[k], oldObj[k]) } else if (oldObj[k] instanceof Array){ newObj[k] = {} deepCopy(newObj[k], oldObj[k]) } else { newObj[k] = oldObj[k] } } } -
lodash/cloneDeep
-
-
通过JSON.stringify()实现
2. 异常处理
-
throw 抛异常:
throw new Error('参数不能为空') -
try / catch 捕获异常
-
debugger 在代码中写debugger关键字,浏览器则会开启debug断点模式
-
3. 处理this
3.1 this指向
-
普通函数this指向:谁调用 this 的值指向谁普通函数严格模式下指向:undefined
-
箭头函数:箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !
1.箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的 2.箭头函数中的this引用的就是最近作用域中的this 3.向外层作用域中,一层一层查找this,直到有this的定义
适用:需要使用上层this的地方
不适用:构造函数,原型函数,dom事件函数等等
3.2 改变this
- call() 使用 call 方法调用函数,同时指定被调用函数中 this 的值
使用:函数.call(thisArg, arg1, arg2, ...) thisArg:在 fun 函数运行时指定的 this 值,arg1,arg2:传递的其他参数
2.apply() 使用 apply 方法调用函数,同时指定被调用函数中 this 的值
使用:函数.apply(thisArg, [argsArray]) argsArray:传递的值,必须包含在数组里面
3.bind()-重点 bind() 方法不会调用函数。但是能改变函数内部this 指向
使用:函数.bind(thisArg, arg1, arg2, ...)
-
返回由指定的 this 值和初始化参数改造的 原函数拷贝 (新函数)
-
因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的
this指向.
4. 性能优化
4.1 节流 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
开发使用场景:
假如一张轮播图完成切换需要300ms, 不加节流效果,快速点击,则嗖嗖嗖的切换
加上节流效果, 不管快速点击多少次, 300ms时间内,只能切换一张图片。
4.2 防抖 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
开发使用场景- 搜索框防抖:
假设输入就可以发送请求,但是不能每次输入都去发送请求,输入比较快发送请求会比较多我们设定一个时间,假如300ms, 当输入第一个字符时候,300ms后发送请求,但是在200ms的时候又输入了一个字符,则需要再等300ms 后发送请求