每天五道前端面试题(二)

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
  • 构造函数生成的实例对象aa__proto__指向构造函数A原型对象
  • A.prototype.__proto__ 指向内置对象,因为 A.prototype 是个对象,默认是由 Object 函数作为类创建的,而 Object.prototype 为内置对象
  • A.__proto__ 指向内置匿名函数,因为 A是个函数对象,默认由 Function 作为类创建
  • Function.prototypeFunction.__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) // 前端

但是该方法也是有局限性的:

  1. 会忽略 undefined
let a = {
    age: undefined,
    job: function () { },
    name: '张三'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) //{name: "张三"}
  1. 该对象也不能正常的序列化
let a = {
    age: undefined,
    job: function () { },
    name: '张三'
}
let b = JSON.parse(JSON.stringify(a))
console.log(JSON.stringify(a)) //{"name":"张三"} 并不能输出age
  1. 不能解决循环引用的对象
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))
  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值