常见的前端面经试题整理。持续更新中....

文章目录

阿里巴巴试题

1.元素居中的方式

法一:
display:flex;
justify-content:center


法二:
margin:0 auto;

法三:
positon: absolute;
left:50%;
transform:translateX(-50%);

2. XSS和CSRF(react天然对xss有防范)

CSRF

((Cross-site request forgery):跨域请求伪造。)

image-20210912220530940

XSS

(XSS(Cross Site Scripting):跨域脚本攻击)

XSS的攻击原理

XSS攻击的核心原理是:不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等)。

最后导致的结果可能是:

盗用Cookie破坏页面的正常结构,插入广告等恶意内容D-doss攻击

区别一:

CSRF:需要用户先登录网站A,获取 cookie。XSS:不需要登录。

区别二:(原理的区别)

CSRF:是利用网站A本身的漏洞,去请求网站A的api。XSS:是向网站 A 注入 JS代码,然后执行 JS 里的代码,篡改网站A的内容。

3.前端项目工程化

就是首先需要先写页面,复用性组件进行模块化,书写代码时要求规范化,然后页面功能完毕之后进行测试功能,实现完成之后就提交到远程git仓库。·

个人总结经验,认为前端工程化主要应该从模块化、组件化、规范化、自动化四个方面来思考。如何选型技术、如何定制规范、如何分治系统、如何优化性能、如何加载资源,当你从切图开始转变为思考这些问题的时候,我想说:你好,工程师!

4.深克隆、浅克隆

js有两种大类的类型:基本类型(undefined、Null、Boolean、Number和String)和引用类型(Object、Array和Function),基本类型的复制是不会产生深度复制和浅度复制问题,当使用引用类型进行复制的时候,其实复制的是指向内存值的指针,并没有真实复制其数据(浅复制)。

浅克隆:

也就是两个引用类型之间进行赋值,将一个引用类型赋值给另一个引用类型,事实上是赋的是引用类型的地址,而非数据。当赋值之后,两个引用类型占用共同的地址空间,其实就是两个引用类型是指向同一个堆地址空间,引用类型的数据也就自然一样了。

// 浅克隆
let obj = {
   
    name: "wh",
    age: 23
}
let objShallowClone = obj //此时就相当于浅克隆,他们只是指向同一个地址空间,所以数据也就自然一样了,并没有重新构建一个对象
console.log(objShallowClone == obj);  //true

深克隆

相对于浅克隆,深克隆是重新构建了一个对象,开辟了一个新的地址空间,将原先对象的数据全都复制到新的对象上,新对象和原对象毫无关系,互不影响。而不是项浅克隆那样只是指向同一个地址空间,

采用json.parse()和json.stringify()实现深克隆
// 深克隆
// json.parse()和stringify实现
let obj = {
   
    name: "wh",
    age: 23
}
let objDeepClone = JSON.parse(JSON.stringify(obj));
console.log(objDeepClone == obj); //false
使用ES6的Array.from(arr)实现数组深克隆
// 第二种方式:使用Array.from(arr)深拷贝一个新的数组
let arr = [1, 2, 3]
let arr1 = Array.from(arr)
console.log(arr == arr1);  //false
使用ES6的扩展运算符…
// 第三种方式,扩展运算符 ...
let arr3 = [...arr]
console.log(arr3 == arr); //false
暴力循环进行赋值
// 第四种方式,也就是最粗暴的方式,暴力循环进行赋值
let arr4 = new Array(arr.length)
for (const key in arr) {
   
    arr4[key] = arr[key]
}
console.log(arr4 == arr);  //false

5.对微前端,微服务,大前端的理解。

微前端

什么是微前端?

image-20211114111314091

微前端就是将不同的功能按照不同的维度将一个应用拆分成多个子应用,通过主应用来加载这些子应用。微前端的核心在于拆、拆完后再合。

为什么要使用它?

image-20211114110701117

我们可以将一个应用划分为若干个子应用,将子应用再进行打包成一个个的lib,然后在分别上线(打包上线)。当路径切换时加载不同的子应用,这样每个子应用都是独立的,技术栈也不用做限制了(每个子应用可以使用vue或者react再或者andular.js),从而解决了前端协同开发问题。

image-20211114111229604

关于微前端的两个成熟的框架:Single-SPA,qiankun。

Single-SPA

Single-SPA是一个用于微前端服务化的js前端解决方案(本身没有处理样式冲突隔离(这是因为一个应用中包括的很多子应用可能会有样式冲突,他没有解决),也没有处理js冲突隔离),它仅仅做了一件事,就是实现了路由劫持和应用加载。

使用Single-spa框架的整个流程:

第一步:首先需要创建vue create一个子应用和父应用。

第二步:在子应用中,如果你是vue项目,就引入single-spa-vue库,然后导出这个库提供的3个方法(也称为singleSpa的生命周期函数:bootstrap、mount、unmount),然后再创建一个vue.config.js在里面配置一些webpack打包输出信息,打包此应用为一个lib。

其中vue.config.js是vue-cli3新增加的一个功能,就是如果要配置webpack的属性,就需要在项目根目录下创建一个vue.config.js。

第三步:在父应用中,需要引入single-spa库,引入这个库的注册方法(registerApplication方法)和启动方法(start()方法)

single-spa缺陷:不够灵活,不能动态加载js文件,并且样式不隔离;

没有解决沙箱的问题(就是让一个应用处于一个隔离的环境,不受其他外界应用造成影响)

qiankun

qiankun框架是基于Single-spa框架,他提供了开箱急用的API(single-spa + sandbox + import-html-entry),并且技术栈无关,并且接入简单(像iframe一样,但是iframe现在基本已经废弃了,因为如果只是使用iframe,iframe中的子应用在切换路由时用户刷新页面就不好了,一旦刷新那么url的状态就会丢失,就不能前进和后退了且页面会丢失)。

总结:子应用可以独立构建,运行时动态加载,主子应用完全解耦,各个子应用中技术栈可以不同,考的是协议接入(子应用必须导出bootstrap(启动方法)、mount、unmount方法),并且解决了样式隔离、js隔离等沙箱问题,

使用qiankun框架的步骤:

第一步:首先需要在子页面中设置子应用的协议,也就是导出的那三个生命周期函数bootstrap、mount、unmout。然后在vue.config.js中配置webpack,配置子应用的可跨域以及输出类型为umd类型(使得父应用可以得到子应用的生命周期函数)

//子应用的main.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// 引入全局变量
import "./globle"
// import reportWebVitals from './reportWebVitals';
// 封装一个render方法
function render() {
   
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    document.getElementById('root')
  );
}

// 如果是父应用调用
if (window.__POWERED_BY_QIANKUN__) {
   

}
// 本项目独立运行调用
if (!window.__POWERED_BY_QIANKUN__) {
   
  render();
}
async function bootstrap() {
    }
async function mount(props) {
   
  render();
  // 在这里面可以拿到父应用传递的props
  // console.log(props.name1);
}
async function unmount() {
   
  // ReactDOM.unmountComponentAtNode(document.getElementById('root'));
}
export {
   
  bootstrap,
  mount,
  unmount
}
//子应用的webpack.config.js
module.exports = {
   
    devServer: {
   
        port: "8002",
        headers: {
   
            // 设置可跨域
            'Access-Control-Allow-Origin': '*'
        }
    },
    configureWebpack: {
   
        //设置打包输出
        output: {
   
            // 打包名
            library: "vueApp",
            // 打包为什么类型,配置打包后的模块类型为umd模块,umd的特点就是使得父应用的window对象有singleVue.bootstrap、mount、unmount。
            libraryTarget: "umd"
        }
    }
}

第二步:在父应用中安装qiankun库npm install qiankun;然后设置子应用挂载的位置,以及一些切换路由跳转到此子应用;再引入qiankun库的registerMicroApp()方法(用来注册子应用)、start(用来启动子应用);这样就完成了

// 引入qiankun
import {
   
  registerMicroApps,
  start,
  initGlobalState,
  MicroAppStateActions,
} from "qiankun";
// 注册的应用列表
const apps = [
   // react子应用
      {
   
        name: "reactApp",
        entry: "http://localhost:8002",
        container: "#childReact",
        activeRule: "/login",
       // 通过props实现通信传递值
        props: {
   
          callback: function (res) {
   
            this.res = res;
            console.log(res);
          },
        },
      },
  // 子应用react应用
  {
   
    name: "reactApp",
    // 默认请求的url,并解析这个文件里面的script标签,因为此时父应用请求了子应用里面的资源,所以子应用必须支持跨域
    entry: "http://localhost:8002",
    // 子应用挂载到哪个元素
    container: "#react",
    //激活规则,当路由切换到“/react”时,就会把本地端口为20000的子应用挂载到"#react"元素上
    activeRule: '/react'
  }
]
// 注册子应用
registerMicroApps(apps,
  // 可以设置一下加载逻辑,也可不用
  {
   
    beforeMount() {
   
      console.log(1);
    }
  })
// 启动子应用
start({
   
  prefetch: false //取消预加载,即当不点击触发子应用加载时,不会去启动这些子应用
})

采用微前端,各个子应用之间如何通信。

  1. 最简单的方式是通过url来进行数据的传递,但是传递消息能力弱
  2. 基于CustomEvent实现传递
  3. 基于props主子应用间进行通信
  4. 使用全局变量、redux进行通信

大前端

简单来说,大前端就是所有前端的统称,比如Android、iOS、web、Watch等,最接近用户的那一层也就是UI层,然后将其统一起来,就是大前端大前端是web统一的时代,利用web不仅能开发出网站,更可以开发手机端web应用和移动端应用程序。

其实大前端的主要核心就是跨平台技术,有了跨平台技术,各个平台的差异性就抹平了,开发者只需要一套技术栈就可以开发出适用于多个平台的客户端。

大前端也就是囊括了很多种不同类型的设备的前端页面开发,不拘泥与pc端,也有移动端包括Android、ios、watch等移动端开发。

采用vue的weex可以实现跨平台技术开发(Android、ios、pc前端),Weex 是阿里巴巴开源的一套构建高性能、可扩展的原生应用跨平台开发方案。

采用react-native也可以完成。

6.正则表达式

let re  = new RegExp("at","i");  //构造函数创建正则
let re = /at/i   //通过字面量创建
//正则实例的主要方法就是exec("要捕获的字符串"),主要用于配合捕获组使用,如果找到了匹配项则返回包含第一个匹配信息的数组,数组第一个元素就是匹配到的内容;如果没有找到,则返回null,返回的数组包含index和input属性,分别代表捕获到的在原始字符串中的起始位置,input代表输入的字符串
re.exec("fat2");   //返回的数组中index为1,input为fat2
re.test("fat2");  //返回的是Boolean类型 ,匹配到就返回true,否则返回false

[]中括号是一个集合代表在里面元素中任选一个

(0-9)小括号是匹配的区间

{}代表 匹配次数

7.设计模式

image-20211130212757126

设计模式就是套路,就是前人在解决问题的时候遇到频繁相似的问题,都需要去做重复的工作写重复的代码, 这就需要用一种东西去组织一下,自动化它,这就是设计模式。主要解决的问题就是怎么样让代码更好维护、有更高的复用性、更加稳健。

比如以下代码重用性维护性很差,所以我们采用一些设计模式来进行代码维护

// 最冗余的创建一个对象就是直接赋值,代码重构性是极差的,是开发过程中不推荐的
let p1 = {
   
    name: "wh",
    age: 23
}
let p2 = {
   
    name: "xx",
    age: 23
}

一. 创建型模式

a. 构造器模式

也就是通过使用构造函数创建一个“类”,以后需要创建一个所需要的对象时就不需要写重复代码,只需要传入所需要的参数,new 构造函数创建一个对象。

// 一个构造函数来进行创建对象,代码重用性高且可维护性较好
function Person(name, age) {
   
    this.name = name;
    this.age = age
}
b.原型模式

原型模式跟构造器模式一样的,只是原型模式是在构造函数的原型对象上添加属性和方法

c.建造者模式
// c.建造者模式,也就是对于创建类的时候不是直接传参了,而是在类中设置了一些方法,通过向此方法传参来修改对象属性,还有通过调用一个方法返回所创建的对象
// 然后一点就是可以通过建造者模式创建所传的参数的规则,如果不符合规则,则会有对应的措施
function Student(name, age) {
   
    this.name = name;
    this.age = age
}
function StudentBuilder() {
   
    this.student = new Student;
    this.setName = function (name) {
   
        // 设置一些判断规则
        if (name == "*") {
   
            throw "取名不对"
        }
        this.student.name = name
    }
    // 调用此方法
    this.build = function () {
   
        return this.student
    }
}
d.单例模式

保证一个类只有一个实例,并提供一个访问他的全局访问点。只有第一次new的时候会创建一个对象,以后再new这个单例构造函数时,只会返回第一次new的那个对象。

// 单例模式,在多次new对象的时候只会创建一个对象,之后new 对象的时候一直是同一个对象,也就是说只有第一次new的时候会创建一个对象,以后再new这个单例构造函数时,只会返回第一次new的那个对象
function SingleResource() {
   
    this.balance = 100;
    // 如果之前有new过实例,即存在instance
    if (SingleResource.instance) {
   
        //    直接返回已存在的实例
        return SingleResource.instance
    }
    // instance相当于是刻在SingleResource机器上的,相当于给这个构造函数上添加了一个属性,这个属性等于创建的实例对象
    SingleResource.instance = this
}
// 创建两个实例s1,s2,你会发现其实他们两个是共用的,其实是同一个对象
let s1 = new SingleResource();
let s2 = new SingleResource();
s1.balance = 50;
console.log(s2.balance);  //50
e.工厂模式

工厂模式是用来创建对象的一种最常用的设计模式。不暴露创建 对象的具体逻辑,而是将逻辑封装在一个函数里面,那么这个函数就是一个工厂。

它可以很快的生成几类对象,而且每次返回的对象都是新对象

// e.工厂模式,会在构造函数里面判断所传参数内容,对于不同的内容返回对应的数据或者执行相应的逻辑
/**
 * 
 * @param {string} name 姓名 
 * @param {*} subject  学科
 */
function Student(name, subject) {
   
    this.name = name;
    this.subject = subject
}
/**
 * 
 * @param {string} name   姓名
 * @param {string} type  文科还是理科
 * @return {Student} 
 */
function Factory(name, type) {
   
    // 对传入的type参数先进行判断,不同的文理科返回不同的结果
    switch (type) {
   
        case "理科":
            return new Student(name, ["物理", "化学", "生物"]);
            break;
        case "文科":
            return new Student(name, ["政治", "历史", "地理"]);
            break;
        default:
            throw "没有这个专业,别乱填哇"
    }
    // if (type == "理科") {
   
    //     return new Student(name, ["物理", "化学", "生物"]);
    // }
    // if (type == "文科") {
   
    //     return new Student(name, ["政治", "历史", "地理"]);
    // }
}
let wh = new Factory("wanghe", "理科");
let xx = new Factory("xx", "文科")

二. 行为型模式

观察者模式

定义了对象键一对多的依赖关系,当目标对象的状态发生变化时,所有依赖它的对象会得到通知。并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。

目标对象会订阅多个观察者,当目标对象的观察者发生改变时,会通知观察者

观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。

// 观察者模式
// 当目标状态发生改变时,需要通知给观察者

// 目标者类
class Subject {
   
    constructor() {
   
        // 观察者列表
        this.observers = []
    }
    // 添加观察者
    add(observer) {
   
        this.observers.push(observer)
    }
    // 删除观察者
    remove(observer) {
   
        let index = this.observers.indexOf(observer)
        if (index != -1) {
   
            this.observers.splice(index, 1);
        }
    }
    // 通知给观察者
    notify() {
   
        for (const observer of this.observers) {
   
            observer.update()
        }
    }
}

// 观察者类
class Observer {
   
    constructor(name) {
   
        this.name = name
    }
    // 目标更新时触发的回调
    update() {
   
        console.log(`目标者通知我更新了,我是${
     this.name}`);
    }
}

let o1 = new Observer("wh")
let o2 = new Observer("xxx")
let o3 = new Observer("wmg")
let sub = new Subject();
sub.add(o1)
sub.add(o2)
sub.add(o3)
sub.notify()
//目标者通知我更新了,我是wh
//目标者通知我更新了,我是xxx
//目标者通知我更新了,我是wmg
发布订阅模式

实现了对象间多对多的依赖关系,通过事件中心管理多个事件。通过事件中心管理多个事件,目标对象并不直接通知观察者,而是通过事件中心来派发通知。

事件中心先订阅事件,可以用一个数组来订阅事件,然后再发布事件。

三. 结构型模式

代理模式

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象就可以代替一个对象去引用另一个对象,代理对象起到了中介的作用。

/**
 *一个小学生不能直接开车,需要判断它的年龄是否成年,如果成年了就可以开车,否则就不能,
 则就需要有一个车的代理CarProxy来在人与车之间产生关联
 */

//  车
class Car {
   
    drive() {
   
        return "driving"
    }
}
// 驾驶人
class Driver {
   
    constructor(age) {
   
        this.age = age
    }
}
// 车的代理
class CarProxy {
   
    constructor(driver) {
   
        this.driver = driver
    }
    drive() {
   
        if (this.driver.age >= 18) {
   
            return new Car().drive();
        } else {
   
            return "未满18岁,开个der的车哇"
        }
    }
}

let driver = new Driver(19);
let carProxy = new CarProxy(driver);
console.log(carProxy.drive()); //driving

其实也可以使用ES6的Proxy代理,也就是在目标对象之前假设一层拦截,或者叫做代理

console.log("-------------------------", "ES6代理");
let obj = {
   };
let proxy = new Proxy(obj, {
   
    get: function (target, key, receiver) {
   
        console.log("get"); //当获取值时触发get
        return Reflect.get(target, key, receiver)
    },
    set: function (target, key, value, receiver) {
   
        console.log("set"); //当设置值时触发set
        return Reflect.set(target, key, value, receiver)
    }
});
proxy.count = 1  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

veggie_a_h

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值