1.原型和原型链
原型
在javascript中每个对象拥有一个原型对象
试图访问一个对象的属性时,它不仅仅在该对象上查找,还会在该对象的原型上查找,以及该对象的原型的原型,依次层层向上查找,直到找到一个与之匹配的属性或到达原型链的末尾
准确来说这些属性及方法定义在Object
的构造器函数(constructor functions
)之上的prototype
属性上
function a(){}
console.log(a.prototype)
控制台输如下图:
上面输出的这个对象就是所说的原型对象
原型对象有一个自有属性constructor,这个属性指向该函数,如下图关系展示
原型链
原型对象也可能拥有原型,从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链
// 第一步 构造函数
function A(name) {
this.name = name;
this.age = 18;
this.sayName = function () {
console.log(this.name);
}
}
// 第二步 创建实例对象
var a = new A('a')
console.log(A.prototype ) //{constructor: ƒ} 原型对象
console.log(a) //A {name: 'a', age: 18, sayName: ƒ}
// 观察实例对象输出可得
console.log(A.prototype === a.__proto__) //true
从以上代码分析:
- 构造函数
A
存在原型对象A.prototype
- 构造函数生成的实例对象
a
,a
的__proto__
指向构造函数A
原型对象 A.prototype.__proto__
指向内置对象,因为A.prototype
是个对象,默认是由Object
函数作为类创建的,而Object.prototype
为内置对象A.__proto__
指向内置匿名函数,因为A
是个函数对象,默认由Function
作为类创建Function.prototype
和Function.__proto__
同时指向内置匿名函数,这样原型链的终点就是null
闭包
闭包的定义很简单: 函数 A 返回了⼀个函数 B, 并且函数 B 中使用了函数 A的变量, 函数 B 就被称为闭包。
如下
function A() {
var a = '函数a的变量'
function B() {
// 使用函数a的变量
console.log(a) //函数a的变量
}
return B()
}
A()
闭包的使用场景以及解决问题:
- 循环中使用闭包解决 var 定义函数的问题
for(var i=1;i<=5;i++){
setTimeout(timer =>{
console.log(i)
},i*1000)
}
- 首先因为
setTimeout
是个异步函数,所有会先把循环全部执行完毕, 这时候i
就是6 了,所以会输出⼀堆 6 。 - 解决办法两种, 第⼀种使用闭包
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
// 这是自调用函数 相当于把i传给了 匿名函数
}
- 第⼆种就是使用 setTimeout 的第三个参数
for (var i = 1; i <= 5; i++) {
setTimeout(function timer(j) {
console.log(j);
}, i * 1000, i);
// setTimeout第三个参数相当于 timer函数的参数
}
- 还有一种简单的办法将
var
变成let
也可解决问题 应为let
会形成块级作用域
for(let i=1;i<=5;i++){
setTimeout(timer =>{
console.log(i)
},i*1000)
}
深浅拷贝
如果给⼀个变量赋值⼀个对象,那么两者的值会是同⼀个引用, 其中⼀方改变, 另⼀方也会相应改变。
let a = {
age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2
在开发中我们不希望出现这样的问题, 就可以使用浅拷贝
来解决这个问题
浅拷贝
- 首先可以通过
Object.assign
来解决这个问题
let a = {
age: 1
}
let b = Object.assign({}, a)
b.age = 2
console.log(a.age) // 1
console.log(b.age) // 2
- 也可以通过展开运算符
...
来解决
let a = {
age: 1
}
let b = {...a}
b.age = 2
console.log(a.age) // 1
console.log(b.age) // 2
深拷贝
let a = {
age: 1,
job: {
first: '前端'
}
}
let b = {...a}
a.job.first = 'native'
console.log(b.job.first) // native
浅拷贝只解决了第⼀层的问题, 如果接下去的值中还有对象的话,那么就又回到刚开始的话题了,两者享有相同的引用 。要解决这个问题,我们需要引入深拷贝
- 可以通过
JSON.parse(JSON.stringify(object))
来解决
let a = {
age: 1,
job: {
first: '前端'
}
}
let b = JSON.parse(JSON.stringify(a))
a.job.first = 'native'
console.log(a.job.first) // native
console.log(b.job.first) // 前端
但是该方法也是有局限性的:
- 会忽略
undefined
let a = {
age: undefined,
job: function () { },
name: '张三'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) //{name: "张三"}
- 该对象也不能正常的序列化
let a = {
age: undefined,
job: function () { },
name: '张三'
}
let b = JSON.parse(JSON.stringify(a))
console.log(JSON.stringify(a)) //{"name":"张三"} 并不能输出age
- 不能解决循环引用的对象
let obj = {
a: 1,
b: {
c: 2,
d: 3,
},
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj) // 报错
但是在通常情况下, 复杂数据都是可以序列化的,所以这个函数可以解决大部分问题, 并且该函数是内置函数中处理深拷贝性能最快的 。当然如果你的数据中含有以上三种情况, 可以使用
lodash
工具库 的深拷贝函数。
防抖
在日常开发中 在滚动事件中需要做个复杂计算或者实现防止一个按钮的二次点击操作等 可以用防抖来实现
防抖和节流的作用都是防止函数多次调用 。区别在于,假设⼀个用户⼀直触发这个函数,且每次触发函数的间隔小于 wait
, 防抖的情况下只会调用⼀次, 而节流的 情况会每隔⼀定时间 ( 参数 wait
) 调用函数
let btn = document.querySelector('button')
function debounce(fn, wait) {
let timer = null
return function (...args) {
if (timer) {
clearTimeout(timer)
timer = null
}
timer = setTimeout(() => {
fn.apply(this, args)
clearTimeout(timer)
timer = null
}, wait)
}
}
btn.addEventListener('click', debounce((e) => {
console.log(e.target)
}, 2000))
节流
防抖动和节流本质是不⼀样的 。防抖动是将多次执行变为最后⼀次执行, 节流是将多次执行变成每隔⼀段时间执行
let btn = document.querySelector('button')
function throttle(fn, wait) {
let flag = true
return function (...args) {
if (!flag) {
return
}
flag = false
setTimeout(() => {
fn.apply(this, args)
flag = true
}, wait)
}
}
btn.addEventListener('click', throttle((e) => {
console.log(1)
}, 1000))