Evelyn

Evelyn 查看完整档案

杭州编辑东华理工大学  |  软件工程 编辑...xx...  |  前端开发 编辑 www.cnblogs.com/xlb-happymoment/ 编辑
编辑

一只野生媛媛

个人动态

Evelyn 发布了文章 · 1月13日

Vue3.0 + antdV 实现的后台管理项目

背景

针对上一篇文章写的小程序项目,需要一个相对应的后台管理系统来对其中一部分资源进行管理,所以衍生了这个后台管理系统项目。由于时间紧迫,内容也不是特别多,于是决定使用市面上现成的 vue 后台项目模板。用的比较多的是 vue-element-admin,一方面想试试新一点的技术 vue3.0,另一方面是因为业务线其他项目都用的 react + antd,个人也比较喜欢 antd ,所以想试试 antd 的 vue UI 框架,所以找到了这个模版:vue-admin-beautiful

项目搭建
  1. clone 上面说的项目模板(注意分支,使用的是 vue3.0-antdv)
  2. 按照自己项目的需求,把不需要的内容删掉,按需增加自己需要的内容,如各个页面,公共资源,静态资源等
  3. 确定并安装项目所需要的其他依赖插件等
该项目主要涉及的技术
image.png

4.项目结构
image.png

踩坑
国际化

Vue 3.0 不能直接像 2.0 那样直接使用 i18n,3.0 的 i18n 不是写在 main.js 里,而是使用一种 Provide 与 Inject 的方式。在网上找了一堆文章,大部分都还是 2.0 的用法,最后发现两篇帮助很大的文章:

  1. [ Vue3.0 ] 使用 Vue 3.0 / Vue-cli 4 開發 i18n 國際化 多國語言功能
  2. Create a i18n Plugin with Composition API in Vue.js 3

参考文章,创建国际化插件文件 i18nPlugins.js,创建 i18n 文件夹,用于存放项目所需要的各个语言的 js 文件,个人习惯把各个页面的多语言文案和页面文件放在一起,所以 src 下的 i18n 文件夹中的 lang 存放的是一些全局可共用的资源,最终多语言相关目录结构如下红框所示:
image.png

如何使用?
首先要在 App.vue 文件中使用provideI18n函数正确的配置初始化插件。

  // 导入所需要的资源
  import { provideI18n } from '@/i18nPlugin'
  import langEn from './i18n/en'
  import langZh from './i18n/zh'
  import langTh from './i18n/th'
  
  // 初始化
  setup() {
      let langObj = localStorage.getItem('language')
      let lang = langObj ? JSON.parse(langObj).language : ''
      const locale = lang || navigator.language
      let langUsed = ''
      if (locale.indexOf('zh') > -1) {
        langUsed = 'zh'
      } else if (locale.indexOf('en') > -1) {
        langUsed = 'en'
      } else if (locale.indexOf('th') > -1) {
        langUsed = 'th'
      }
      // 核心代码,langUsed 视项目情况而定
      provideI18n({
        locale: langUsed,
        messages: {
          en: langEn,
          zh: langZh,
          th: langTh,
        },
      })
  },

最后,在我们要使用该插件的任何组件中,都必须使用该useI18n函数中的setup函数将其注入。
例如:
image.png

!!!需要注意的点
antd-vue 的 table 组件,要想表头内容也实现多语言,需要把 columns 写在 computed 属性里面,如图:
image.png

富文本编辑器(Tinymce)

未完待续......

查看原文

赞 10 收藏 8 评论 4

Evelyn 发布了文章 · 1月13日

uni-app 小白入门

前言

业务需求,被迫营业小程序,而且需要微信和支付宝两个端。由于之前没有做过小程序,所以稍微调研了下最近比较火的两款小程序跨端开发框架:uni-app 和 Taro。因为很久没有写过 vue 项目,所以一开始比较偏向于使用 Taro,在参照文档搭建完框架并写了一点小 demo 之后,发现 Taro 的 api 支持性没有 uni-app 好,主要是针对支付宝,所以弃坑 Taro,转战 uni-app。当然,只做微信小程序的话,Taro 还是很棒的。

项目搭建

官网提供了两种快速上手的搭建方式,我这里采用的是第二种(参考:官方文档)。

此处我并没有按照官网选择 Hello uni-app 模板,而是选择了默认模板。

主要步骤:(环境安装省略)

  1. vue create -p dcloudio/uni-preset-vue my-project
  2. 选择默认模板
  3. 集成 sass【需要注意的是版本问题,默认会安装最新版本,但是不能支持,所以要自行选择,试了几个版本,最后用的是 sass-loader@8.0.1 node-sass@4.14.1】
  4. ui(可以选择官方的 uni-ui 或者 uView,一开始我使用的是 uni-ui,后来换了 uView,感觉 uView 更全面更好用)
  5. 网络请求(我采用的是别人封装好的插件 luch-request)
  6. 集成 vuex(按需采用)

最终目录结构如下:image.png

随着项目持续开发,项目包大小超过了微信小程序可预览的包大小限制,于是采取分包措施,uni-app 官方也有相关配置说明:分包配置说明
所以最终目录结构如下所示:
image.png

项目重难点

1. 授权(微信和支付宝)

uni-app 提供了相应的接口,但是不支持支付宝小程序,所以需要做跨端兼容,独立支持微信和支付宝小程序。跨端兼容我采用的是官方文档中提到的条件编译。

> 跨端兼容:uni-app 文档中有说明([uni-app 文档](https://uniapp.dcloud.io/platform) 。

微信小程序授权流程:获取所有授权状态 ---- 如果不存在 --- 再调用授权接口 --- 如果授权成功 --- 调用最终 API 接口

<!-- #ifdef MP-WEIXIN -->
<button
  class="sys_btn"
  open-type="getUserInfo"
  @getuserinfo="appLoginWx"
>
  微信授权登录
</button>
<!-- #endif -->

// 获取所有授权状态
uni.getSetting({
  success(res) {
    console.log("授权:", res);
    if (!res.authSetting["scope.userInfo"]) {
      //这里调用授权
      console.log("当前未授权");
      this.appLoginWx();
    } else {
      //用户已经授权过了
      console.log("当前已授权");
    }
  },
});
appLoginWx() {
  uni.getProvider({
    service: "oauth",
    success: function(res) {
      console.log("res", res);
      if (~res.provider.indexOf("weixin")) {
        uni.login({
          provider: "weixin",
          success: (res2) => {
            console.log("res2", res2);
            uni.getUserInfo({
              provider: "weixin",
              success: (info) => {
                console.log('info', info);
                //TODO[请求接口]
              },
              fail: () => {
                uni.showToast({ title: "微信登录授权失败", icon: "none" });
              },
            });
          },
          fail: () => {
            uni.showToast({ title: "微信登录授权失败", icon: "none" });
          },
        });
      } else {
        uni.showToast({
          title: "请先安装微信或升级版本",
          icon: "none",
        });
      }
    },
  });
},

支付宝小程序授权流程:(官方提供的时序图)
image.png

<!-- #ifdef MP-ALIPAY -->
<button
  @getAuthorize="onGetAuthorize"
  scope="userInfo"
  open-type="getAuthorize"
  class="login"
>
  支付宝授权登录
</button>
<!-- #endif -->

onGetAuthorize() {
  my.getAuthCode({
    scopes: "auth_user", // 主动授权(弹框):auth_user,静默授权(不弹框):auth_base
    success: (res) => {
      if (res.authCode) {
        console.log(app.serverUrl + "/login/" + res.authCode);
        // 调用自己的服务端接口,让服务端进行后端的授权认证
        my.httpRequest({
          url: app.serverUrl + "/login/" + res.authCode,
          method: "POST",
          header: {
            "content-type": "application/json",
          },
          dataType: "json",
          success: (res) => {
            // 授权成功并且服务器端登录成功
            console.log(res);
            me.setData({
              userInfo: res.data.data,
            });
          },
        });
      }
    },
  });
},
2. 支付

微信支付

// 核心代码
uni.requestPayment({
    provider: "wxpay",
    timeStamp: timeStamp,
    nonceStr: nonceStr,
    package: package,
    signType: signType,
    paySign: paySign,
    success: (res) => {
      console.log("payment success >>", res);
      // TODO
    },
    fail: (err) => {
      console.log("payment fail >>", err);
},
});
以上各参数官方文档均有详细说明,请参考uni-app[payment]
3. 客户端生成二维码(插件 tkiQrcode)&& 条形码(插件 tkiBarcode)

二维码使用的插件

具体实现参照官方说明即可,需要注意的是 npm 或者 yarn 安装的话,最新只有 0.1.6 版本,但是这个版本不支持支付宝小程序,所以需要把 tki-qrcode 的源码下载下来放在自己项目的 components 文件夹下使用,否则支付宝无法实现客户端生成二维码。

条形码插件
按照文档说明操作,即可实现,暂时没有出现不兼容等其他问题。

4. 踩坑
  • 日历组件问题
背景:页面中需要用到日历组件进行日期选择

使用的 uView 的 Calendar 组件,发现编译后在 H5 端能正常生效,但是微信小程序和支付宝小程序都无效,无法选择其他日期。
使用 uniCalendar 组件,微信小程序中无法实现自定义可选日期范围等功能,需要修改源码。

解决方案:采用原生的 picker 组件

未完待续......

查看原文

赞 0 收藏 0 评论 0

Evelyn 提出了问题 · 2020-11-04

useSelector from 'react-redux' is undefined

./node_modules/react-redux/es/hooks/useSelector.js 107:4-17
"export 'useDebugValue' was not found in 'react'

Taro 使用 redux 时遇到的报错,有大佬遇到过吗,怎么解决的啊?

关注 1 回答 0

Evelyn 收藏了文章 · 2020-06-09

这些前端基础题你能答对几道?(测试你的基础掌握,附答案解析)

在三大框架盛行的时代, 基本上会个Vue就能在小公司浑水摸鱼。但是当想突破的时候就会意识到基础的重要性。

JavaScript中有很多重要特性及概念。比如原型,原型链,this,闭包,作用域,隐式转换等等。如果不能熟练掌握,在进阶中级前端开发工程师的道路上必定是困难重重。

用一个小时把这些题做完。检测一下你的基础掌握程度。

image.png

正题

1.

if(false){
    var a = 1;
    let b = 2;
}
console.log(a);
console.log(b);

2.

var a = 1;
if(true){
    console.log(a);
    let a = 2;
}

3.

var a = {n: 1}
var b = a
a.x = a = {n: 2}

console.log(a.n, b.n);
console.log(a.x, b.x);

4.

console.log(c);
var c;
function c(a) {
    console.log(a);
    var a = 3;
    function a(){
    }
}
c(2);

5.

var c = 1
function c(c) {
    console.log(c);
    var c = 3;
}
console.log(c);
c(2);

6.

var name = 'erdong';
(function () {
    if (typeof name === 'undefined') {
        var name = 'chen'
        console.log(name)
    } else {
        console.log(name)
    }
})();

7.

var a = 10;  
function test() {  
    a = 100;  
    console.log(a);  
    console.log(this.a);  
    var a;  
    console.log(a); 
}
test();  

8.

if (!('a' in window)) {
    var a = 1;
}
console.log(a)

9.

var a = 1

function c(a, b) {
    console.log(a)
    a = 2
    console.log(a)
}
c()

10

var val=1;
var obj={
    val:2,
    del:function(){
        console.log(this);                    
        this.val*=2
        console.log(val) 
    }
}

obj.del();

11

var name = "erdong"
var object = {
    name: "chen",
    getNameFunc: function () {
        return function () {
            return this.name;
        }
    }
}
console.log(object.getNameFunc()());

12

var name = "erdong"
var object = {
    name: "chen",
    getNameFunc: function () {
        var that = this;
        return function () {
            return that.name;
        }
    }
}
console.log(object.getNameFunc()());

13

(function() {
  var a = b = 3;
})();
console.log(typeof a === 'undefined');
console.log(typeof b === 'undefined');

14

var a = 6;
setTimeout(function () {
    a = 666;
}, 0)
console.log(a);

15

function fn1() {
    var a = 2
    function fn2 () {
      a++
      console.log(a)
    }
    return fn2
}
var f = fn1()
f()
f()

16

var a = (function(foo){
    return typeof foo.bar;
})({foo:{bar:1}});

console.log(a);

17

function f(){
    return f;
}
console.log(new f() instanceof f);

18

function A () {
}
A.prototype.n = 1

var b = new A();

A.prototype = {
    n: 2,
    m: 3
}
var c = new A()

console.log(b.n, b.m);
console.log(c.n, c.m);

19

var F = function(){}
var O = {};
Object.prototype.a = function(){
    console.log('a')
}
Function.prototype.b = function(){
    console.log('b')
}
var f = new F()

F.a();  
F.b();  
O.a();
O.b();  

20

function Person() {
    getAge = function () {
        console.log(10)
    }
    return this
}

Person.getAge = function () {
    console.log(20)
}

Person.prototype.getAge = function () {
    console.log(30)
}

var getAge = function () {
    console.log(40)
}

function getAge() {
    console.log(50)
}


Person.getAge();
getAge();
Person().getAge();
new Person.getAge();
getAge();
new Person().getAge();

21

console.log(false.toString()); 
console.log([1, 2, 3].toString()); 
console.log(1.toString()); 
console.log(5..toString());

22

console.log(typeof NaN === 'number');

23

console.log(1 + "2" + "2");

console.log(1 + +"2" + "2");

console.log(1 + -"1" + "2");

console.log(+"1" + "1" + "2"); 

console.log( "A" - "B" + "2"); 

console.log( "A" - "B" + 2); 

24

var a = 666;
console.log(++a);
console.log(a++);
console.log(a);

25

console.log(typeof a);
function a() {}
var a;
console.log(typeof a);

26

var a;
var b = 'undefined';
console.log(typeof a);
console.log(typeof b);
console.log(typeof c);

27

var x = 1;
if(function f(){}){
    x += typeof f;
}
 
console.log(x);

28

var str = "123abc";
console.log(typeof str++);

29

console.log('b' + 'a' + +'a'+'a');

30

var obj = {n: 1};
function fn2(a) {
    a.n = 2;
}
fn2(obj);
console.log(obj.n);

31

var x = 10;
function fn() {
    console.log(x);
}
function show(f) {
    var x = 20;
    f();
}
show(fn);

32

Object.prototype.bar = 1; 
var foo = {
    goo: undefined
};

console.log(foo.bar);
console.log('bar' in foo);

console.log(foo.hasOwnProperty('bar'));
console.log(foo.hasOwnProperty('goo'));

33

Object.prototype.bar = 1;

var foo = {
    moo: 2
};
for(var i in foo) {
    console.log(i); 
}

33

function foo1() {
    return {
        bar: "hello"
    };
}
function foo2() {
    return 
    {
        bar: "hello"
    };
}
console.log(foo1());
console.log(foo2());

35

console.log((function(){ return typeof arguments; })());

36

console.log(Boolean(false));
console.log(Boolean('0'));
console.log(Boolean(''));
console.log(Boolean(NaN));

37

console.log(Array(3));

console.log(Array(2,3));

38

console.log(0.1 + 0.2 == 0.3);

39

var a=[1, 2, 3];
console.log(a.join());

40

var a = [3];
var b = [1];
console.log(a - b); 

解析

第1题解析

// 输出
undefined

ReferenceError: b is not defined

var不会产生块级作用域,let会产生块级作用域。

示例代码相当于:

var a;
if(false){
    a = 1;
    let b = 2;
}
console.log(a); 
console.log(b);

第2题解析

// 输出

ReferenceError: Cannot access 'a' before initialization

let声明的变量不会提升,并且会产生暂存死区。在let声明变量之前访问变量会抛出错误。

第3题解析

// 输出

2 1

undefined {n: 2}

var b = a,此时a和b指向同一个对象。

.运算符比 = 运算符高,先计算`a.x`,此时 
b = {
    n:1,
    x:undefined
}

相当于给对象添加了x属性。

a.x = a = {n:2};

计算完a.x,再计算 = ,赋值是从右向左,此时a指向一个新对象。
a = {
    n:2
}

a.x已经执行过了,此时对象的x属性赋值为a,此时

对象 = {
    n:1,
    x:{
        n:2
    }
}

即:
a = {
    n:2
}

b = {
    n:1,
    x:{
        n:2
    }
}


查看运算符优先级

第4题解析

// 输出 

function c(){
    console.log(a);
    var c = 3;
    function a(){
    }
}

function a(){
}

变量提升也有优先级, 函数声明 > arguments > 变量声明

第5题解析

//  输出

1

TypeError: c is not a function

由于函数声明会提升,当函数外的console.log(c)执行时,c已经被赋值为1。因此,执行c(2)时会抛出TypeError,因为1不是函数。

第6题解析

// 输出 

chen

自执行函数执行时,会先进行变量提升(这里涉及到执行上下文不过多说,一定要搞懂执行上下文),在自执行函数执行时,伪代码为:

var name = 'erdong';
(function () {
    var name;  // 变量name会提升到当前作用域顶部
    if (typeof name === 'undefined') {
        name = 'chen'
        console.log(name)
    } else {
        console.log(name)
    }
})();

所以会执行if中的console.log(name)

第7题解析

// 输出

100
10
100

test()为函数独立调用,作用域中的this绑定为全局对象window

test函数执行时,var a被提升到了作用域顶部,因此函数作用域中存在一个变量a。所以在函数中访问的a都是局部作用域中的a

第8题解析

// 输出
undefined

由于if后的{}不会产生块级作用域(不包含let,const时),此时的伪代码为:

var a;
if (!(a in window)) {
    a = 1;
}
console.log(a);

var a相当于window.a。因此!(a in window)转成布尔值为false,不会执行a = 1。所有console.log(a)输出undefined

第9题解析

//输出

undefined 

2

跟第4题类似。

第10题解析

// 输出
obj(指向的值)

1

当通过obj.del()调用del函数时,del函数作用域中的this绑定为obj

在函数作用域中访问val时,由于函数中并没有变量val,因此实际上访问的是全局作用域中的val,即 1

这里考察的是this的指向,一定要熟练掌握。

第11题解析

// 输出

erdong

object.getNameFunc()(),先执行object.getNameFunc()返回一个函数:

function () {
    return this.name;
}

返回的函数再执行,相当于

(function () {
    return this.name;
})();

此时的this绑定为window。因此输出全局变量name的值erdong

第12题解析

//输出

chen

object.getNameFunc()执行时,此时getNameFunc中的this绑定为object,因此that = objectobject.getNameFunc()返回的函数再执行时,产生闭包,因此返回的函数也能访问到外层作用域中的变量that,因此object.nameobject.name,即 chen

第13题解析

// 输出

true

false

首先要明白a = b = 3是怎样执行的,伪代码:

b = 3;
var a = b;

因此在自执行函数执行时,b由于为经var等操作符声明,因为为全局变量。a为函数作用域中的变量。因此在外面访问ab时,其值分别为ReferenceError: a is not defined3。但是typeof检测未声明的变量不会抛出错误,会返回'undefined'。因此typeof atypeof b分别返回'undefined''number'

第14题解析

//输出

6

setTimeout为宏任务。即使设置延迟为0ms,也是等待同步代码执行完才会执行。因此console.log(a)输出 6

第15题解析

// 输出

3
4

由于fn1函数执行后返回函数fn2,此时产生了闭包。因此fn2a访问的是fn1作用域中的变量a,因此第一次a++,之后a3,第二次之后a4

第16题解析

//输出

undefined

实参foo的值为{foo:{bar:1},因此typeof foo.barundefined

typeof foo.foo.barnumber

第17题解析

//输出

false

由于构造函数f的返回值为f。因此new f()的值为f。所以console.log(new f() instanceof f)console.log(f instanceof f),即 false

第18题解析

// 输出

1,undefined

2,3

var b = new A(); 实例化b时,Aprototype

A.prototype = {
    constructor:A,
    n:1
}

当访问b.nb.m时,通过原型链找到A.prototype指向的对象上,即b.n = 1,b.m = undefined

var c = new A(); 实例化c时,Aprototype

A.prototype = {
    n: 2,
    m: 3
}

当访问a.na.m时,通过原型链找到A.prototype指向的对象上,此时A.prototype重写,因此a.n = 2,b.m = 3

第19题解析

// 输出

a
b
a
TypeError: o.b is not a function

F为函数,它也能访问Object原型上的方法,O为对象,不能访问Function原型上的方法。

F的原型链为:

F => F.__proto__ => Function.prototype => Function.prototype.__proto__ => Object.prototype

由于Object.prototypeF的原型链上,所以F能访问Object.prototype上的属性和方法。即: F.a(),F.b()能正常访问。

O的原型链为:

O => O.__proto__ => Object.prototype

由于Function.prototype不在O的原型链上,因此O不能访问Function.prototype上的方法,即O.b()抛出错误。

如果你对原型和原型链掌握的好,试着理解下面的示例:

console.log(Object instanceof Function);

console.log(Function instanceof Object);

console.log(Function instanceof Function);

第20题解析

// 输出

20
40
10
20
10
30

Person.getAge();此时执行的是Person函数上getAge方法。

Person.getAge = function () {
    console.log(20)
}

所以输出:20。

getAge();此时执行的是全局中的getAge方法。此时全局getAge方法为:

function () {
    console.log(40)
}

所以输出:40。

Person().getAge();由于Person()单独执行所以,作用域中的this绑定为window,相当于window..getAge()。同上,执行的都是全局getAge
方法,但是Person执行时,内部执行了

getAge = function () {
    console.log(10)
}

因此全局getAge方法现在为:

function () {
    console.log(10)
}

所以输出:10。

new Person.getAge();此时相当于实例化Person.getAge这个函数,伪代码:

var b = Person.getAge;
new b();

所以输出:20

getAge();执行全局getAge方法,由于在Person().getAge()执行时把全局getAge方法赋值为:

function () {
    console.log(10)
}

所以输出:10。

new Person().getAge();此时调用的是Person原型上的getAge方法:

Person.prototype.getAge = function () {
    console.log(30)
}

所以输出:30。

这里要注意:1.变量提升及提升后再赋值。2.调用构造函数时,带()和不带()的区别。

第21题解析

// 输出

'false'
'1,2,3'
Uncaught SyntaxError: Invalid or unexpected token
'5'

当执行1.toString();时,由于1.也是有效数字,因此此时变成(1.)toString()。没有用.调用toString方法,因此抛出错误。

正确的应该是:

1..toString();
1 .toString();
(1).toString();

第22题解析

//输出

true

NaN为不是数字的数字。虽然它不是数字,但是它也是数字类型。

第23题解析

//输出

'122'
'32'
'02'
'112'
'NaN2'
NaN

首先要明白两点:

  1. +a,会把a转换为数字。-a会把a转换成数字的负值(如果能转换为数字的话,否则为NaN)。
  2. 字符串与任何值相加都是字符串拼接。

console.log(1 + "2" + "2");简单的字符串拼接,即结果为:'122'

console.log(1 + +"2" + "2");这里相当于console.log(1 + 2 + "2");,然后再字符串拼接。即结果为:'32'

console.log(1 + -"1" + "2");这里相当于console.log(1 + -1 + "2");,然后再字符串拼接。即结果为:'02'

console.log(+"1" + "1" + "2");这里相当于console.log(1 + "1" + "2");,然后再字符串拼接。即结果为:'112'

console.log( "A" - "B" + "2");,由于'A' - 'B' = NaN,所以相当于console.log( NaN + "2");, 然后再字符串拼接。即结果为:'NaN2'

console.log( "A" - "B" + 2);同上,相当于console.log(NaN + 2),由于NaN+任何值还是NaN,即结果为:NaN

第24题解析

// 输出

667
667
668

++a先执行+1再执行取值操作。 此时a的值为667。因此输出667

a++先执行取值操作,再执行+1。 此时输出667,随后a的值变为668

--aa--同理。

使用这类运算符时要注意:

1)这里的++--不能用作于常量。比如

    1++; // 抛出错误

2)如果a不是数字类型,会首先通过Number(a),将a转换为数字。再执行++等运算。

第25题解析

// 输出

'function'
'function'

跟第4题类似。函数会优先于变量声明提前。因此会忽略var a

第26题解析

// 输出
'undefined'
'string'
'undefined'

a为声明未赋值,默认为undefined,b的值为字符串'undefined',c为未定义。

typeof一个未定义的变量时,不会抛出错误,会返回'undefined'。注意typeof返回的都是字符串类型。

第27题解析

//输出

1undefined

function f(){}当做if条件判断,其隐式转换后为true。但是在()中的函数不会声明提升,因此f函数在外部是不存在的。因此typeof f = 'undefined',所以x += typeof f,相当于x = x + 'undefined''1undefined'

第28题解析

// 输出

'number'

在24题解析时提到,使用++运算符时(无论是前置还是后置),如果变量不是数字类型,会首先用Number()转换为数字。因此typeof str++相当于typeof Number(str)++。由于后置的++是先取值后计算,因此相当于typeof Number("123abc")。即typeof NaN,所以输出'number'

第29题解析

// 输出

baNaNa

'b' + 'a' + +'a'+'a'相当于'ba' + +'a'+'a',+'a'会将'a'转换为数字类型,即+'a' = NaN。所以最终得到'ba' + NaN +'a',通过字符串拼接,结果为:baNaNa

第30题解析

// 输出

4
2
2

函数传递参数时,如果是基本类型为值传递,如果是引用类型,为引用传递。因此实参aobj指向对象的一个引用。当执行a.n,实际上共同引用的对象修改了,添加了个n属性,因此obj.n2

第31题解析

// 输出

10

JavaScript采用的是词法作用域,它规定了函数内访问变量时,查找变量是从函数声明的位置向外层作用域中查找,而不是从调用函数的位置开始向上查找。因此fn函数内部访问的x是全局作用域中的x,而不是show函数作用域中的x

第32题解析

//输出

1
true
false
true

in操作符:检测指定对象(右边)原型链上是否有对应的属性值。
hasOwnProperty方法:检测指定对象自身上是否有对应的属性值。两者的区别在于in会查找原型链,而hasOwnProperty不会。

示例中对象foo自身上存在goo属性,而它的原型链上存在bar属性。

通过这个例子要注意如果要判断foo上是否有属性goo,不能简单的通过if(foo.goo){}判断,因为goo的值可能为undefined或者其他可能隐式转换为false的值。

第33题解析

// 输出

'moo'
'bar'

for...in...遍历对象上除了Symbol以外的可枚举属性,包括原型链上的属性。

第34题解析

// 输出

{ bar: "hello" }

undefined

两个函数唯一区别就是return后面跟的值,一个换行一个不换行。

当我们书写代码时忘记在结尾书写;时,JavaScript解析器会根据一定规则自动补上;

return
{
    bar: "hello"
}
=> 会被解析成
return;
{
    bar: "hello"
};

因此函数执行后会返回undefined

第35题解析

// 输出

'object'

arguments为类数组,类型为object。因此typeof arguments = 'object'

第36题解析

//输出

false
true
false
fasle

只有下面几种值在转换为布尔值时为false:

+0,-0,NaN,false,'',null,undefined。

除此之外的值在转换为布尔值的时候全部为true

第37题解析

// 输出

[empty × 3] 

[2,3]

使用Array()创建数组时,要注意传入的值的类型和数量。

// 输出

false

第39题解析

//输出

1,2,3

join方法如果省略参数,默认以,分隔。

第40题解析

// 输出

2

在执行a - b时,ab都要转换为数字。首先a先转换为字符串,[3] => [3].toString() => '3',然后Number(3) => 3b同理。因此转换之后为3 - 1 = 2

最后

如果文中有错误,请务必留言指正,万分感谢。

点个赞哦,让我们共同学习,共同进步。

GitHub

查看原文

Evelyn 发布了文章 · 2020-03-04

Vue + TypeScript 项目之性能优化

Vue + TypeScript 项目之性能优化

项目分析

项目打包分析工具: webpack-bundle-analyzer

vue-cli 3 安装:

npm intall webpack-bundle-analyzer -–save-dev
或 yarn add webpack-bundle-analyzer

vue.config.js 增加配置:

chainWebpack: (config) => {
    /* 添加分析工具*/
    if (process.env.NODE_ENV === 'production') {
        if (process.env.npm_config_report) {
            config
                .plugin('webpack-bundle-analyzer')
                .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
                .end();
            config.plugins.delete('prefetch')
        }
    } 
}

安装完成后,运行


npm run analyz 或者 yarn run analyz
会在浏览器打开:http://127.0.0.1:8888,一个项目打包的情况图,便于直观地比较各个 bundle文件的大小


资源优化

图片优化

  1. 尽量把项目中使用到的图片资源压缩到最小(较为推荐的压缩工具:https://tinypng.com/
  2. 使用雪碧图
  3. 使用 iconfont

路由懒加载

在 router.js文件中,项目开始使用的是静态引用方式:

import HelloWord from '@/components/HelloWorld'
routes:[ path: '/hello', name: 'HelloWorld', component: HelloWorld ]

后来改成了:


routes:[ 
     path: '/hello',
     name: 'HelloWorld',
     component: () => import('./components/HelloWorld')
 ]

包优化

  1. 注意按需引用,项目中没用到的包,不要擅自引入,有些包占用的资源挺多的
  2. 如果引入的包比较大,而且使用的时候只是用了其中某一个或者是极少部分的功能,最好使用其他可行的方案取而代之(如:本次项目中,引入的 lodash 这个包,仅仅使用了其中的 _merge 这个方法,而且只用了一次,所以最后用原生写了个方法取而代之)
  3. 移动端页面调试工具:eruda,虽然在使用的时候可以做环境判断,到那时打包发布的时候 eruda 依然被打包进去了,导致打包后的 js 文件无端增加了好几百 k,发现之后果断在发布之前把它注释掉了。这样每次在发布之前都需要把 eruda 注释掉,很多时候会忘记,所以但是这只是一定程度上解决了当前问题,最后希望的结果是:引入 eruda 的代码在发布之前不需要注释,而且 eruda 不会被打包。目前还没有想到更好的解决方法

接口请求

async/await 的利用

以后写的文章也会更新在自己的公众号,而且公众号还有其他的干货,路过的同学可以支持下

qrcode.jpg

查看原文

赞 0 收藏 0 评论 0

Evelyn 发布了文章 · 2020-01-16

vue + typescript 项目初体验

为期一个月的后台管理项目,在模版的基础上对路由和权限做了一定的更改以适用于业务。

1、业务组件的封装

把操作按钮封装在组件内部,整个按钮作为一个独立组件,在组件内控制当前 dialog 的显示隐藏,各类操作也在组件内完成,对外只需传入一个标识,暴露出一个关闭方法,用于操作完成后更新列表。

2、项目目录结构
单个页面的所有文件置于同一文件夹下,然后在 store 中注册

3、namespace 的使用

4、路由命名:简洁高可读性

5、TS 接口可读性、可维护性、可扩展性(与服务端商议,保持数据结构一致)

6、Proxy代理:将请求的域名指向 “target” 里面的 URL,找到 “pathRewrite” 里面的部分,将其替换成实际需要的部分

7、Jenkins发布:后端配置好 nginx,前端 server 通过 nginx 配置,指向 WebServer

8、权限相关:权限过滤 - 交集,权限匹配

9、自定义指令

10、路由渲染,根据当前产品需求,

11、所有涉及表单提交的,尽量使用 element-ui 表单校验。充分利用 form 表单的属性(rules, ref),在使用 this.$refs 时需要先声明

1、组件

·业务组件
    -- 注意自上而下,耦合性
·通用组件
    -- 高度可扩展性
    
    
项目完结时间太久,未完待回忆......
查看原文

赞 0 收藏 0 评论 0

Evelyn 发布了文章 · 2020-01-16

React Next 项目初体验

项目背景

国际化官网

项目结构

|-- .next  
|-- components --------------- 组件
|-- lang --------------------- 多语言
|-- pages -------------------- 页面
|-- scripts ------------------ 脚本文件
|-- static ------------------- 静态资源
|-- utils -------------------- 自定义或其他库以及封装文件
|-- .babelrc
|-- .eslintignore
|-- .eslintrc
|-- .gitignore
|-- .prettierrc
|-- next.config.js
|-- package.json
|-- prettier.config.js
|-- README.md
|-- server.js
|-- yarn.lock

项目搭建

多语言:react-intl

样式:起初使用的是 jss,后来改成了引入外部样式文件

http 请求:data-fetch

UI 库:antd-design-less

路由:withRouter

所有示例均参考自 next 官方

踩坑

痛点:

  1. 国际化语言切换
  2. 自定义字体不生效
  3. 样式使用 import 引入到对应 jsx 文件,发布到生产环境之后样式不立马生效
  4. antd-design 表单样式发布到生产环境同上

填坑

  1. 最终采用将语言作为路由查询参数,跳转时带向各页面,配合使用 Ant Design 的 ConfigProvider
  2. 自定义样式配置
  3. 在 next.config.js 文件中增加用户自定义样式配置代码

    const themeVariables = lessToJS(fs.readFileSync(path.resolve(__dirname, './static/style/antd-custom.less'), 'utf8'));

    不在单个文件中引入相应的样式文件,而是把样式文件都在用户自定义样式文件先引入

  4. 在 .babelrc 文件将 antd 库作为插件(plugins)引入,在 static 文件夹下新建 style 文件夹(存放样式文件),新建 index.less 文件,在文件中引入 antd 样式

    @import '~antd/dist/antd.less';
  5. 生产环境发起请求需要,设置反向代理(express)

GET

获取当前 URL 参数:在钩子 componentDidMount 里面获取

```

componentDidMount() {
    const search = window.location.search;
    console.log(search); // 获取 url ? 后带的参数
}

```

注意

不要自定义静态文件夹的名字,只能叫 static ,因为只有这个名字 Next.js 才会把它当作静态资源。(出自文档:next文档

Ending

看过那么多别人写的 react 项目,这是第一个自己上手的实战项目,还用到了 next.js 做服务端渲染。虽然项目很简单,几乎纯展示,难度系数较低,但对于 react 入门级小白而言,第一次参与 react 实战,还是从零到一自己搭建(当然,期间有大佬相助),还是能有所收获。

因为之前写 vue 项目的时候学到了该如何的划分组件,怎么定义组件,所以对于页面和组件拆分几乎没有问题。此次实战,对 jsx 语法有了更进一步的了解,但是对 react 的生命周期还不是很了解,至少没有在此次项目中体现。对于组件间的传值,会用大于理解。对于 react 高阶组件,有待深入体验。

查看原文

赞 0 收藏 0 评论 4

Evelyn 收藏了文章 · 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。
座右铭:你想要在外人看来毫不费力,那你就要平时不断努力~

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

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

查看原文

Evelyn 发布了文章 · 2019-11-13

Next.js 集成 React + Antd-design 踩坑记

作为一个 React 小白(至今未写过 react 项目),接到需求的第一步就是选择 Google,以为自己很 lucky,搜到了标题为 Typescript+Ant-Design + Redux+Next.js搭建服务端渲染框架 的一篇文章,粗略看了下立马开撸,毕竟实践才能发现真理,撸完之后项目跑得非常完美,以为自己的工作已经完成百分之五十了,因为项目要做国际化,要实现多语言,于是又是一波 Google,刚好有一篇 Next.js 配置 react-intl 实现语言国际化 的文章,以为自己可以很顺利的完成这个框架的搭建,万万没想到,大坑不断,先是因为文章 demo 中调用的一些 API 在新版本被弃用了,然后去 github 上看 react-intl 官方给的 demo,并想寻找可替代方案,但是越改报错越多,虽然程序员制造 bug 不奇怪,但是越来越多不可预测的 bug 和坑,让我果断选择放弃当前方案,选择求助。

小伙伴给了个建议(很棒的建议),去看 Next.js 官方给出的例子,把需要的自己拼凑起来改一改。(第一次接触 next,并不知道它官方给出了那么多demo,可以说是很全面了,一目了然。
于是进度 50% 变成了 0。开始从头梳理我做的这个项目需要参考的 demo 主要有哪些:

* 多语言     -- react-intl(官方推荐)
* 样式       -- jss 
* UI 库      -- antd-design-less
* 路由       -- using-with-router
* http 请求  -- data-fetch   

先从多语言下手,进度还算顺利,但是想用中文的时候,发现控制台报错,于是去查看官方 demo 里的 README 文档,
WeChat6ccd89a7e9e8a74388c8786fd1fe8b5f.png
意思大致是只有生产环境才能看到效果,所以必须先 build,然后再启动项目多语言这一块才不会有警告和报错。

这一步完成之后,接着拼上了样式的 demo,这一块比较顺利,但是后来发现当前页面的样式在页面加载时是生效的,但是刷新当前页面,样式将不再生效,暂时还没找到原因,还没填上这个坑,正在求助中。。。

第三步,拼凑的是 UI 库,coding 过程还算顺利,没有报什么看不懂的错误和警告,至少控制台没有出现一片红色的情况,但是 antd-design 样式没有生效,不知道是哪一步出了故障,目前也没找到原因,靠自己是填不了这个坑了,也是正在求助中。。。

原因:没有配置 antd-design 插件
解决方案:修改 .babelrc 文件,改为如下所示    

WeChat82be0f5925eddc89a1e6496c000498dc.png

第四步,加上路由,暂时没有踩到坑,比较顺利的加上了 demo。

发现刷新当前页面时报错,尚未解决。

WeChat4d2b63bbc878624d754ca122ada40bc6.png

第五步,加 http 请求。也还算顺利的改造了下代码,暂时无坑。

最后,优化了下项目目录结构,基础框架搭建完毕。

这里面花费时间最多的是多语言,一开始是方案不正确,然后想改造官方多语言 demo,想写成之前 vue 项目里面的多语言那样,把文件夹分的细一些,但是没有成功,越改报错越多,最后通过不了编译,选择放弃。

总结:对于一个第一次写 React 项目的 react 小白来说,第一步就需要自己搭框架,觉得跨度有点大,有点困难,而且还需要做服务端渲染,这一块也是不熟悉的,之前从未接触过的。通过目前踩坑的情况来看,一定要多看官方给出的文档例子啥的,虽然于自己而言源码几乎是看不懂的,但是仔细阅读文档和研究官方给的例子,可以少踩一些坑,少走很多弯路。

It’s much better to think more and then do it than to get started.

查看原文

赞 0 收藏 0 评论 0

Evelyn 收藏了文章 · 2019-04-15

2019前端工程师自检清单与思考

2019前端工程师自检清单

对于JavaScript,掌握其语法和特性是最基本的,但是这些只是应用能力,最终仍旧考量仍然是计算机体系的理论知识,所以数据结构,算法,软件工程,设计模式等基础知识对前端工程师同样重要,这些知识的理解程度,可以决定你在前端工程师这条路上能走多远,是时候进行一波自检了,查漏补缺,只有全面发展,才会使你更强,以下会进行自问的形式按照图片里面的知识点进行自我check

知识点在面试过不下50人后进行的一波总结

可能不全,有新增的点可以放在评论区讨论

前端技术栈


1.你关于性能优化是否只知道js文件摆放顺序、减少请求、雪碧图等等,却连衡量指标window.performance.timing都不清楚是干什么的?

2.请你描述下一个网页是如何渲染出来的,dom树和css树是如何合并的,浏览器的运行机制是什么,什么是否会造成渲染阻塞?

3.请简述下js引擎的工作原理,js是怎样处理事件的eventloop,宏任务源tasks和微任务源jobs分别有哪些?js是如何构造抽象语法书(AST)的?

4.你是否考虑全面你编写的整个函数,或者整个功能的容错性与扩展性?怎样构建一个组件是最合理最科学的,对于错误的处理是否有统一的方式方法?

5.浏览器缓存的基本策略,什么时候该缓存什么时候不该缓存,以及对于控制缓存的字段的相关设置是否清楚?

6.你是否可以利用面向对象的思维去抽象你的功能,你会构建一个class(ES6)吗?你对于前端架构的理解?

7.你会用VUE,你会用React,你读得懂这两个架构的源码吗?你懂他俩的基本设计模式吗?让你去构建一个类似的框架你如何下手?

8.你了解的ES6只是const、let、promise吗?你考虑过ES6提出的真正趋势吗?

9.你会用less,那么让你去写一个loader你可以吗?

10.webpack你也会用,你了解其中原理吗?你知道分析打包依赖的过程吗?你知道tree-shakeing是如何干掉无用重复的代码的吗?

11.你真的熟练使用css吗,那你知道position有几个属性吗,具体参考https://github.com/wintercn/b...

12.你了解js的数据结构吗?基本数据类型有哪些?复杂数据类型有哪些?在内存是如何表现的?

13.你可以用js去实现一个单向、双向、循环链表吗?你可以实现查找、插入、删除操作吗?

14.你了解基本常见算法吗?快速排序写一个?要是限制空间利用你该如何写?

15.你了解贪心算法、动态规划、分治算法、回溯算法等常见的算法吗?

16.你是如何理解前端架构的?你了解持续集成吗?

17.你了解基本的设计模式吗?举例单例模式、策略模式、代理模式、迭代模式、发布订阅模式。。。?

18.写一个事件监听函数呗?实现once、on、remove、emit功能

19.node.js的实现层是什么?

20.node的事件循环机制是怎样的?

21.node的child_process模块有几个api,分别的作用是什么?

22.http1.0与1.1协议的区别?

23.node是如何实现http模块的?

24.如何构建一个主从模式?

25.nginx相关配置了解过吗?

26.你真的理解前端吗?


有落下的方面,请在评论区写下您宝贵的看法,我会第一时间加入进来!!!

虽然有些方面还未补足,但是可以作为参考~
查看原文

认证与成就

  • 获得 26 次点赞
  • 获得 23 枚徽章 获得 0 枚金徽章, 获得 6 枚银徽章, 获得 17 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-07-05
个人主页被 1.8k 人浏览