machinist

machinist 查看完整档案

北京编辑南华大学  |  撸猫俱乐部 编辑撸猫俱乐部  |  前端工程师 编辑 segmentfault.com/u/machinist 编辑
编辑

JavaScript、 react、 echarts、 umi使用者

个人动态

machinist 发布了文章 · 8月7日

如何优雅的在项目中设置多个环境变量

总所周知项目完成以后代码都会统一打包上线,有些时候我们需要同时上线不同环境,对于不同的环境我们所展示页面、功能有时会有很大变化,那么我们如何应对这样的变化呢?

问题

我发现有些琐碎的事情有些琐碎并且很难做,而在Web应用程序内部却是如此。这些事情之一是处理不同的环境变量。

当您仍在开发应用程序时,通常只有一个环境是正常的,但是当其他一些用户开始对其进行测试或要发布它时,您可能希望对API使用不同的URL,例如该API的开发版本。可以访问新端点或更全面的记录器以及生产就绪版本的API,这些API经过了良好的测试,并具有发行版本的生产数据库。
有时您需要两个以上的环境,例如:

  • 开发:这是您开发应用程序所有新功能的地方;该环境已准备就绪,可以帮助您获得更好的日志和伪造的数据库,如果您花费很多,可以随时删除并重新创建它们;
  • 生产版:这是您将发布到线上的版本

那么如何为不同的环境存储(和使用)不同的变量呢?

显然,我尝试向Google寻求答案,但是我发现的解决方案似乎太困难或不够灵活。

使用.env

image.png
就是这个文件,在文件中可以定义我们需要的配置,然后在项目中使用process.env全局变量获取,声明的属性在 vue以及react中都有特殊的命名要求,比如vue中属性名必须以VUE\_APP\_开头,如VUE_APP_XXX,react则需要以REACT_APP开头。
不幸的是,似乎最多只能处理两种环境(一种开发和一种发行/生产),并且还具有一些可怕的变量缓存。

更简单的解决方案

参照后端配置打包profile的方式。
这是我目前正在使用的方法;准备手指,因为您将需要创建一些新文件。

首先,我们将所有环境存储在envs文件夹中的JSON文件中。
我将为本文创建3个环境(开发,暂存和生产),每个环境将具有不同的API_URL。显然,您可以根据需要添加任意多个变量!

envs/development.json

{   
    "API\_URL": "https://development.api.com",
}

envs/staging.json

{   
    "API\_URL": "https://staging.api.com",  
}

envs/production.json

{   
    "API\_URL": "https://production.api.com", 
}

还在我这儿?现在,我们将创建一个节点脚本,该脚本将在每次我们想要切换环境时更改环境文件。

我将其放在脚本文件夹中(在该文件夹中,我还有其他脚本可以自动执行构建过程的其他部分)

脚本/set-environment.js
#!/ bin/node
const fs = require("fs");//获取传递给节点脚本的环境字符串  

const environment = process.argv[2] //读取json文件的内容  
const envFileContent = require(`../envs/$ {environment} .json`); //将env.json文件内的json复制到  
fs.writeFileSync("env.json", JSON.stringify(envFileContent, undefined, 2));

设置enviroment.js首先,它在一个变量中存储的第一个参数,当你调用脚本,那么它将require里面的文件ENVS具有相同的名称,最后将创建一个env.json文件包含envFileContent内容的根文件夹。

现在我们可以尝试并调用

节点脚本/set-environment.js开发

✨MAGIC✨…一个新的env.json文件在正确的位置,可以为您的所有变量提供服务。

另外,您可以在package.json文件中设置一些脚本,以切换环境而无需在终端中键入所有单词:只需键入yarn env:dev即可。

{   
    "name": "您的应用程序名称",
    "scripts": {   
    "start": "本机开始",
    "env: staging":"节点脚本/set-environment.js阶段",
    "env: dev”:"节点脚本/set-environment.js 开发",
    "env: prod":"节点脚本/set-environment.js,
    "oduction",...   
 },
 "依赖项":{...},
 ...   
}

如何在代码中使用新的.env文件

现在,您只需要从src/env.json文件导入变量,就可以开始了!🚀
1_gHUd_znZCC0p8A9fJtNEWw(1).png

如何自动执行环境切换

现在,我们需要一种自动切换环境的方法。

如果我们在pckage.json建一些新脚本,这几乎是免费的

{
  "name": "your-app-name",
  "scripts": {
    "start": "react-native start",
    "env:staging": "node scripts/set-environment.js staging",
    "env:dev": "node scripts/set-environment.js development",
    "env:prod": "node scripts/set-environment.js production",
    "_ios": "react-native run-ios",
    "ios": "yarn env:dev && yarn _ios",
    "ios:staging": "yarn env:staging && yarn _ios",
    "ios:prod": "yarn env:prod && yarn _ios",
    
    "_build:ios": "react-native bundle --platform ios ...",
    "build:ios": "yarn env:dev && yarn _build:ios",
    "build:ios:staging": "yarn env:staging && yarn _build:ios",
    "build:ios:prod": "yarn env:prod && yarn _build:ios",
  },
  "dependencies": {...},
  ...
}

后期我们也可以打包出来的文件(src/env.json)统一放置到一个文件下只需要修改/set-environment.js文件中fs的写入路径即可。

查看原文

赞 0 收藏 0 评论 0

machinist 提出了问题 · 8月5日

vscode 这个推荐用词怎么关掉

image.png

关注 0 回答 0

machinist 提出了问题 · 8月5日

vscode 这个推荐用词怎么关掉

image.png

关注 0 回答 0

machinist 发布了文章 · 8月1日

hooks 中使用dva

hooks 中使用dva

reacts hooks已经问世很久了今天来记录下如何在hooks是使用dva,
众所周知函数是不可以是不可以使用修饰符修饰的,因为函数存在变量提升问题。
所以大多数人有选择再次掏出自己的redux,但是redux使用起来确实没有dva方便,那么下面就让我们看看如何在hooks中优雅的使用dva吧!

废话不多说直接上代码!

index.js

import React from 'react';
import { connect } from 'dva';
import {Button} from 'antd';

const mapStateToProps = (state)=> {
  return {
    home: state.home,
  }
};

const usePage = (props) => {
const {home} = props;
  const btnClick = () => {
    const {dispatch} = props;
    dispatch({
      type:"home/getList",
      payload: !home.likes
    })
  };

  return (
    <div>
      <Button onClick={btnClick}>点击有惊喜</Button>
      <span>{home.likes ? 'true' : 'false'}</span>
    </div>
  );
};
export default connect(mapStateToProps, null)(usePage);

model.js

export default {
  namespace: 'home',
  state: {
    likes: null,
  },
  effects: {},
  reducers: {
    getList(state, {payload}) {
      return {...state, likes: payload};
    },
  },
};

为了简单我连css以及副作用都不要了。够简单不? 不会还不会吧?

查看原文

赞 1 收藏 0 评论 0

machinist 发布了文章 · 4月27日

post 模拟下载接口 (兼容ie)

let url = '/FileService/Export/Task/download', param;

 axios.post(, param, {responseType: 'blob'})
      .then(function(response) {
        let res = response.data;
        if (res.type.indexOf('application/json') !== -1) {
          let reader = new FileReader();
          reader.readAsText(res, 'utf-8');
          reader.onload = function (e) {
            let data = JSON.parse(reader.result);   //e.target.result也可
            if(data.info){
              message.error(data.info);
            }else if(data.msg){
              message.error(data.msg);
            }
          }
        }
        else{
          let blob = new Blob([response.data]);
          let downloadElement = document.createElement('a');
          let href = window.URL.createObjectURL(blob); //创建下载的链接
          let fileName = response.headers["content-disposition"].split(";")[1].split("filename=")[1].split(".zip")[0];
          if (isIE()) {
            window.navigator.msSaveBlob(blob, fileName);
          } else {
            downloadElement.href = href;
            downloadElement.download = `${fileName}.zip`; //下载后文件名
            document.body.appendChild(downloadElement);
            downloadElement.click(); //点击下载
            document.body.removeChild(downloadElement); //下载完成移除元素
            window.URL.revokeObjectURL(href); //释放掉blob对象
          }
        }
      })
      .catch(function(error) {
        console.error("error",error)
        message.error("出现异常,请重试");
      });
查看原文

赞 5 收藏 5 评论 0

machinist 收藏了文章 · 2019-11-25

万字长文,20-50K前端工程师部分面试题集锦 - 附答案

​ 现在20-50K的招聘,我们先看看是什么要求?

蚂蚁金服招聘要求:

虾皮招聘:

腾讯:

明源云:

毫无疑问,这些公司都是招聘的大前端技术栈的职位,之前文章提到过2020年大前端最理想的技术栈,其实真的弄得很明白那些,出去面试基本上不会有什么问题。
小提示:如果发现小公司面试套你的技术和架构,迅速结束,开出天价薪资走人

下面正式公布部分面试题,以及答案

  • 出于对各个公司的尊重,不公布是哪家公司的面试题,以及面试技巧。只公布部分面试题和答案,以及分析问题的角度,学习方向,面试中考察的不仅仅技术深度,还有广度,每个人不可能技术面面俱到,前端学习的东西太多,忘掉一部分也是正常。记住核心就是关键,这些都是一些基础面试题,比较通用。
  • 一般面试都会要做题,据我经验看,一般都是6页,三张纸。考察的大部分是前端技术栈,原生Javascript的内容,当然,有的外企的面试体验更棒,技术一面规定是半个小时,国内公司可能有5轮,甚至6、7轮。
  • 面试题我会归纳成原生JavaScript、Node.js、React、Vue、通信协议、运维部署、CI自动化部署、Docker、性能优化、前端架构设计、后端常见的架构等来分开写

原生JavaScript篇

以下代码跟我写的有点不一样,但是大致差不多,最终都是在纸上手写实现

手写一个深拷贝:

此处省略了一些其他类型的处理,可以在题目旁注释、

手写一个reduce:

Array.isArray的原理:

手写一个的防抖函数:

手写一个Promise:

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
​
function MyPromise(fn) {
    const self = this;
    self.value = null;
    self.error = null;
    self.status = PENDING;
    self.onFulfilledCallbacks = [];
    self.onRejectedCallbacks = [];
​
    function resolve(value) {
        if (value instanceof MyPromise) {
            return value.then(resolve, reject);
        }
        if (self.status === PENDING) {
            setTimeout(() => {
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
            }, 0)
        }
    }
​
    function reject(error) {
        if (self.status === PENDING) {
            setTimeout(function() {
                self.status = REJECTED;
                self.error = error;
                self.onRejectedCallbacks.forEach((callback) => callback(self.error));
            }, 0)
        }
    }
    try {
        fn(resolve, reject);
    } catch (e) {
        reject(e);
    }
}
​
function resolvePromise(bridgepromise, x, resolve, reject) {
    if (bridgepromise === x) {
        return reject(new TypeError('Circular reference'));
    }
​
    let called = false;
    if (x instanceof MyPromise) {
        if (x.status === PENDING) {
            x.then(y => {
                resolvePromise(bridgepromise, y, resolve, reject);
            }, error => {
                reject(error);
            });
        } else {
            x.then(resolve, reject);
        }
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(bridgepromise, y, resolve, reject);
                }, error => {
                    if (called) return;
                    called = true;
                    reject(error);
                })
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}
​
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    let bridgePromise;
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
    if (self.status === FULFILLED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0);
        })
    }
    if (self.status === REJECTED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(self.error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0);
        });
    }
    if (self.status === PENDING) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            self.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            self.onRejectedCallbacks.push((error) => {
                try {
                    let x = onRejected(error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
}
MyPromise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}
​
MyPromise.deferred = function() {
    let defer = {};
    defer.promise = new MyPromise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
}
try {
    module.exports = MyPromise
} catch (e) {}
promisify原理:

promisify = function(fn) {
  return function() {
    var args = Array.from(arguments);
    return new MyPromise(function(resolve, reject) {
      fn.apply(
        null,
        args.concat(function(err) {
          err ? reject(err) : resolve(arguments[1]);
        })
      );
    });
  };
};
Redux核心源码解析:

bindActionCreator源码解析:


export default function bindActionCreator(actions, dispatch) {
    let newActions = {};
    for (let key in actions) {
        newActions[key] = () => dispatch(actions[key].apply(null, arguments));
    }
    return newActions;
}

核心:将多个action和dispatch传入,合并成一个全新的actions对象

combineReducers源码:

export default combineReducers = reducers => (state = {}, action) => Object.keys(reducers).reduce((currentState, key) => {
    currentState[key] = reducers[key](state[key], action);
    return currentState;
}, {});

核心:跟上面有点类似,遍历生成一个全新的state,将多个state合并成一个state

createStore源码结合applyMiddleware讲述如何实现处理多个中间件:


export default function createStore(reducer, enhancer) {
    if (typeof enhancer !== 'undefined') {
        return enhancer(createStore)(reducer)
    }
    let state = null
    const listeners = []
    const subscribe = (listener) => {
        listeners.push(listener)
    }
    const getState = () => state
    const dispatch = (action) => {
        state = reducer(state, action)
        listeners.forEach((listener) => listener())
    }
    dispatch({})
    return { getState, dispatch, subscribe }
}
applyMiddleware:
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer) => {
        const store = createStore(reducer)
        let dispatch = store.dispatch
        let chain = []
​
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
​
        return {
            ...store,
            dispatch
        }
    }
}

核心:当发现传入createStore的第二个或者第三个参数存在时候(这里没有像原生redux支持SSR代码注水,不支持第二个参数initState),就去返回它的调用结果

整个Redux这里是最绕的,这里不做过分的源码讲解,其实核心就是一点:

实现多个中间件原理,就是将dispatch当作最后一个函数传入,利用compose这个工具函数,最终实现多个中间件同时起作用,当你源码看得比较多的时候会发现,大多数的源码是跟redux相似

compose工具函数实现:
export default function compose(...funcs) {
    return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

核心:其实就是一个reduce函数实现,每次返回一个新的函数,再将新的参数传入

redux下次会专门出个文章讲解,它的源码太重要了~

原生JavaScript考察点比较多,这里只列出一部分,还有像结合TypeScript一起问的,组合继承,对象创建模式、设计模式等,但是那些本次不做讲解

Node.js篇幅:

简述Node.js的EventLoop:

现场出题,项目里有下面这段代码,输出是什么,稳定吗,说明原因:
setTimeout(() => {
  console.log(1);
});
//----若干代码逻辑
new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log(2);
});

答案:先输出2,再输出1,但是不稳定。因为定时器的执行时间不确定,node.js的轮询相当于一个定时器,一直从上往下6个阶段轮询,此时如果中间代码比较耗时,还没运行到Promise时候,已经轮询到第一阶段,定时器的回调就会被触发。

Node.js为什么处理异步IO快?

答:Node 底层采用线程池的原理管理异步 IO,所以我们通常所的 单线程是指 Node 中 JavaScript 的执行是单线程的,但 Node 本身是多线程的。Node.js 中异步 IO 是通过事件循环的方式实现的,异步 IO 事件主要来源于网络请求和文件 IO。但是正因为如此,Node.js处理很多计算密集型的任务,就比较吃力,当然有多进程方式可以解决这个问题。(自己给自己挖坑)

以前听过一个很形象的回答:Java是一百个服务员对应一百个用餐客人,Node是一个服务员对应一百个用餐客人(因为客人不需要分分钟服务,可能只要三分钟----好像,东哥?)

Node.js有cluster、fork两种模式多进程,那么这两种情况下,主进程负责TCP通信,怎样才可以让子进程共享用户的Socket对象?

答案:cluster模式,多实例、自动共享端口链接、自动实现负载均衡。fork模式实现的多进程,单实例、多进程,可以通过手动分发socket对象给不同子进程进行定制化处理、实现负载均衡

Node.js多进程维护,以及通信方式:

答案:原生的cluster和fork模式都有API封装好的进行通信。如果是execfile这样形式调起第三方插件形式,想要与第三方插件进行通信,可以自己封装一个类似promisyfy形式进行通信,维护这块,子进程可以监听到异常,一旦发现异常,立刻通知主进程,杀死这个异常的子进程,然后重新开启一个子进程~

简单谈谈,Node.js搭建TCP、restful、websocket、UDP服务器,遇到过哪些问题,怎么解决的

答案:这里涉及的问题比较多,考察全方位的通信协议知识,需要出个专题后期进行编写

看你简历上写,对koa源码系统学习过,请简述核心洋葱圈的实现:

答案:洋葱圈的实现,有点类似Promise中的then实现,每次通过use方法定义中间件函数时候,就会把这个函数存入一个队列中,全局维护一个ctx对象,每次调用next(),就会调用队列的下一个任务函数。伪代码实现~:


use (fn) {
    // this.fn = fn 改成:
    this.middlewares.push(fn) // 每次use,把当前回调函数存进数组
}
compose(middlewares, ctx){ // 简化版的compose,接收中间件数组、ctx对象作为参数
    function dispatch(index){ // 利用递归函数将各中间件串联起来依次调用
        if(index === middlewares.length) return // 最后一次next不能执行,不然会报错
        let middleware = middlewares[index] // 取当前应该被调用的函数
        middleware(ctx, () => dispatch(index + 1)) // 调用并传入ctx和下一个将被调用的函数,用户next()时执行该函数
    }
    dispatch(0)
}
​

所以这里说,源码看多了会发现,其实大都差不多,都是你抄我的,我抄你的,轮子上搭积木

你对TCP系统学习过,请你简述下SYN flood攻击:

小提示:我的TCP是跟张师傅学习的,在某金的小册上有卖。~推荐购买

答案:攻击方伪造源地址发送SYN报文,服务端此时回复syn+ack,但是真正的IP地址收到这个包之后,有可能直接回复了RST包,但是如果不回复RST包,那就更严重了,可能服务端会在几十秒后才关闭这个socket链接(时间根据每个系统不一样)

抓包可见~:

TCP可以快速握手吗?

答案:可以 -- 内容来自 张师傅的小册

TCP链接和UDP的区别,什么时候选择使用UDP链接?

http://www.xiuchuang.com/ques...

总结就是:TCP面向链接,UDP面向消息,TCP的ACK作用是确认已经收到上一个包,UDP只管发送,一些无人机的操作,就用UDP链接,每个无人机就是一个服务器,跟地面通讯。

通信协议还是要系统学习,通信这里也问了大概半个小时,包括密钥交换等

看你简历上有写自己实现了一个mini-react,请简述实现原理,以及diff算法实现

仓库地址:https://github.com/JinJieTan/...

答案:利用了babel,将虚拟dom转换成了我想要的对象格式,然后实现了异步setState、component diff 、 element diff 、props 更新等。类似PReact的将真实dom和虚拟dom对比的方式进行diff,这里结合代码讲了大概半个小时~ 大家可以看源码,这个对于学习React是非常好的资料,当时我花了半个多月学习

看你对Vue的源码有系统学习过,请简述下Vue2.x版本的数据绑定:

答案:Vue里面的{{}}写法, 会用正则匹配后,拿到数据跟data里的做对比-解析指令,观察数据变化是利用defineProperty来实现,因为监听不到数组的变化,所以尤大大只重写了6个数组API。源码解析,后面就是拼细节,主要讲一些核心点的实现。

为什么Vue的nextTick不稳定?

答案:Vue的nextTick原理是:

优雅降级:

首选promise.then

然后是setImmediate

然后是一个浏览器目前支持不好的API

最后是setTimeout

dom真正更新渲染好的时间,不能真正确定,不论是框架还是原生,都存在这个问题。所以用nextTick并不能保证拿到最新的dom

谈谈你对微前端的看法,以及实践:

答案:将Vue和React一起开发,其实一点都不难,只要自己能造出Redux这样的轮子,熟悉两个框架原理,就能一起开发,难的是将这些在一个合适的场景中使用。之前看到网上有微前端的实践,但是并不是那么完美,当然,类似Electron这样的应用,混合开发很正常,微前端并不是只单单多个框架混合开发,更多是多个框架引入后解决了什么问题、带来的问题怎么解决?毕竟5G还没完全普及,数据传输还是不那么快。过大的包容易带来客户端的过长白屏时间(自己给自己挖坑)

你有提到白屏时间,有什么办法可以减少吗?都是什么原理

答案: GZIP,SSR同构、PWA应用、预渲染、localStorage缓存js文件等、

下面就是细分拆解答案,无限的连带问题,这里非常耗时,这些内容大都网上能搜到,我这里就不详细说

其中有问到PWA的原理,我的回答是:

Service Worker 有一套自己的声明周期,当安装并且处于激活状态时候,网站在https或者localhost的协议时候,可以拦截过滤发出的请求,会先把请求克隆一份(请求是流,消费就没有了),然后判断请求的资源是否在 Service Worker 缓存中,如果存在那么可以直接从 Service Worker 缓存中取出,如果不存在,那么就真正的发出这个请求。

看你的技术栈对Electron比较熟悉,有使用过React-native,请你谈谈使用的感受?

答案:React-native的坑还是比较多,但是目前也算拥有成熟的生态了,开发简单的APP可以使用它。但是复杂的应用还是原生比较好,Electron目前非常受欢迎,它基本上可以完成桌面应用的大部分需求,重型应用开发也是完全没问题的,可以配合大量C# C++插件等。

Node.js的消息队列应用场景是什么?原理是什么

答案:我们公司之前用的kafka,消息队列的核心概念,异步,提供者,消费者。例如IM应用,每天都会有高峰期,但是我们不可能为了高峰期配置那么多服务器,那样就是浪费,所以使用消息队列,在多长时间内流量达到多少,就控制消费频率,例如客户端是流的提供者,有一个中间件消费队列,我们的服务器是消费者,每次消费一个任务就回复一个ACK给消费队列,消费频率由我们控制,这样任务不会丢失,服务器也不会挂。 还有一个异步问题,一个用户下单购买一件商品,可能要更新库存,已购数量,支付,下单等任务。不可能同步进行,这时候需要异步并行,事务方式处理。这样既不耽误时间,也能确保所有的任务成功才算成功,不然没有支付成功,但是已购数量增长了就有问题。

此处省略、、、一万字

用户就是要上传10个G的文件,服务器存储允许的情况下,你会怎么处理保证整体架构顺畅,不影响其他用户?

答案:我会准备两个服务器上传接口,前端或者原生客户端上传文件可以拿到文件大小,根据文件大小,分发不同的对应服务器接口处理上传,大文件可以进行断点续传,原理是md5生成唯一的hash值,将分片的hash数组先上传到后端,然后将文件分片上传,对比hash值,相同的则丢弃。不一致的话,根据数组内容进行buffer拼接生成文件。

关于服务器性能,大文件上传的服务器允许被阻塞,小文件的服务器不会被阻塞。

谈谈你对前端、客户端架构的认识?

答案:前端的架构,首先明确项目的兼容性,面向浏览器编程,是否做成PC、移动端的响应式布局。根据项目规模、后期可能迭代的需求制定技术方案,如果比较重型的应用应该选用原生开发,尽量少使用第三方库。

客户端架构:是否跨平台,明确兼容系统,例如是否兼容XP ,如果兼容XP就选择nw.js,再然后根据项目复杂度招聘相应技术梯度人员,安排系统学习相关内容,招聘人员或者购买定制开发相关原生插件内容。

虽然说只是谈谈,但是感觉面试的职位越高级、轮数越往后,越考验你的架构能力,前面考察基础,后面考察你的技术广度以及逻辑思维,能否在复杂的应用中保持清醒头脑,定位性能这类型的细节能力。很多人基础面试面得很好,但是拿不到offer,原因就是没有这种架构能力,只能自己写代码,不能带领大家学习、写代码。这也是我在面试时偶然听到某个大公司HR之间的对话,原话是:他面试还可以,看起来是很老实(某个之前的面试者),但是他对之前项目整体流程并不是那么清楚,连自己做的项目,前后端流程都不清楚,感觉不合适。

介绍一下Redis,为什么快,怎么做持久化存储,什么叫缓存击穿?

答案:Redis将数据存储在内存中,key-value形式存储,所以获取也快。支持的key格式相对于memorycache更多,而且支持RDB快照形式、AOF

1.RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。RDB是Redis默认的持久化方式,会在对应的目录下生产一个dump.rdb文件,重启会通过加载dump.rdb文件恢复数据。

2、优点

1)只有一个文件dump.rdb,方便持久化;

2) 容灾性好,一个文件可以保存到安全的磁盘;

3) 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化(使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能) ;

4)如果数据集偏大,RDB的启动效率会比AOF更高。

1)数据安全性低。(RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不是特别严格的时候)

2)由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF持久化是以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,文件中可以看到详细的操作记录。她的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

2、优点

1)数据安全性更高,AOF持久化可以配置appendfsync属性,其中always,每进行一次命令操作就记录到AOF文件中一次。

2)通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题。

3)AOF机制的rewrite模式。(AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall))

3、缺点

1)AOF文件比RDB文件大,且恢复速度慢;数据集大的时候,比rdb启动效率低。

2)根据同步策略的不同,AOF在运行效率上往往会慢于RDB。

具体可看、

https://baijiahao.baidu.com/s...

Redis可以配合session等做服务端持久化存储、还介绍了下session的场景,

介绍下缓存击穿和穿透:

缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

介绍下你会用的自动化构建的方式:

答案

Jenkins自动化构建

自己搭建Node.js服务器,实现Jenkins

Docker配合Travis CI实现自动化构建

细化答案:

Jenkins自动化构建:

配置,自动同步某个分支代码,打包构建。

自己搭建Node.js服务器,实现Jenkins:

自己搭建Node.js的服务器,在GitLab上指定webhook地址,分支代码更新触发事件,服务器接受到post请求,里面附带分支的信息,执行自己的shell脚本命令,指定文件夹,构建打包。

服务器上使用Docker-compose指定镜像,每次代码推送到gitHub,通过自己编写的yml和dockerfile文件构建打包,服务器自动拉取最新镜像并且发布到正式环境

代码实现:

.travis.yml

language: node_js
node_js:

  • '12'

services:

  • docker


before_install:

  • npm install
  • npm install -g parcel-bundler


script:

  • parcel build ./index.js
  • echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
  • docker build -t jinjietan/mini-react:latest .
  • docker push jinjietan/mini-react:latest

dockerfile:

FROM nginx
COPY ./index.html /usr/share/nginx/html/
COPY ./dist /usr/share/nginx/html/dist
EXPOSE 80

问完Redis,肯定会问数据库,mysql mongodb sqlite都问了,这里就暂时不写了

数据库需要系统的学习,特别是mysql,我这里就不班门弄斧了,推荐某金上面的小孩子的小册。零蛋学mysql(本文没有收取任何广告费,自己买了看完才推荐给大家)

附带的一些问题:

Linux常见操作

云端部署

等。

当然有人会问20-50K的问题怎么这么简单,因为每个问题都是串起来的,需要你的知识面足够广,才能一路面下去,直到拿到offer。而且每个问题都是有坑,例如pm2 start 如果不指定参数到底会启动多少个进程? 在云端和自己的电脑上是不一样的,这些都需要你去有实际操作经验才能应对。

本文的初衷,不为了面试而背面试题,只是让大家知道应该学习什么方向。纯粹为了面试去背诵面试题,是很容易被识破,只有不断积累学习,你才会轻易拿下offer。
座右铭:你想要在外人看来毫不费力,那你就要平时不断努力~

如果觉得写得不错,欢迎点个在看,关注一下小编的公众号:前端巅峰,赞赏一下更好啦。我们有大前端交流群,里面妹子多多~大牛多多,欢迎加入~ 微信公众号发送加群:即可加入~

文章里面有写得不对的地方,可以在下面指出,我的答案也不一定对,上面部分答案由于太长,网上复制了一部分,但是大部分是自己写的~

查看原文

machinist 回答了问题 · 2019-11-21

解决关于javascript内存泄露的一个问题

null 是释放了user的数据内存,但是这个变量的内存没释放吧 其他地方使用user 有可能为null报错

关注 6 回答 5

machinist 回答了问题 · 2019-11-20

前端播放本地视频卡

首先问下视频有多大啊,有可能是因为文件过大的问题造成的,那就使用预加载,或者loading,
如果不是文件过大可以看下video的标签:
autoplay autoplay 如果出现该属性,则视频在就绪后马上播放。
controls controls 如果出现该属性,则向用户显示控件,比如播放按钮。
height pixels 设置视频播放器的高度。
loop loop 如果出现该属性,则当媒介文件完成播放后再次开始播放。
preload preload
如果出现该属性,则视频在页面加载时进行加载,并预备播放。
如果使用 "autoplay",则忽略该属性。
src url 要播放的视频的 URL。
width pixels 设置视频播放器的宽度。

看下吧,具体属性都在这里了

关注 2 回答 1

machinist 回答了问题 · 2019-11-20

解决有哪些网站使用了rem?

淘宝一类的吧 老页面应该还可以找到一些,大多数页面都没有了

关注 3 回答 4

machinist 发布了文章 · 2019-11-15

教你应付一个很恶心却常见的需求(点击自身以外的区域关闭自己)

做前端最难受的就是产品给的需求以及测试给的bug了,看待这个标题是不是有点蒙,这是什么需求,下面来解读下!

需求

需求:如下图,点击已保存按钮弹出已保存列表,当点击屏幕其他区域要关闭这个下拉框,是不是很简单

加点东西,但是点击这个下拉框内部的区域不能关闭下拉框)(原谅我里面没写东西你就当有东西,给个机会噻!),是不是很常见,是不是很恶心!你们是怎么做的。 之前我见过有人把事件挂在window上,然后把不想要操作dom都挂上class,然后在点击事件里面一一排除括弧笑,或者有人投机取巧在一部分dom上挂了事件,结果又是测试不是每次都能关闭,全看运气!

接天就来说下我的解决办法,因为我是写的react,我就直接上react代码了,逻辑都是一样,无妨!

挂载事件

首先在componentDidMount中写下我们函数的触发:

window.addEventListener('click', (event) => {
      event = event || window.event;

        this.setState({
          savedListShow: false, // 这个是控制列表显隐的状态
        });
    })

事件挂在window上怎么避免点击列表内部的dom不会触发呢,不要说什么阻止冒泡,不好使的,首先我们在事件中输出我们event,
看看到底是什么!

元素挂上不想触发的识别class

这是我点击列表内部输出的event

说是路径,其实在深一步输出出来的是js对象,既然是js对象那我们就能拿到对象上的calss属性了,对于我们的判断也就不成问题了。

去代码中到找我们不想要触发的那个(或者是一堆)不想触发的dom的最大父级,

对的我这里是class为SavedList的div,在最前边(记住是最前边)加上一个class _&saveBtn(别人不用的,免得冲突)。

点击事件中加判断排除不想触发的dom

在细看下上边我点击输出的event中的path, 每一个元素.后的就是class, 注意横线处的class,这说明我点击的事件源是我不希望触发的区域,然后我们回到最开始componentDidMount中的事件函数。

componentDidMount() {
    // 点击其他区域关闭已保存条件弹窗
    window.addEventListener('click', (event) => {
      event = event || window.event;

      let isSaveStructure = event.path.some(item => {
      这里使用了startsWith,相对来说比较严谨,这要是为什么强调要在最前边添加的原因了
        if (item.className && typeof item.className === 'string' && (item.className.startsWith('_&saveBtn') || item.className.indexOf('_&saveBtn') !== -1)) {
          return true;
        }
      });

      if (!isSaveStructure) {
        this.setState({
          savedListShow: false
        });
      }
    })
  }

这样依赖我们不想触发的dom以及dom内部的元素,遇到这个点击事件就会呗排除在外了,是不是很省心。

总结

  • 添加事件 window.addEventListener('click', (event) => {})
  • 在不想触发的dom的最大父级上添加用于区分的class,比如classSavedList的div以及内部dom不需要触发那就在SavedList上添加需要识别的class, 主要要避免和其他人的冲突,最好是特殊一点;
  • 在之前写好的实践中,判断注意path里面的是一个数组,包含的是js对象,抓取返回回来的classNamestring类型,不是数组,我们判断使用的是startsWith();
查看原文

赞 11 收藏 8 评论 36

认证与成就

  • 获得 94 次点赞
  • 获得 6 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 6 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-08-13
个人主页被 756 人浏览