深拷贝和原型原型链和web api 和 this指向等(中初级前端面事题)

Posted 暑假过期le

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深拷贝和原型原型链和web api 和 this指向等(中初级前端面事题)相关的知识,希望对你有一定的参考价值。

深拷贝

值类型的赋值就是深拷贝:变量赋值时,拷贝的不是内存地址,而是将数据完整的在内存中复制了一份

  const a = 10
  const b = a
  console.log(b);

在这里插入图片描述

浅拷贝

引用类型(null 对象 数组)的赋值操作都不是深拷贝:拷贝的是内存地址,最终两个变量指向的是同一个地址

const a = {
name: 'jj',
age: 20
}
const b = a
b.age = 18
console.log(a.age); // 18

在这里插入图片描述

深拷贝和浅拷贝的区别

  • 浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级
  • 浅拷贝有效性针对的是单一层级对象 [1,2,3]或者{a:1,b:2}
  • 深拷贝有效性针对的是单层或者多层级对象 [1,2,3]或者{a:1,b:2}或者[1,[1],{a:1}]或者{a:[1],b:{c:2}}

如何让引用类型深拷贝

简单的实现深拷贝

const p1 = {
    name: 'jj',
    age: 20
  }
  const deepClone = (obj) => {
    let result = {}
    for (let i in obj) {
      // console.log(obj[i]);
      result[i] = obj[i]
    }
    return result
  }
  const p2 = deepClone(p1)
  p2.age = 12
  console.log(p1, p2);

在这里插入图片描述

加入递归

const p1 = {
    name: 'ss',
    age: 20,
    address: {
      province: '河北省',
      city: '保定市',
      area: '莲池区',
      info: '河北软件职业技术学院'
    }
  }
  const deepClone = (obj) => {
    let result = {}
    for (let i in obj) {
      result[i] = obj[i]
    }
    return result
  }
  const p2 = deepClone(p1)
  p2.age = 2
  p2.address.city = '衡水'
  console.log(p1, p2);

输出结果

在这里插入图片描述

  • 对于name和age属性手机深拷贝

  • 对于address属性不是深拷贝

  • 深拷贝的核心是我没找到对象或数值中的值类型(age 和 name属性的值),然后进行赋值操作,就是深拷贝,而address属性是一个对象自然采用内存地址拷贝

  • 解决方案:加入递归,一致递归到值类型为止

  • 在deepClone函数中的循环改写为:

    result[i]=deepclone(obj[i])
    

完整代码

const deepClone = (obj) => {
    // 如果不是对象或者等于null的时候无需进行深拷贝
    if (typeof obj !== 'object' || obj == null) {
      return obj
    }
    //声明变量,用于存储拷贝的数据
    let result
    if (obj instanceof Array) {
      result = []
    } else {
      result = {}
    }
    for (let i in obj) {
      // console.log(deepClone(obj[i]));
      result[i] = deepClone(obj[i])
    }
    return result
  }
  const p1 = {
    name: 'ss',
    age: 20,
    address: {
      province: '河北省',
      city: '保定市',
      area: '莲池区',
      info: '河北软件职业技术学院'
    }
  }
  const p2 = deepClone(p1)
  p2.age = 2
  p2.address.city = '衡水'
  console.log(p1, p2);

原型和原型链

隐式原型和显示原型

  class People {
    constructor(name) {
      this.name = name
    }
    eat() {
      console.log('eat');
    }
  }
  class Student extends People {
    constructor(name, number) {
      super(name)
      this.number = number
    }
    sayHi() {
      console.log(`姓名:${this.name},学号:${this.number}`);
    }
  }
  const xialuo = new Student('夏洛特', '110')
  console.log(xialuo.__proto__ === Student.prototype);//true
  console.log(xialuo.__proto__);//对象的对象原型,也是隐式原型
  console.log(Student.prototype);//类的原型对象,也是显示原型

对象的隐式原型与类的显式原型是同一个对象,内存地址一致
在这里插入图片描述

原型关系

  • 每个class都有显示原型prototype
  • 每个实例都有隐式声明__proto__
  • 实例的__proto__指向对应的class的prototype

获取属性方法时

  const xialuo = new Student('夏洛特', '110')
  console.log(xialuo.name);//夏洛特
  xialuo.sayHi()
  xialuo.eat()
  • 获取属性或方法时,首先在对象的自身属性中查找如上面name就是对象的自身属性
  • 如果找不到,则自动取__proto__中查找,如上面的sayHi方法,在对象中并没有,所有取对象的隐式原型上查找

原型链

 hasownProperty 方法判断某个属性或者方法是否在当前隐式原型中

javascript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接(proto)。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向)。这种一级一级的链结构就称为原型链(prototype chain)。 当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止;到查找到达原型链的顶部(Object.prototype),仍然没有找到指定的属性,就会返回 undefined。

在这里插入图片描述

所有 class 都是从 Object 继承的,也就是说 Object 是其他类的原型

作用域

script标签的开始和结束标记之间,叫全局作用域

函数内部的是函数作用域

花括号之间的是块级作用域

自由变量:在当前作用域中没有,在上级找到的变量就是自由变量如果全局没有找到就回报 is not defiend

自由变量的查找就是在函数定义的地方,向上级作用域查找

闭包

在函数调用完之后作用域和变量都被清除

闭包在函数调用之后不会清除作用域和变量,只有调用内部函数的时候才会清除

闭包是一种现象 闭包可以访问其他作用域的变量 有两种情况:1.函数作为返回值 2.函数作为参数

this指向

this 是在函数执行时(调用时)确定的,而不是定义时,

在全局作用域中this是指 window

普通函数this指向

作为普通函数调用this值是window

apply、bind 和 call 方法可以改变普通函数中 this 的指向,传递的第一个参数是什么,则函数中的 this 就是什么

function fn () {
console.log(this);
}
fn.call({ x: 100 }) // {x:100}
const fn_res = fn.bind({ x: 100 })
fn_res() // {x:100}

定义在对象里面的函数(this指向)

定义在对象里面的函数this指向的是这个对象

const person = {
name: 'onlifes',
sayHi () {
console.log(this);
},
wait () {
setTimeout(function () {
console.log(this);
}, 0)
}
}
person.sayHi()
person.wait()

结果

{name: "onlifes", sayHi: ƒ, wait: ƒ}
Window

原因:因为 wait 方法中使用了 setTimeOut 函数,其中的函数永远被当作一个普通函数,所以 this=window

箭头函数(this指向)

箭头函数没有自己的this指向,箭头函数的this指向是父级作用域的this指向决定的

class类中方法的this(this指向)

class类中方法的this指向的是声明的对象实例

同步和异步

单线程和异步

JS 是单线程语言,只能同时做一件事,这时候就需要异步解决单线程的问题 异步是解决JS语言单线程瓶颈的一个解决方案

异步和同步的区别

异步是基于 JS 单线程特点而产生的

1.异步不会阻塞代码执行

同步会阻塞代码执行

2.同步就相当于是 当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当服务端做完了才返回到客户端。这样的话客户端需要一直等待。用户使用起来会有不友好。

异步就是,当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间,提高了效率。

存在就有其道理 异步虽然好 但是有些问题是要用同步用来解决,比如有些东西我们需要的是拿到返回的数据在进行操作的。这些是异步所无法解决的。

解决异步问题

使用Promise方法

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div class="info">

  </div>
</body>

</html>
<script>
  const url1 =
    'https://th.bing.com/th/id/R33674725d9ae34f86e3835ae30b20afe?rik=Pb3C9e5%2b%2b3a9Vw&riu=http%3a%2f%2fwww.desktx.com%2fd%2ffile%2fwallpaper%2fscenery%2f20180626%2f4c8157d07c14a30fd76f9bc110b1314e.jpg&ehk=9tpmnrrRNi0eBGq3CnhwvuU8PPmKuy1Yma0zL%2ba14T0%3d&risl=&pid=ImgRaw'
  const url2 = 'https://tse2-mm.cn.bing.net/th/id/OIP.T1-KOJxH7Dg0YqlcZiJ6vAHaHa?pid=ImgDet&rs=1'
  const url3 =
    'https://th.bing.com/th/id/Rc6c03edea530e9caa677c9d17f193a4d?rik=MBgpsjumbTD5eQ&riu=http%3a%2f%2fwww.desktx.com%2fd%2ffile%2fwallpaper%2fscenery%2f20170209%2fca186d97701674b996264b2d352894a7.jpg&ehk=HunG%2fPF7pUbpcS34cWpNvlS%2faoDPbcaTYL6LFFPQIIM%3d&risl=&pid=ImgRaw'

  function loadImg(url) {
    return new Promise((resolve, reject) => {
      const img = document.createElement('img')
      img.src = url
      img.onload = function () {
        resolve(img)
      }
    })
  }
  loadImg(url1)
    .then(img => {
      loadInfo(img)
      return loadImg(url2)
    }).then(img => {
      loadInfo(img)
      return loadImg(url3)
    }).then(img => {
      loadInfo(img)
    })

  function loadInfo(ele) {
    const sp = document.createElement('span')
    sp.innerHTML = ele.width
    document.querySelector('.info').appendChild(sp)
    document.body.appendChild(ele)
  }
</script>

用 await方法改造

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div class="info">

  </div>
</body>

</html>
<script>
  const url1 =
    'https://th.bing.com/th/id/R33674725d9ae34f86e3835ae30b20afe?rik=Pb3C9e5%2b%2b3a9Vw&riu=http%3a%2f%2fwww.desktx.com%2fd%2ffile%2fwallpaper%2fscenery%2f20180626%2f4c8157d07c14a30fd76f9bc110b1314e.jpg&ehk=9tpmnrrRNi0eBGq3CnhwvuU8PPmKuy1Yma0zL%2ba14T0%3d&risl=&pid=ImgRaw'
  const url2 = 'https://tse2-mm.cn.bing.net/th/id/OIP.T1-KOJxH7Dg0YqlcZiJ6vAHaHa?pid=ImgDet&rs=1'
  const url3 =
    'https://th.bing.com/th/id/Rc6c03edea530e9caa677c9d17f193a4d?rik=MBgpsjumbTD5eQ&riu=http%3a%2f%2fwww.desktx.com%2fd%2ffile%2fwallpaper%2fscenery%2f20170209%2fca186d97701674b996264b2d352894a7.jpg&ehk=HunG%2fPF7pUbpcS34cWpNvlS%2faoDPbcaTYL6LFFPQIIM%3d&risl=&pid=ImgRaw'

  function loadImg(url) {
    return new Promise((resolve, reject) => {
      const img = document.createElement('img')
      img.src = url
      img.onload = function () {
        resolve(img)
      }
    })
  }
  async function fn() {
    loadInfo(await loadImg(url1))
    loadInfo(await loadImg(url2))
    loadInfo(await loadImg(url3))
  }
  fn()

  function loadInfo(ele) {
    const sp = document.createElement('span')
    sp.innerHTML = ele.width
    document.querySelector('.info').appendChild(sp)
    document.body.appendChild(ele)

  }
</script>

web api

  • ECMA 和 W3C 是两个机构,但这两个机构的会员有很大重合
  • ECMA 规定 ECMAScript 的标准,W3C 规定了 Web API 的标准,也就是说 ECMA 负责 ECMAScript 脚本语言的研究和发布,W3C 定义了 ECMAScript 能做什么
  • ECMA 262 是基础,与 Web API 结合才能发挥作用

DOM操作

dom的本质是什么

我们都知道,DOM 是一棵树。梨树还是桃树?

首先要明白,通过 VS Code 编辑的 HTML 源代码,最终会被浏览器拿到 浏览器拿到源代码经过解析,构建成DOM树,浏览器可以识别并通过JS可以操作的这个DOM树 所以,要将源代码与 DOM 区别开来

  • 源代码是开发者编写的,由浏览器进行解析并渲染成页面,但渲染完成后,无法通过 JS 进行修改
  • DOM 存在于浏览器的内存中(我自己的理解),通过 JS 可以修改,修改后,浏览器会根据修改后 的 DOM 树重新渲染页面

DOM节点操作

  • property :修改对象属性,不会体现到 HTML 结构中
  • attribute:修改 html 结构,会改变 HTML 结构
  • 两者都可能引起 DOM 结构重新渲染
// property 方式:
element.style.color='red'
element.className='blue'
// atribute 方式
element.setAttribute('class','blue')
element.getAttribute('href')

结论:尽量使用 property 的方式,万不得已,再使用 attribute 方式

DOM 结构操作

  • 新增,插入节点
  • 获取子元素列表,获取父元素
  • 删除元素

新增节点

document.createElement('p')

插入节点

父节点.appendChild('p')

获取子元素列表

父元素.childNodes

获取父元素列表

子元素.parentNode

删除子元素

父元素.removeChild(子元素)

DOM性能优化

  • 对DOM查询做缓存
  • 将频繁的DOM操作作为一次性操作

用代码片段进行一次性的操作

  body
  <ul id="list"></ul>
  
  //js
  const listNode = document.getElementById("list")
  // 创建一个文档片段,此时还没有插入到DOM树中
  const frag = document.createDocumentFragment()
  // 执行插入
  for (let x = 0; x < 10; x++) {
    const li = document.createElement('li')
    li.innerHTML = "list item " + x
    frag.appendChild(li)
  }
  // 完成之后,再插入到DOM树中
  listNode.appendChild(frag)

BOM操作

BOM:浏览器对象模型

1.如何识别浏览器类型

用Navigator对象的

属性说明
appCodeName返回浏览器的代码名
appName返回浏览器的名称
appVersion返回浏览器的平台和版本信息
cookieEnabled返回指明浏览器中是否启用 cookie 的布尔值
platform返回运行浏览器的操作系统平台
userAgent返回由客户机发送服务器的user-agent 头部的值

在这里插入图片描述

分析拆解 URI 各个部分

在这里插入图片描述

Screen 对象

Screen 对象包含有关客户端显示屏幕的信息。

属性描述
availHeight返回显示屏幕的高度 (除 Windows 任务栏之外)。
availWidth返回显示屏幕的宽度 (除 Windows 任务栏之外)。
bufferDepth设置或返回调色板的比特深度。
colorDepth返回目标设备或缓冲器上的调色板的比特深度。
deviceXDPI返回显示屏幕的每英寸水平点数。
deviceYDPI返回显示屏幕的每英寸垂直点数。
fontSmoothingEnabled返回用户是否在显示控制面板中启用了字体平滑。
height返回显示屏幕的高度。
logicalXDPI返回显示屏幕每英寸的水平方向的常规点数。
logicalYDPI返回显示屏幕每英寸的垂直方向的常规点数。
pixelDepth返回显示屏幕的颜色分辨率(比特每像素)。
updateInterval设置或返回屏幕的刷新率。
width返回显示器屏幕的宽度。

History 对象

History 对象包含用户(在浏览器窗口中)访问过的 URL

History 对象属性

属性描述
length返回浏览器历史列表中的 URL 数量。

History 对象方法

方法描述
back()加载 history 列表中的前一个 URL。
forward()加载 history 列表中的下一个 URL。
go()加载 history 列表中的某个具体页面。

事件

事件绑定

1.嵌入式

<button onclick="open()">按钮</button>

<script>
function open(){
	alert(1)
}
</script>

2.事件监听

<button id="btn">按钮</button>
<script>
document.getElementById('btn').addEventListener('click',function(){
	alert(1)
})
</script>

3.直接绑定

<button id="btn">按钮</button>
<script>
document.getElementById('btn').onclick = function(){
	alert(1)
}
</script>

addEventListener和on的区别

on:如果事件相同就会覆盖前一个但是有时我们又需要执行多个相同的事件 on就完成不了我们想要的效果

addEventListener:可以多次绑定同一个事件并且不会覆盖上一个事件。

事件代理

**事件代理:**将事件代理到父容器上,这样修改子元素,父容器都会监听到,从而执行对应的操作,若是将事件绑定到子元素上,假设新增了子元素,又得重新给子元素绑定事件,是非常繁琐的。

事件冒泡,事件捕获

<div>
  <p>元素</p>
</div>

事件捕获
当你使用事件捕获时,父级元素先触发,子级元素后触发,即div先触发,p后触发。
事件冒泡
当你使用事件冒泡时,子级元素先触发,父级元素后触发,即p先触发,div后触发。

组织冒泡的方法

第一种:event.stopPropagation();

第二种:return false;

第三种:event.preventDefault();

以上是关于深拷贝和原型原型链和web api 和 this指向等(中初级前端面事题)的主要内容,如果未能解决你的问题,请参考以下文章

深拷贝和原型原型链和web api 和 this指向等(中初级前端面事题)持续更新中,建议收藏

深拷贝和原型原型链和web api 和 this指向等(中初级前端面事题)持续更新中,建议收藏

原型模式案例详解-浅拷贝与深拷贝

js ECMAscript原型,继承,this,bind,闭包,浅/深拷贝,正则表达式

原型链和作用域链

JS中有关对象的继承以及实例化浅拷贝深拷贝的奥秘