前端面试集合(会更新)

121 阅读10分钟

typeof能判断哪些类型

undefined  string  number boolean symbol funciton 
不能识别的一律是object
typeof console.log   // function
typeof function(){}  // function
// 能识别应用类型(不能再继续识别)
typeof null // object
typeof ['a','b'] // object
typeof {x:100} // object

深拷贝手写

function deepClone(obj = {}) {
  if (typeof obj !== "object" || typeof obj == null) {
    // obj是null,或者不是对象和数组直接返回
    return obj;
  }
  // 初始化返回结果
  let result;
  if (obj instanceof Array) {
    result = [];
  } else {
    result = {};
  }
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key]);
    }
  }
  return result;
}

字符串拼接

const a = 100+10
const b = 100 + '10'
const c = true + '10'

==运算符

100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true

!!

// 以下是falsely变量,除此之外都是truly变量
​
!! 0 === false
!! NaN === false
!! '' === false
!! null === false
!! undefined === false
!! false === false

原型和原型链

class

  • constructor
  • 属性
  • 方法

如何准确判断一个变量是数组

  • 使用instanceof(原型链)

class的原型本质

  • 原型和原型链的图示
  • 属性和方法的执行规则

instanceof

类型判断

xiahu instanceof Student // true

xiahu instanceof People // true

xiahu instanceof Object // true

[] instanceof Array // true

[ ] instanceof Object // true

{} instanceof Object // true

原型

隐式原型和显示原型
console.log(xialuo.__proto__)  //person
console.log(Student.prototype) //person
console.log(xialuo.__proto__ === Student.prototype) //person

基于原型的执行规则

1、获取属性xialuo.name 或者执行方法xialuo.sayhi()
2、先在自身属性和方法寻找
3、如果找不到则自动去__proto__中查找

原型关系

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

手写jquery考虑插件和扩展性

class jQuery {
  constructor(selector) {
    const result = document.querySelectorAll(selector);
    console.log("result", result);
    const length = result.length;
    for (let i = 0; i < length; i++) {
      this[i] = result[i];
    }
    this.length = length;
  }
  get(index) {
    return this[index];
  }
  each(fn) {
    for (let i = 0; i < this.length; i++) {
      const element = this[i];
      fn(element);
    }
  }
  on(type, fn) {
    return this.each((element) => {
      element.addEventListener(type, fn, false);
    });
  }
}
​
// 插件
jQuery.prototype.dialog = function () {};
​
// "造轮子"
class myJquery extends jQuery {
  constructor(selector) {
    super(selector);
  }
  // ...jQuery. 扩展自己的方法
}
​
  • 插件也就是只原型上进行扩展
  • 造轮子也就是扩展就是对他的继承

闭包

定义和总结

  • 一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
  • 从广义的角度来说:JavaScript中的函数都是闭包;
  • 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;
  • 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域;

闭包的内存泄露

  • 那么我们为什么经常会说闭包是有内存泄露的呢?
 1、在上面的案例中,如果后续我们不再使用add10函数了,那么该函数对象应该要被销毁掉,并且其引用着的父作用域AO也应该被销毁掉;
 2、但是目前因为在全局作用域下add10变量对0xb00的函数对象有引用,而0xb00的作用域中AO(0x200)有引用,所以最终会造成这些内存都是无法被释放的;
 3、所以我们经常说的闭包会造成内存泄露,其实就是刚才的引用链中的所有对象都是无法释放的;
  • 那么,怎么解决这个问题呢?
 1、因为当将设置为null时,就不再对函数对象0xb00有引用,那么对应的AO对象0x200也就不可达了;
 2、在GC的下一次检测中,它们就会被销毁掉;

作用域

  • 全局作用域
  • 函数作用域
  • 块级作用域

this的不同应用场景

  • 当做函数被调用
  • 使用call apply bind
  • 作为对象方法调用
  • 在class的方法中调用
  • 箭头函数

手写bind函数

function fn1(a, b, c) {
  console.log("this", this);
  console.log(a, b, c);
  return "this is fn1";
}
// call() 和 apply() 之间的区别
// 不同之处是:
// call() 方法分别接受参数。
// apply() 方法接受数组形式的参数。
// 如果要使用数组而不是参数列表,则 apply() 方法非常方便。
const fn2 = fn1.bind({ x: 100 }, 100, 200);
const res = fn2;
// fn1.__proto__ === Function.prototype // true
// 由此可得
// 模拟bind
Function.prototype.bind1 = function () {
  // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
  // 将参数解析为数组
  const args = Array.prototype.slice.call(arguments);
​
  // 获取 this(数组第一项)也就是需要绑定的this
  const t = args.shift();
​
  // 之前的this 先放一个变量里去
  const self = this;
  return function () {
    return self.apply(t, args);
  };
};
​

从js基础知识到JS web api

  • JS基础知识,规定语法(ECMA 262标准)
  • JS Web API,网页操作的API(w3c标准)
  • 前者是后者的基础,两者集合才能真正实际应用

JS WEB API

  • DOM
  • BOM
  • 事件绑定
  • ajax
  • 存储

DOM

DOM本质

DOM本质就是一棵树

DOM节点的property

const pList = document.querySelectorAll("p")
const p = pList[0]
console.log(p.style.width)
p.style.width = '100px'
p.className = 'p1'
console.log(p.nodeName)
console.log(p.nodeType)
​
//property 所以不是api的名字他是一种形式

DOM节点的attribute

const pList = document.querySelectorAll("p")
pList.getAttribute("className")
pList.setAttribute("data-name","zx")
​
// 

property和attribute

  • property:修改对象属性,不会体现到html结构中
  • attribute:修改html属性,会改变html结构
  • 两者都有可能引起dom重新渲染

DOM性能

  • DOM操作非常“昂贵”避免频繁的DOM操作
  • 对DOM查询做缓存
  • 将频繁操作改为一次操作

image-20230321153558713

image-20230321153810606

事件代理

  • 代码简洁
  • 减少浏览器内存占用
  • 但是,不要滥用(比如明明就一个点击事件还要去事件代理)

描述下事件冒泡的流程

  • 基于DOM树形结构
  • 事件会顺着触发元素向上冒泡
  • 应用场景:代理

无线下拉图片列表,如何监听每个图片的点击

  • 事件代理
  • 用e.target获取触发元素
  • 用matches来判断是否是触发元素的
function bindEvent(elem, type, selector, fn) {
    if (fn == null) {
        fn = selector
        selector = null
    }
    elem.addEventListener(type, event => {
        const target = event.target
        if (selector) {
            // 代理绑定
            if (target.matches(selector)) {
                fn.call(target, event)
            }
        } else {
            // 普通绑定
            fn.call(target, event)
        }
    })
}
​
// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
    // console.log(event.target) // 获取触发的元素
    event.preventDefault() // 阻止默认行为
    alert(this.innerHTML)
})
​
// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
    event.preventDefault()
    alert(this.innerHTML)
})
​
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>事件 演示</title>
        <style>
            div {
                border: 1px solid #ccc;
                margin: 10px 0;
                padding: 0 10px;
            }
        </style>
    </head>
    <body>
        <button id="btn1">一个按钮</button>
​
        <!-- <div id="div1">
            <p id="p1">激活</p>
            <p id="p2">取消</p>
            <p id="p3">取消</p>
            <p id="p4">取消</p>
        </div>
        <div id="div2">
            <p id="p5">取消</p>
            <p id="p6">取消</p>
        </div> -->
​
        <div id="div3">
            <a href="#">a1</a><br>
            <a href="#">a2</a><br>
            <a href="#">a3</a><br>
            <a href="#">a4</a><br>
            <button>加载更多...</button>
        </div>
​
        <script src="./event.js"></script>
    </body>
</html>

ajax

XMLHttpRequest

// get 请求
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api", false);
xhr.onreadystatechange = function () {
// 这里的函数异步执行,可参考之前的js基础中的异步模块
if (xhr.readyState == 4) {
if (xhr.status == 200) {
 console.log(xhr.responseText);
}
}
};
xhr.send(null);
const xhr = new XMLHttpRequest();
xhr.open("POST", "./data.json", false);
xhr.onreadystatechange = function () {
// 这里的函数异步执行,可参考之前的js基础中的异步模块
if (xhr.readyState == 4) {
if (xhr.status == 200) {
 console.log(xhr.responseText);
} else {
 console.log(xhr.responseText);
}
}
};
​
const postData = {
name: "zx",
password: "xxx",
};
xhr.send(JSON.stringify(postData));

xhr.readyState

  • 0 - unset 尚未调用open方法
  • 1 - opened open 方法已被调用
  • 2 - HEADERS_RECEIVED send 方法已被调用,header已被接收
  • 3 - loading下载中,responseText已有部分内容
  • 4 - done 下载完成

JSONP和cors

JSONP的实现主要是<script></script>

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 <meta http-equiv="X-UA-Compatible" content="ie=edge" />
 <title>jsonp 演示</title>
</head>
<body>
 <p>一段文字 1</p><script>
   window.abc = function (data) {
     console.log(data);
   };
 </script>
 <script src="http://localhost:8002/jsonp.js?username=xxx&callback=abc"></script>
</body>
</html>
abc(
 { name: 'xxx' }
)

存储

描述cookie localStorage sessionStorage区别

  • cookie

    • 本身用于浏览器和server通讯

    • 被“借用”到本地存储来

    • 可用document.cookie来修改

    • 缺点

      • 存储大小 最大4kb
      • http请求时需要发送到服务端,增加请求数据量
      • 只能用document.cookie="..."来修改,太简陋了
  • localStorage和sessionStorage

    • h5专门为存储设计的,最大可存5m

    • API简单易用 setItem getItem

    • 不会随着http请求被发送出去

    • 区别

      • localStorage数据会永久存储,除非你代码或者手动删除
      • sessionStorage 数据只存在于当前会话,浏览器关闭则清空

BOM

navigator 和 screen

const ua = navigator.userAgent
const isChrome = ua.indexOf('Chrome')
​
​
screen.width
screen.height

location和history

https://www.bilibili.com/video/BV1W24y1b7n4/?spm_id_from=333.1007.tianma.2-1-4.click&vd_source=462b5b0d167594bb2782aad6421bba87

ocation.href // 'www.bilibili.com/'

location.protocol // 'https:'

location.hostname // 'www.bilibili.com'

location.pathname // '/video/BV1W24y1b7n4/'

location.search // '?spm_id_from=333.1007.tianma.2-1-4.click&vd_source=462b5b0d167594bb2782aad6421bba87'

history.back

history.forward

H5抓包

  • 移动端h5页,查看网络请求,需要用工具抓包
  • windows一般用fiddler
  • masc os一般用charles

webpack和babel

  • ES6模块化,浏览器不支持

  • ES6语法,浏览器并不完全支持

  • 压缩代码,整合代码,以让网页加载更快

  • webpack搭建

    npm init
    npm i webpack webpack-cli -D
    npm i html-webpack-plugin -D
    npm i webpack-dev-server -D
    
  • 配置babel

    1、npm install @babel/core @babel/preset-env babel-loader -D
    2、创建 .babelrc
    {
      "presets": ["@babel/preset-env"]
    }
    3、配置webpack.config,js的module
          {
            test: /.js$/,
            loader: ["babel-loader"],
            include: path.join(__dirname, "src"),
            exclude: /node-modules/,
          },
    
  • 如何配置webpack生产环境

    const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    module.exports = {
      mode: "production", // development是没有压缩的  production是压缩的
      entry: "./src/index.js",
      output: {
        path: path.resolve(__dirname, "./dist"),
        filename: "bundle.[contenthash].js",
      },
      resolve: {
        extensions: [".ts", ".js"],
      },
      module: {
        rules: [
          {
            test: /.js$/,
            loader: "babel-loader",
            include: path.join(__dirname, "src"),
            exclude: /node_modules/,
          },
          {
            test: /.ts$/,
            loader: "ts-loader",
          },
        ],
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: "./index.html",
          // filename: "index.html",
        }),
      ],
    };
    ​
    

网页url加载

  • DNS解析:域名 -> IP地址
  • 浏览器根据IP地址向服务器发起http请求
  • 服务器处理Http请求,并返回给浏览器

网页渲染过程

  • 根据HTML代码生成 DOM tree
  • 根据css代码生成CSSOM
  • 将DOM Tree 和CSSOM整合形成Render Tree
  • 根据Render tress 渲染页面
  • 遇到<script>则暂停渲染,优先加载并执行js代码,完成在继续(js和渲染是共享同一个进程的,因为js可能会改变DOM)
  • 直至把RenderTree渲染完成

css为什么放到header中

首先整个页面展示给用户会经过html 的解析与渲染过程。如果将css放在尾部,html的内容可以第一时间显示出来,但是会阻塞html行内css的渲染。

浏览器的这个策略其实很明智的,想象一下,如果没有这个策略,页面首先会呈现出一个行内css样式,待CSS下载完之后又突然变了一个模样。用户体验可谓极差,而且渲染是有成本的。

如果将css放在头部,css的下载解析是可以和html的解析同步进行的,放到尾部,要花费额外时间来解析CSS,并且浏览器会先渲染出一个没有样式的页面,等CSS加载完后会再渲染成一个有样式的页面,页面会出现明显的闪动的现象。

JS为什么要放到body之后

js本身是会阻塞渲染,js和渲染是共享同一个进程的,因为js可能会改变DOM,等把该渲染出来的渲染,再去加载js

window.onload和DOMContentLoaded

window.addEventListener('load',function(){
    // 页面的全部资源加载完成才会执行,包括图片、资源等
})
document.addEventListener('DOMContentLoaded',function(){
    // DOM 渲染完成即可执行,此时图片,视频还没可能没有加载完
})

性能优化

  • 多使用内存,缓存或其他的方法

  • 减少cpu计算量,减少网络加载耗时

  • 适用于所有编程的性能优化------ 空间换时间

  • 让加载更快

    • 减少资源体积:压缩代码
    • webpack:打包方式:production
  • 资源合并(多次变少次请求)

  • 缓存 ---> webpack

    • 静态资源加hash后缀,根据文件内容计算hash

    • 文件内容不变,则hash不变,则url不变

      • url和文件不变,则会自动触发http触发缓存机制,返回304
  • ssr

    • 服务端渲染:将网页和数据一起加载,一起渲染
    • 非SSR(前后端分离):先加载网页,再加载数据,再渲染数据
    • 早先的jsp asp php现在的vue react
  • 懒加载(图片)

  • 缓存DOM查询

    • 能一次性DOM就一次性不要频繁去操作DOM
  • 尽早的开始js执行

    • window.onload和DOMContentLoaded使用第二者
  • 防抖

    • 监听一个输入框的,文字变化后触发change事件
    • 直接用keyup事件,则会频繁触发change事件
    • 防抖:用户输入结束或暂停时,才会触发change事件
    • const input1 = document.getElementById("input1");
      // let timer = null;
      // input1.addEventListener("keyup", function () {
      //   if (timer) {
      //     clearTimeout(timer);
      //   }
      //   timer = setTimeout(() => {
      //     console.log("[Log] input1-->", input1.value);
      //     // 清空定时器
      //     timer = null;
      //   }, 500);
      // });
      ​
      function debounce(fn, delay = 500) {
        // timer是闭包中的
        let timer = null;
        return function () {
          if (timer) {
            clearTimeout(timer);
          }
          timer = setTimeout(() => {
            fn.apply(this, arguments);
            // 清空定时器
            timer = null;
          }, delay);
        };
      }
      input1.addEventListener( 
        "keyup",
        debounce(() => {
          console.log("[Log] input1-->", input1.value);
        })
      );
  • 节流

    • const div1 = document.getElementById("div1");
      let timer = null;
      // div1.addEventListener("drag", function (e) {
      //   if (timer) {
      //     return;
      //   }
      //   timer = setTimeout(() => {
      //     console.log(e.offsetX, e.offsetY);
      //     timer = null;
      //   }, 100);
      // });
      function throttle(fn, delay = 500) {
        let timer = null;
        return function () {
          if (timer) {
            return;
          }
          timer = setTimeout(() => {
            fn.apply(this, arguments);
          }, delay);
        };
      }
      div1.addEventListener(
        "drag",
        throttle(() => {
          console.log(e.offsetX, e.offsetY);
        })
      );

安全

  • xss跨站请求攻击

    • 嵌入<script>脚本 获取cookie发送到我的服务器

    • 预防

      • 替换特殊字符如<变为&lt
      • 前端后端都替换 都做
  • xsrf跨站请求伪造

    • 你正在购物,看中了商品商品id是100

    • 付费接口xxx.com/pay?id=100,但没有任何验证

    • 我是攻击者我看中了一个商品id是200

    • 发一封邮件,邮件隐藏着<img src=xxx.com/pay?id=200>

    • 你查看邮件,就帮我购买id是200的商品

    • 预防

      • 使用post接口
      • 增加验证:例如密码,短信验证指纹等等