Git Product home page Git Product logo

blog's Introduction

嗨! 我是 win👋, 一个光吃不胖的大叔。

My GitHub stats Languages

𝗠𝘆 𝗧𝗲𝗰𝗸 𝗦𝘁𝗮𝗰𝗸

HTML5 CSS3 JavaScript TypeScript Vue.js React

Less Sass Stylus TailwindCss Windicss

Webpack Rollup Vite ESlint Git GitLab VS Code Netlify

My opensource activieies:

blog's People

Contributors

ly2011 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

CSS如何实现文字两端对齐

CSS如何实现文字两端对齐

关键

  1. text-align: justify
  2. after、before伪元素

具体实现

<!DOCTYPE html>
<html>
<head>
  <title>demo</title>
  <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  <style type="text/css">
  * {
    margin: 0;
    padding: 0;
  }
  .row>div:first-of-type {
    /* width: 500px; */
    height: 50px;
    line-height: 50px;
    font-size: 12px;
    border: 1px solid red;
    text-align: justify;
  }
  .row>div:first-of-type:after {
    content: " ";
    display: inline-block;
    width: 100%;
  }
  </style>
</head>
<body>
<div class="container">
  <div class="row">
    <div class="col-md-4 col-xs-4 col-sm-4 col-lg-4">姓名</div>
    <div class="col-md-8 col-xs-8 col-sm-8 col-lg-8"></div>
  </div>
  <div class="row">
    <div class="col-md-4 col-xs-4 col-sm-4 col-lg-4">手机号码</div>
    <div class="col-md-8 col-xs-8 col-sm-8 col-lg-8"></div>
  </div>
    <div class="row">
    <div class="col-md-4 col-xs-4 col-sm-4 col-lg-4">验证码</div>
    <div class="col-md-8 col-xs-8 col-sm-8 col-lg-8"></div>
  </div>
    <div class="row">
    <div class="col-md-4 col-xs-4 col-sm-4 col-lg-4">密码</div>
    <div class="col-md-8 col-xs-8 col-sm-8 col-lg-8"></div>
  </div>
</div>
</body>
</html>

image

参考

  1. https://segmentfault.com/a/1190000011336392
  2. https://zhuanlan.zhihu.com/p/53428937

lazyload - 图片懒加载

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

<head>
    <title>LazyLoad</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        img {
            display: block;
            margin-bottom: 50px;
            width: 400px;
            height: 500px;
        }
    </style>
</head>

<body>
    <img src="images/default.jpg" data-src="http://lofter.nos.netease.com/sogou-RkZCZmhNZldvRy1NYTViWHFyYjZSZl9NUE5pM0hTN0pLSmZGSko3N3JUTXVfakNBMFVBbVVTaW9FVDhPOGNJdw.jpg" alt="">
    <img src="images/default.jpg" data-src="http://lofter.nos.netease.com/sogou-ZHk5Y1ZCdHB6SWU4VDdhdWRsTzgxSmFCaThmZUJYQjRScngwd3N6M0NPY0h1ZFo0TzhoZnR0enpDRUVOTEdKWQ.jpg" alt="">
    <img src="images/default.jpg" data-src="http://lofter.nos.netease.com/sogou-VjJKN2c3WDRZQXJTM2tabjkxM29yRzQxMkRSUjdFZV9OWHJVT2xueXlBeUY0VU1sZ1lUbFZscUwtLWtSVms1Uw.jpg" alt="">
    <img src="images/default.jpg" data-src="http://lofter.nos.netease.com/sogou-TW82T2hHdk9iMlM4WGNadjU5bE0yNmRSTGZ6ZVlCNVdWWThORmR4eXJoaWdnel9tanAyWXBVemxZQWpDVjZydw.jpg" alt="">
    <img src="images/default.jpg" data-src="http://lofter.nos.netease.com/sogou-aTQtNE5hVzVZRDFLZTFkeWVZNWRSbkZFXzhaMDhTblRFSXBieklXUUQ0LUhKVUxkNTZlZ3ZiTmVuRkRVVHRFNg.jpg" alt="">
    <img src="images/default.jpg" data-src="http://lofter.nos.netease.com/sogou-eXFRQzQ4Tm92OEQ1VVl4b0pkWWdTQXFLUmJDS3BSX2dWR1VJT3pVZlZnM2w3YV9zS3lkT2VVZUVQckI4dDNCYg.jpg" alt="">

    <script>
        let images = document.getElementsByTagName('img')
        let num = images.length
        let n = 0 // 存储图片加载到的位置, 避免每次都从第一张图片开始遍历

        function lazyload() {

            console.log('lazyload...')

            const seeHeight = document.documentElement.clientHeight // 可视区域高度
            const scrollTop = document.documentElement.scrollTop || document.body.scrollTop // 滚动条距离顶部高度
            for (let i = n; i < num; i++) {
                if (images[i].offsetTop < seeHeight + scrollTop) {
                    if (images[i].getAttribute('src') === 'images/default.jpg') {
                        images[i].src = images[i].getAttribute('data-src')
                    }
                    n = i + 1
                }
            }
        }

        /**
         * 节流函数
         * fun 要执行的函数
         * delay 延迟时间
         * time 在 time 时间内必须执行一次
         */

        function throttle(fun, delay, time) {
            let timeout = null
            let startTime = new Date()

            return function() {
                var context = this
                var args = arguments
                var curTime = new Date()

                clearTimeout(timeout)

                // 如果达到了规定的触发时间间隔, 触发 handler
                if (curTime - startTime >= time) {
                    fun.apply(context, args)
                    startTime = curTime // 还没达到触发间隔, 重新设定定时器
                } else {
                    timeout = setTimeout(function() {
                        fun.apply(context, args)
                    }, delay)
                }
            }
        }

        lazyload() // 页面载入完毕初始化首页(可视区域)的页面图片
        window.addEventListener('scroll', throttle(lazyload, 500, 1000), false)
    </script>
</body>

</html>

Chrome DevTools 中你可能不知道的调试技巧

Scroll Into View 滚动入视图内

Elements 标签内,查看页面元素的时候,如果当前这个元素不在视图内,可以通过这个方法让这个元素快速滚入视图中。

操作:

  • Elements 标签页中选择一个不在视图内的元素 -右击,选择 Scroll into view

Copy As Fetch 复制为 Fetch

Network 标签下的所有请求,都可以复制为一个完整的Fetch` 请求的代码。
操作:

  • Network 标签中,选中一个请求
  • 右击,选择 Copy --> Copy as Fetch

阻塞请求

Network 标签页下,选中一个请求,右击该请求,选择 Block request domainBlock request URL, 可以分别阻塞该请求在 domain 下的所有请求和该请求。

手动给元素添加一个点击事件监听

debug的时候,有时候需要在元素的点击事件监听函数中,将该点击事件对象打印出来。有个更方便的方式,是可以直接在 Elements 标签页为页面元素添加事件监听事件。
操作:

  • Elements 标签页中选中一个页面元素(选中之后,默认可以通过 $0 变量获取到该元素)
  • Console标签页中,调用函数 monitorEvents, 输入两个参数,第一个是当前元素($0), 第二个是事件名(click)
  • Enter 后,当被选中的元素触发了点击事件之后,Console 标签页会将该点击事件对象打印出来

拖动页面元素

Elements 标签页,你可以拖动任何 HTML 元素,改变它在页面中的位置。
操作:如下图。

DOM 断点调试

基本上大家都会用JavaScript 的断点调试,但是应该很多人不知道 DOM 节点也可以进行断点调试。
Chrome DevTools 提供了三种针对 DOM 元素的断点调试:子元素改变时属性改变时元素被移除时
操作:

  • Elements标签页,选中一个元素
  • 右击,选择Break on --> subtree modifications (或 attribute modificationsnode removal)

截屏

在新版本的 Chrome中,提供了一个截图的 API,你可以将整个页面截图或者截取部分页面元素,且截取的图片尺寸跟浏览器当前视图中要截取的内容所占尺寸一致。截图输出的是 png 格式的图片,会自动通过浏览器下载到默认的目录下。现在有三种截取的方式:

截取页面部分元素的操作:

  • CMD + SHIFT + P(windows 中用 CTRL + SHIFT + P) 打开命令菜单
  • Elements 标签页,选中要截取的页面元素
  • 选中 Capture node screenshot

截取完整页面的操作

  • CMD + SHIFT + P(windows 中用 CTRL+SHIFT+P)打开命令菜单
  • 选择 Capture full size screenshot (不需要选择页面元素)

截取当前视图内的页面

  • CMD + SHIFT + P(windows 中用 CTRL + SHIFT + P)打开命令菜单
  • 选择 Capture screenshot (不需要选择页面元素)

缓存上一步操作的结果

Chrome DevTools 上运行 JavaScript 表达式的时候,可以使用 $_ 来获取到上一步操作的返回值。

Overrides 重写

Chrome DevTools 上调试 CSSJavaScript, 修改的属性值在重新刷新页面时,所有的修改都会被重置。如果你想把修改的值保存下来,刷新页面的时候不会被重置,那就看看下面这个特性 Overrides 吧。 Overrides 默认是关闭的,需要手动开启,开启的步骤如下。
开启的操作:

  • 打开 Chrome DevToolsSources 标签页
  • 选择 Overrides 子标签
  • 选择 +Select folder for overrides, 来为 Overrides 设置一个保存重写属性的目录

前端面试常见问题汇总

前端面试常见问题汇总

CSS

1. @mixin 中的 @include@extend 的区别(sass)

列举不同的清除浮动的技巧

/*  1. 添加新元素  */
<div class="outer">
  <div class="div1"></div>
  <div class="div2"></div>
  <div class="div3"></div>
  <div class="clearfix"></div>
</div>

.clearfix {
  clear: both;
}

/* 2. 为父元素增加样式 */
.clearfix {
  overflow: auto;
  zoom: 1; // 处理兼容性
}

/* 3. :after 伪元素方法(作用于父元素) */
.outer {
  zoom: 1;
  &:after {
    visibility: hidden;
    display: block;
    font-size: 0;
    content: " ";
    clear: both;
    height: 0;
  }
}

CSS 实现移动端 1px 线条的绘制

  • 先放大,再缩小

方法1:

.box:before {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        right: 0;
        height: 1px;
        content: '';
        -webkit-transform: scaleY(.5);
        transform: scaleY(.5);
        background-color: #e2e2e2;
    }

方法2:

.box:before{
    content: "";
    pointer-events: none; /* 防止点击触发 */
    box-sizing: border-box;
    position: absolute;
    width: 200%;
    height: 200%;
    left: 0;
    top: 0;
    border:1px solid #000;
    -webkit-transform(scale(0.5));
    -webkit-transform-origin: 0 0;
    transform(scale(0.5));
    transform-origin: 0 0;
}

缺点:

  1. 占据了该元素的伪元素,代码量相对较多
  2. input元素没有伪元素

2. 用 css 实现垂直居中(子元素宽高不确定?)(兼容ie8?)

  • 方案一:使用css3的新属性transform:translate(x,y)属性
.eight {
    position: absolute;
    width: 80px;
    height: 80px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    -webkit-transform: translate(-50%, -50%);
    -moz-transform: translate(-50%, -50%);
    -ms-transform: translate(-50%, -50%);
    background: green;
}

这个方法可以不需要设定固定的宽高,在移动端用的会比较多,在移动端css3兼容的比较好

3. 如果设计中使用了非标准的字体,你该如何去实现?

4. 行内元素有哪些?块级元素有哪些?空元素有哪些?

  • 行内元素有:span、img、input、select、strong
  • 块级元素有:div、ul、ol、dl、dt、dd、h1、h2、h3、h4、p...
  • 常见的空元素有:br、hr、img、input、link、meta、、base、area、command、embed、keygen、param、source、track、wbr...

5. 两个div进行两列布局,要求高度不定(父元素也是),我需要两个div实时等高,即左边div高度被其内子元素撑高时,右边的div高度和左边同步【等高布局

  • 父元素 display: table; 左边定宽 width: 200px; display: table-cell;右边 width: 100%;
  • 父元素 display: flex; align-item: stretch; 左边定宽 width: 200px; 右边设置项伸展属性: flex-grow: 1;

6. 按照下图,说说flexbox的原理

JavaScript

1、编写一个函数将列表子元素顺序反转

2、请实现一个Event类,继承自此类的对象都会拥有两个方法on,off,once和trigger

3、如何创建一个ajax请求(https://zhuanlan.zhihu.com/p/27776535)

创建Ajax的过程:
1)创建XMLHttpRequest对象(异步调用对象)

var xhr = new XMLHttpRequest();

2)创建新的Http请求(方法、URL、是否异步)

xhr.open('get', 'example.php', false);

3)设置响应HTTP请求状态变化的函数
onreadystatechange事件中readyState属性等于4。响应的HTTP状态为 200(OK)或者304(Not Modified)。
4)发送http请求

xhr.send(data);

5)获取异步调用返回的数据

详细过程:

const xhr = new XMLHttpRequest();
xhr.open(method, url, async);

// send 方法发送请求,并接受一个可选参数
// 当请求方式为 post 时,可以将请求体的参数传入
// 当请求方式为 get 时,可以不传或传入 null
// 不管是 get 还是 post,参数都需要通过 encodeURIComponent 编码后拼接
xhr.send(data);
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    // HTTP 状态在 200 -300 之间表示请求成功
    // HTTP 状态为 304 表示内容未发生改变,可直接从缓存中获取
    if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
      console.log('请求成功', xhr.responseText);
    }
  }
}

// 超时时间单位为毫秒
xhr.timeout = 1000

// 当请求超时时,会触发 ontimeout 方法
xhr.ontimeout = () => console.log('请求超时')

4. javascript 连等赋值问题

var a = { n: 1 };
var b = a;
a.x = a = { n: 2 }
console.log(a.x); // undefined
console.log(b.x); // {n:2}

解析:
解析器在接收到 a = a.x = { n: 2 } 这个语句后,会这样子做:

  1. 在找到 a 和 a.x 的指针。如果已有指针,那么不改变它。如果没有指针, 即那个变量还没被申明,那么就创建它,指向 null。
    a 是有指针的,指向 {n: 1}; a.x 是没有指针的,所以创建它,指向 null。

  2. 然后把上面找到的指针,都指向最右赋值的那个指,即是 {n: 2}

5. JS数组深浅复制

浅拷贝:

  • slice 实现
var arr = ['old', '1' , true, null, undefined];
var new_arr = arr.slice();
new_arr[0] = 'new';
console.log(arr);
console.log(new_arr);
  • concat 实现
var arr = ['old', '1' , true, null, undefined];
var new_arr = arr.concat();
new_arr[0] = 'new';
console.log(arr);
console.log(new_arr);
注释:如果数组元素是基本类型,就会拷贝一份新的;但是如果数组元素是对象或者数组,就只会拷贝引用(类似指针),修改其中一个就会影响另外一个。例如:
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];
var new_arr = arr.concat();
new_arr[0] = 'new';
new_arr[3][0] = 'new1';
console.log(arr) // ["old", 1, true, ['new1', 'old2'], {old: 1}]
console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}]

深拷贝

  • JSON.stringify 实现数组深拷贝
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];
var new_arr = JSON.parse(JSON.stringify(arr));
new_arr[0] = 'new';
new_arr[3][0] = 'new1';
console.log(arr) // ["old", 1, true, ['old1', 'old2'], {old: 1}]
console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}]
缺点:简单粗暴,但是不能拷贝函数,不推荐。

手动实现深浅拷贝

  • 浅拷贝
var shallowCopy =  function (obj) {
  // 判断是否是数组或者对象
  if (typeof obj !== 'object') {
        return obj;
  }
  var newObj = obj instanceof Array ? [] : {};
  for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key]
        }
  }
  return newObj;
}
  • 深拷贝
var deepCopy = function (obj) {
    // 判断是否是数组或者对象
    if (typeof obj !== 'object') {
        return obj;
    }
    var newObj =  obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

实现一个深度复制的deepCopy 函数

6. 实现dom的增删改查复制

// 创建节点
createDocumentFragment()
createElement()
createTextNode()

// 添加 移除 替换 插入
appendChild()
removeChild()
replaceChild()
insertBefore()

// 查找
document.getElementsByTagName()
document.getElementsByName()
document.getElementsByClassName()
document.getElementById()
document.querySelector()
document.querySelectorAll()

7. 谈谈您对 MVC、MVVM 的理解

MVC

- 视图(View):用户界面。
- 控制器(Controller):业务逻辑。
- 模型(Model):数据保存。

各部分之间的通讯方式如下:

default

1、View 传送指令到 Controller
2、Controller 完成业务逻辑后,要求 Model 改变状态
3、Model 将新的数据发送到 View,用户得到反馈

特点:

所有通讯都是单向的。

MVP

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

default

1、各部分之间的通讯,都是双向的。
2、View 与 Model 不发生联系,都通过 Presenter 传递。
3、View 非常薄,不部署任何业务逻辑,被称为 “被动视图”(Passive View),即没有任何主动性,而 Presenter 非常厚,所有逻辑都部署在那里。

MVVM

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

default

唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反应在 ViewModel,反之亦然。

8. vue 的生命周期钩子函数

    1. beforeCreate
    1. created
    1. beforeMount
    1. mounted
    1. beforeUpdate
    1. updated
    1. actived
    1. deactivated
    1. beoforeDestory
    1. destoryed

9. 使用正则实现电话号码和邮箱的验证

10. 使用正则实现trim函数删除字符串前后空格

function trim(str) {
  return str.replace(/(^\s*)|(\s*$)/g, '');
}

11. BOM对象,DOM对象

BOM对象:

  • window对象
  • location对象
  • navigator对象
  • screen对象
  • history对象

DOM对象

  • 常用的DOM方法:

    • getElementById(id)
    • getElementsByTagName()
    • appendChild(node)
    • removeChild(node)
    • replaceChild()
    • insertChild()
    • createELement()
    • createTextNode()
    • getAttribute()
    • setAttribute()
  • 常用的DOM属性:

    • innerHTML 节点(元素)的文本值
    • parentNode 节点(元素)的父节点
    • childNodes
    • attributes 节点(元素)的属性节点

参考: HTML DOM 方法

12. 任务队列优先级(Promise、setTimout、setImmediate)

下面代码输出结果?为什么?

demo1:

setTimeout(() => console.log('a'), 0);
var p = new Promise((resolve) => {
  console.log('b');
  resolve();
});
p.then(() => console.log('c'));
p.then(() => console.log('d'));
console.log('e');
// 结果:b e c d a
// 任务队列优先级:promise.Trick()>promise的回调>setTimeout>setImmediate

demo2:

async function async1() {
    console.log("a");
    await  async2(); //执行这一句后,await会让出当前线程,将后面的代码加到任务队列中,然后继续执行函数后面的同步代码
    console.log("b");

}
async function async2() {
   console.log( 'c');
}
console.log("d");
setTimeout(function () {
    console.log("e");
},0);
async1();
new Promise(function (resolve) {
    console.log("f");
    resolve();
}).then(function () {
    console.log("g");
});
console.log('h');
// 谁知道为啥结果不一样?????????????
// 直接在控制台中运行结果:      d a c f h g b e
// 在页面的script标签中运行结果:d a c f h b g e

13. js中事件代理:

  <ul id="ui-test">
    <li>item1</li>
    <li>item2</li>
    <li>item3</li>
    <li>item4</li>
    <li>item5</li>
    <li>item6</li>
    <li>item7</li>
    <li>item8</li>
    <li>item9</li>
    <li>item10</li>
  </ul>

  <script>
  document.addEventListener('DOMContentLoaded', function(event) {
  var oUL = document.querySelector('#ui-test')
  // var oLI = document.querySelectorAll('#ui-test > li')
  // for (var i = 0, len = oLI.length; i < len; i++) {
  //   console.log(oLI[i]);
  //   oLI[i].addEventListener('click', function() {
  //     console.log(this.innerHTML)
  //   })
  // }

  // 代理模式
  oUL.addEventListener('click', function(ev) {
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    if (target.nodeName.toLowerCase() === 'li') {
      console.log(target);
      console.log(target.innerHTML)
    }
  })
});
  </script>

14. js实现数组去重怎样实现?

  • 1、创建一个临时数组来保存数组中已有的元素
  • 2、使用哈希表存储已有的元素
  • 3、使用 indexOf 判断数组元素第一次出现的位置是否为当前位置
  • 4、先排序再去重
  • 5、使用 Set

找出数组中的最大值

  • reduce
var arr = [6, 4, 1, 8, 2, 11, 3];
function max (prev, next) {
    return Math.max(prev, next)
}
console.log(arr.reduce(max));
  • apply
var arr = [6, 4, 1, 8, 2, 11, 3];
console.log(Math.max.apply(null, arr)); // 11
  • ES6
var arr = [6, 4, 1, 8, 2, 11, 3];
function max (arr) {
    return Math.max(...arr);
}
console.log(max(arr));

打乱数组的方法

var arr = [];
for(var i = 0; i < 100; i++) {
    arr[i] = i;
}
arr.sort(function() {
    return 0.5 - Math.random();
})
console.log(arr)

数组扁平化

var arr = [1, [2, [3, 4]]]
function flatten(arr) {
    while(arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }

    return arr;
}

console.log(flatten(arr)); // [1, 2, 3, 4]

简单的字符串模板

var TemplateEngine = function(tpl, data) {
    var re = /<%([^%>]+)?%>/g,
        match;
    while(match = re.exec(tpl)) {
        console.log('match0: ', match[0]);
        console.log('match1: ', match[1]);
        tpl = tpl.replace(match[0], data[match[1]])
    }
    return tpl;
}

var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.<p>';
console.log(TemplateEngine(template, {
    name: 'ly2011',
    age: 16
}))

函数函数柯里化

函数柯里化:在一个函数中首先填充几个参数(然后再返回一个新函数)的技术称为柯里化(Currying)。

  • 通用的柯里化方法
// 通用的函数柯里化构造方法
function curry(fn) {
  // 新建 args 保存参数,注意,第一个参数应该是要科利华的函数,所以 args 里面去掉第一个
  var args = [].slice.call(arguments, 1);

/*  // 新建 _fn 函数作为返回值
  var _fn = function() {
    // 参数长度为 0, 执行 fn 函数,完成该函数的功能
    if(arguments.length === 0) {
      return fn.apply(this, args)
    } else {
      // 否则,存储参数到闭包中,返回本函数
      [].push.apply(this, arguments);
      return _fn;
    }
  }

  return _fn;*/
  return function() {
    var newArgs = args.concat([].slice.call(arguments))
    return fn.apply(null, newArgs)
  }
}
function add() {
  return [].reduce.call(arguments, function(a, b) {
    return a + b;
  } )
}

function multiply() {
  return [].reduce.call(arguments, function(a, b) {
    return a * b;
  })
}
console.log(curry(add, 1, 2, 3, 4, 5)(1)) // 16
console.log(curry(multiply, 1, 2, 3, 4)(5)) // 120
  • 简单的用法
function multiply(x, y) {
  if (y === undefined) {
    return function(z) {
      return x * z
    }
  } else {
    return x * y
  }
}

console.log(multiply(3, 4)); // 12
console.log(multiply(3)(4)); // 12

柯里化的作用

  • 延迟计算
  • 参数复用
  • 动态创建函数

多个标签页之间通信(如何实现浏览器中多个标签页之间的通信?)

websocket协议、localstorage、以及使用 html5浏览器的新特性 SharedWorker。

请描述一下 cookies, sessionStorage 和 localStorage 的区别?

  • cookie 是网站为了标示用户身份而储存在用户本地终端(Client Slide) 上的数据(通常经过加密)
  • cookie 数据始终在同源的 http 请求中携带(即使不需要),既会在浏览器和服务器间来回传递
  • sessionStoragelocalStorage 不会自动把数据发给服务器,仅在本地保存

存储大小:

  • cookie 数据大小不能超过 4k
  • sessionStoragelocalStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或 更大

有效时间:

  • localStorage 存储持久数据,浏览器关闭后数据不丢失除非浏览器主动删除数据。
  • sessionStorage 数据在当前浏览器窗口关闭后自动删除
  • cookie 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭

js实现循环 setTimeout输出 0, 1, 2, 3, 4

for(var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

结果为: 5,5,5,5,5

解决办法

  • 第一种:闭包
for(var i = 0; i < 5; i++) {
  (function(j) { // j = i
    setTimeout(function() {
      console.log(j)
    }, 1000)
  })(i)
}
  • 第二种:值类型传递
var output = function(i) {
  setTimeout(function() {
    console.log(i)
  }, 1000)
}

for(var i = 0; i < 5; i++) {
  output(i) // 这里传过去的 i 被复制了
}
  • 第三种:es6 let
for(let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i)
  }, 1000);
}

如果要让0-4一秒一秒地输出来呢?

  • 第一种:
for(var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j)
    }, 1000 * j ) // 这里修改 0-4 的定时器时间
  })(i)
}

第二种:

const tasks= []
const output = (i) => new Promise((resolve) => {
  setTimeout(() => {
    console.log(i)
    resolve() // 这里一定要 resolve, 否则代码不会按预期 work
  }, 1000 * i)
})
// 生成全部的异步操作
for(var i = 0; i < 5; i++) {
  tasks.push(output(i))
}
// 异步操作完成之后,输出最后的i
Promise.all(tasks).then(() => {
  setTimeout(() => {
    console.log(i)
  }, 1000)
})
  • 第三种
// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = (timeountMS) => new Promise((resolve) => {
    setTimeout(resolve, timeountMS);
});

(async () => {  // 声明即执行的 async 函数表达式
    for (var i = 0; i < 5; i++) {
        await sleep(1000);
        console.log(new Date, i);
    }

    await sleep(1000);
    console.log(new Date, i);
})();

15. 谈谈对this的理解

1)this总是指向函数的直接调用者(而非间接调用者)
2)如果有new关键字,this指向new出来的那个对象
3)在事件中,this指向目标元素,特殊的是IE的attachEvent中的this总是指向全局对象window。

16. eval是做什么的?

它的功能是把对应的字符串解析成JS代码并运行;应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。

17. ['1', '2', '3'].map(parseInt)答案是多少?

[1, NaN, NaN]

解析:

  1. Array.prototype.map()
    array.map(callback[, thisArg])
    callback函数的执行规则
    参数:自动传入三个参数
    currentValue(当前被传递的元素)
    index(当前被传递的元素的索引)
    array(调用map方法的数组)

  2. parseInt
    parseInt方法接收两个参数
    第三个参数['1', '2', '3']将被忽略。parseInt方法将会通过以下方式被调用
    parseInt('1', 0)
    parseInt('2', 1)
    parseInt('3', 2)

parseInt的第二个参数radix为0时,ECMAScript5将string作为十进制数字的字符串解析。
parseInt的第二个参数radix为1时,解析结果为NaN;
parseInt的第二个参数radix在2-36之间时,如果string参数的第一个字符(除空白以外),不属于radix指定进制下的字符,解析结果为NaN。
parseInt('3', 2)执行时,由于 '3' 不属于二进制字符,解析结果为 NaN。

18. 什么是闭包(closure),为什么要用它?

闭包指的是一个函数可以访问另一个函数作用域中变量。常见的构造方法,是在一个函数内部定义另外一个函数。内部函数可以引用外层的变量;外层变量不会被垃圾回收机制回收。
注意:闭包的原理是作用域链,所以闭包访问的上级作用域中的变量是个对象,其值为其运算结束后的左后一个值。
优点:避免全局变量污染。
缺点:容易造成内存泄漏。

例子:

function makeFunc() {
  var name = 'Mozilla'
  function displayName() {
    console.log(name)
  }
  return displayName
}
var myFunc = makeFunc()
myFunc() // 输出Mozilla

myFunc变成一个闭包。闭包是一种特殊的对象。它由两部分构成: 函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。在我们的例子中,myFunc是一个闭包,由displayName函数和闭包创建时存在的 'Mozilla' 字符串组成。

19. JS延迟加载的方式有哪些?

defer和async、动态创建DOM方式、按需异步载入JS
defer: 延迟脚本。立即下载,但延迟执行(延迟到整个页面都解析完毕后再运行),按照脚本出现的先后顺序执行。
async: 异步脚本。下载完立即执行,但不保证按照脚本出现的先后顺序执行。

20. 什么是跨域问题,如何解决跨域问题?

21. 页面编码和被请求的资源编码不一致如何处理?

若请求的资源编码,如外引js文件编码与页面编码不同。可根据外引资源编码方式定义为 charset="utf-8"或"gbk"。
比如:http://www.yyy.com/a.html 中嵌入了一个http://www.xxx.com/test.js
a.html 的编码是gbk或gb2312的。 而引入的js编码为utf-8的 ,那就需要在引入的时候

<script src="http://www.xxx.com/test.js" charset="utf-8"></script>

22. 渐进增强与优雅降级

渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进,达到更好的用户体验。
优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

23. 在一个DOM上同时绑定两个点击事件:一个用捕获,一个用冒泡。事件会执行几次,先执行冒泡还是捕获?

  • 该DOM上的事件如果被触发,会执行两次(执行次数等于绑定次数)
  • 如果该DOM是目标元素,则按事件绑定顺序执行,不区分冒泡/捕获
  • 如果该DOM是处于事件流中的非目标元素,则先执行捕获,后执行冒泡

24. 事件的代理/委托

  • 事件委托是指将事件绑定目标元素到父元素上,利用冒泡机制触发该事件

优点:

  • 可以减少事件注册,节省大量内存占用
  • 可以将事件应用于动态添加的子元素上

缺点:

  • 使用不当会造成事件在不应该触发时触发

26. javascript如何实现继承?

27. 给出一个字符串 'abfecg', 如何快速反转?

28.给出一个不存在重复元素的数组 [...'item'...], 如何最快速插入一个新元素 'itemNew'?

29. 说说普通函数与箭头函数的区别?

30. 说说循环数组和对象,你用过哪些方法?

  • for

  • forEach

  • map

  • filter

  • some

对象循环:
for ... in

  • every

31. 你对浏览器的兼容性有了解过吗?

32. 哪些操作会引起浏览器重绘(repaint)和重排(reflow), 延伸:重绘和重排谁更消耗性能?

  • postion:absolute; left:100px;会不会引起?
  • translateX:100px;会不会引起?
  • getBoundingClientRect会不会引起?
  • getClientWidth、getClientHeight会不会引起?

原理:

触发重排
页面布局和元素几何属性的改变就会导致重排
下列情况会发生重排

  • 页面初始渲染
  • 添加/删除可见DOM元素
  • 改变元素位置
  • 改变元素尺寸(宽、高、内外边距、边框等)
  • 改变元素内容(文本或图片等)
  • 改变窗口尺寸

以下属性或方法会刷新渲染队列

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • getComputedStyle()(IE中currentStyle)

减少重绘和重排的原理很简单:

  • 元素脱离文档
  • 改变样式
  • 元素回归文档

如何在浏览器中查看页面渲染时间

HTTP

1、从浏览器地址栏输入url到显示页面的步骤(以HTTP为例)

主要过程是:
浏览器解析->查询缓存->dns查询->建立链接->服务器处理请求->服务器发送响应->客户端收到页面->解析HTML->构建渲染树->开始显示内容(白屏时间)->首屏内容加载完成(首屏时间)->用户可交户(DOMContentLoaded)->加载完成(load)

  1. 在浏览器地址栏输入URL
  2. 浏览器查看 缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤
    1. 如果资源未缓存,发起新请求
    2. 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。
    3. 检验新鲜通常有两个HTTP头进行控制 ExpiresCache-Control
      • HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期
      • HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间
  3. 浏览器解析URL获取协议,主机,端口,path
  4. 浏览器组装一个HTTP(GET)请求报文
  5. 浏览器获取主机ip地址,过程如下:
    1. 浏览器缓存
    2. 本机缓存
    3. hosts文件
    4. 路由器缓存
    5. ISP DNS缓存
    6. DNS递归查询(可能存在负载均衡导致每次IP不一样)
  6. 打开一个socket与目标IP地址,端口建立TCP链接,三次握手如下:
    1. 客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口
    2. 服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
    3. 客户端发送ACK=Y+1, Seq=Z
  7. TCP链接建立后发送HTTP请求
  8. 服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用HTTP Host头部判断请求的服务程序
  9. 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
  10. 处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
  11. 服务器将响应报文通过TCP连接发送回浏览器
  12. 浏览器接收HTTP响应,然后根据情况选择 关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下
    1. 主动方发送Fin=1, Ack=Z, Seq= X报文
    2. 被动方发送ACK=X+1, Seq=Z报文
    3. 被动方发送Fin=1, ACK=X, Seq=Y报文
    4. 主动方发送ACK=Y, Seq=X报文
  13. 浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
  14. 如果资源可缓存,进行缓存
  15. 对响应进行解码(例如gzip压缩)
  16. 根据资源类型决定如何处理(假设资源为HTML文档)
  17. 解析HTML文档,构建DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释
  18. 构建DOM树
    1. Tokenizing:根据HTML规范将字符流解析为标记
    2. Lexing:词法分析将标记转换为对象并定义属性和规则
    3. DOM construction:根据HTML标记关系将对象组成DOM树
  19. 解析过程中遇到图片、样式表、js文件,启动下载
  20. 构建CSSOM树
    1. Tokenizing:字符流转换为标记流
    2. Node:根据标记创建节点
    3. CSSOM:节点创建CSSOM树
  21. 根据DOM树和CSSOM树构建渲染树:
    1. 从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none
    2. 对每一个可见节点,找到恰当的CSSOM规则并应用
    3. 发布可视节点的内容和计算样式
  22. js解析如下
    1. 浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading
    2. HTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用document.write()把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容
    3. 当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素
    4. 当文档完成解析,document.readState变成interactive
    5. 所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()
    6. 浏览器在Document对象上触发DOMContentLoaded事件
    7. 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件
  23. 显示页面(HTML解析过程中会逐步显示页面)

安全

综合

1、如何进行网站性能优化

  • content 方面
  1. 减少 HTTP 请求:合并文件、CSS 精灵、inline Image
  2. 减少 DNS 查询: DNS 缓存、将资源分布到恰当数量的主机名
  3. 减少 DOM 元素数量
  • server 方面
  1. 使用 CDN
  2. 配置 ETag
  3. 对组件使用 Gzip 压缩
  • cookie 方面
  1. 减少 Cookie 大小
  • css 方面
  1. 将样式表放到页面顶部
  2. 不使用 CSS 表达式
  3. 使用 不使用 @import
  • javascript 方面
  1. 将脚本放到页面底部
  2. 将 javascript 和 css 从外部引入
  3. 压缩 javascript和css
  4. 删除不需要的脚本
  5. 减少 DOM 访问
  • 图片方面
  1. 优化图片:根据实际颜色需要选择色深、压缩
  2. 优化 css 精灵
  3. 不要在 HTML 中拉伸图片

渲染

如何提高webpack的编译速度?

  • 版本升级
  • 利用多核(happypack)
  • 利用缓存(dll)
  • 开发环境去掉组件懒加载、不压缩图片、不分离css文件

算法

  • 冒泡排序
解析:
1. 比较相邻的两个元素,如果前一个比后一个大,则交换位置。
2. 第一轮的时候最后一个元素应该是最大的一个。
3. 按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。
function sort(elements) {
  for(var i = 0; i < elements.length - 1; i++) {
    for(var j = 0; j < elements.length - i - 1; j++) {
      if (elements[j] > elements[j + 1]) {
        var swap = elements[j];
        elements[j] = elements[j + 1];
        elements[j + 1] = swap;
      }
    }
  }
}

var elements = [3, 1, 5, 7, 2, 4, 9, 6, 10, 8];
console.log('before: ', elements)
sort(elements)
console.log('after: ', elements)
  • 快速排序
解析:
快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。然后递归调用,在两边都实行快速排序。
function quickSort(elements) {
  if (elements.length <= 1) {
    return elements;
  }
  var pivotIndex = Math.floor(elements.length / 2);
  var pivot = elements.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < elements.length; i++) {
    if (elements[i] < pivot) {
      left.push(elements[i])
    } else {
      right.push(elements[i])
    }
  }
  return quickSort(left).concat([pivot] , quickSort(right))
}

var elements = [5, 6, 2, 1, 3, 8, 7, 1.2, 5.5, 4.5]
console.log(quickSort(elements))
  • 查找字符串中出现次数最多的字符和次数

例如:sdddrtkjsfkasjdddj中出现最多的字符是d,出现了6次

var str = "sdddrtkjsfkkkasjdddj";
var max = 0;
var char;

function Search(str) {
   var json = {};
   for (var i = 0; i < str.length; i++) {
       if (!json[str[i]]) {
           json[str[i]] = str[i];
       } else {
           json[str[i]] += str[i];
       }
   }

   for (var i = 0; i < str.length; i++) {
       if (json[str[i]].length > max) {
           max = json[str[i]].length;
           char = str[i];
       }
   }
   console.log('json: ', json)
   console.log("出现次数最多的字符是" + char + ",出现了" + max + "次")
}
Search(str);
  • 计算数组中每个元素出现的次数
// 计算数组中每个元素出现的次数
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']
var countedNames = names.reduce(function(allNames, name) {
  if(name in allNames) {
    allNames[name]++
  } else {
    allNames[name] = 1
  }
  return allNames;
}, {})
console.log(countedNames)
  • for(var i=0,j=0;i<10,j<6;i++,j++) { sum = i+j }, sum值最后是多少?
var sum = 0;
for(var i=0,j=0;i<10,j<6;i++,j++) {
  sum = i+j
}
console.log(sum)

解析:
答案是: 10, 首先每次for循环的i和j值是相等的:
第一次:i=0, j =0; 符合条件, sum = i + j = 0;
第二次:i=1,j=1;符合条件, sum = i + j = 2;
第三次:i=2,j=2;符合条件, sum = i + j = 4;
第四次:i=3,j=3;符合条件,sum = i + j = 6;
第五次:i=4,j=4;符合条件, sum = i + j = 8;
第六次:i=5,j=5;符合条件,sum = i + j =10;
第七次:i=6,j=6;不符合条件((这里需要注意,循环继续的判断依据以分号前的最后一项为准,即判断j<6符不符合条件),循环结束。

注意:
这里值得一提的是 如果把条件i<10,j<6;改成i<6,j<10;
结果将完全不同,此时循环执行到j<10才会结束,此时sum=18。
参考地址

  • 说下你所知道的排序方法?
    冒泡排序、快排、二分法、广度遍历、深度遍历...

情景题

如何限制用户在一定时间内(例如2~3s)重复点击按钮?

  • 利用防抖/节流(推荐)
  • 利用setTimeout定时器
  • rxjs

平常你是如何保证前端代码质量的?

参考文档

  1. 2018美团前端面试题
  2. 2017前端面试题及答案总结
  3. 【面试】2018前端常见问题整理
  4. js十大排序算法
  5. 前端面试之道
  6. 在酷家乐做面试官的日子

前端面试题-Vue

请详细说下你对vue生命周期的理解

vue的生命周期总共分为8个阶段:创建前/后,载入前/后,更新前/后,销毁前/后。

创建前/后:在beforeCreate阶段,vue实例的挂载元素$el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,$el还没有。

载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。

更新前/后:当data变化时,会触发beforeUpdate和updated方法。

销毁前/后:在执行destoryed方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在。

生命周期图:

实例代码查看代码源

谈谈你对vue的双向数据绑定的原理的理解

vue是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤:

第一步:通过Observer提供的接口,对需要observe的数据对象进行递归遍历,给对象的每个属性、子属性对象的属性,都加上setter和getter(都绑定了一个专用的 `Dep`对象,这里的状态对象主要指组件当中的`data`属性)。
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。

第二步:compile解析模版指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1.在自身实例化时往属性订阅器(dep)里面添加自己
2.自身必须有一个update()方法
3.待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模版指令,最终利用Watcher搭起Observer和Compiler之间的通信桥梁,达到数据变化->视图更新;视图交互变化(input)->数据model变更的双向绑定效果。

对应下图:

谈谈你对vue组件间数据传递

  1. 通过 props 的方式向子组件传递(父子组件)
  2. vuex进行状态管理(父子组件和非父子组件)
  3. 非父子组件的通信传递 Vue Event Bus($emit),使用Vue的实例,实现事件的监听和发布,实现组件之间数据的传递。
  4. inheritAttrs + $attrs + $listeners

附上原文链接Vue2.4版本中新添加的$attrs以及$listeners属性使用Vue.js最佳实践(五招让你成为Vue.js大师)

谈谈vue中jsx(render)的理解

谈谈vue中虚拟dom的理解

聊聊你对vue的template编译的理解

event & v-model: 事件和v-model的实现原理

slot & keep-alive: 内置主键的实现原理

transition: 过渡的实现原理

vue-router: 官方路由的实现原理

vuex: 官方状态管理的实现原理

Vuex框架原理与源码分析

如何让 Vue 书写更佳优美?

vue-router的api方法有哪些?

vuex的api方法有哪些?

vue如何封装一个组件使其具有v-model属性(双向绑定)

提示:参考ElementUI组件的实现

你使用过vue哪些高级语法,或者定制过vue的指令、过滤器、mixins等?

你使用vue封装过哪些组件?

如果想添加vue的data()里的根数据(一级属性),该如何操作

提示:this.$set

能谈谈你对vuex数据传递处理的过程(view-state-mution-actions-getters之间的关系)吗?

情景题:

  1. 例如今日头条的tab栏(推荐、视频、社会、音乐、本地...),切换每一个tab,并且滚动到一定高度,返回上一个tab栏,如何保证滚动的高度?
    提示: keep-alive、template

  2. 如何搭建一个项目或企业的组件库?

参考文档

  1. 太原面经分享:如何在vue面试环节,展示你晋级阿里P6+的技术功底?
  2. 收藏好这篇,别再只说“数据劫持”了
  3. Vue.js最佳实践(五招让你成为Vue.js大师)
  4. 【vuejs面试题】务必熟知的vuejs面试题「务必收藏」

使vscode更优雅地支持vue

使vscode更优雅地支持vue

第一步,要支持 vue 文件的基本语法高亮

  • 1、安装 vetur 插件

Ctrl + P 然后输入 ext install vetur 然后回车点安装即可

这时可以打开一个vue文件试试,注意下右下角状态栏是否正确识别为 vue 类型:

如果被识别为 text 或 html ,则记得要点击切换下。

  • 2、配置 vetur 插件

安装完 vetur 后还需要加上这样一段配置下:

"emmet.syntaxProfiles": {
  "vue-html": "html",
  "vue": "html"
}

第二步,要支持 vue 文件的 ESLint

  • 1、安装 eslint 插件

Ctrl + P 然后输入 ext install eslint 然后回车点安装即可。

  • 2、配置 eslint

ESLint 不是安装后就可以用的,还需要一些环境和配置:

首先,需要全局的 ESLint , 如果没有安装可以使用 npm install -g eslint 来安装。

其次,vue文件是类 HTML 的文件,为了支持对 vue 文件的 ESLint ,需要 eslint-plugin-html 这个插件。可以使用 npm install -g eslint-plugin-html 来安装

接着,安装了 HTML 插件后,还需要在 vscode 中配置下 ESLint:

"eslint.validate": [
    "javascript",
    "javascriptreact",
    "html",
    "vue"
],
"eslint.options": {
    "plugins": ["html"]
},

最后,别忘了在项目根目录下创建 .eslintrc.json , 如果还没创建,还可以使用下面快捷命令来创建:

这样一来 vue 中写的 js 代码也能正确地被 lint 了。

JS如何判断一个对象为空?

JS如何判断一个对象为空

  1. 最常见的思路, for...in...遍历属性,为真则"非空数组";否则为"空数组"。
var judgeObj = function(obj) {
    for(var item in obj) {
        return false;
    }
    return true;
}

2.通过JSON自带的.stringify方法来判断。

var judgeObj = function(obj) {
    if(JSON.stringify(obj) === '{}') return true;
    return false;
}
  1. ES6新增的方法Object.keys()
var judgeObj = function(obj) {
    if(Object.keys(obj).length === 0) return true;
    return false;
}

清空数组的两种方法

如果你定义了一个数组,然后你想清空它。 通常,你会这样做:


// 定义一个数组
var list = [1, 2, 3, 4];
function empty() {
    //清空数组
    list = [];
}
empty();

但是,这有一个效率更高的方法来清空数组。 你可以这样写:


var list = [1, 2, 3, 4];
function empty() {
    //empty your array
    list.length = 0;
}
empty();

  • list = [] 将一个新的数组的引用赋值给变量,其他引用并不受影响。 这意味着以前数组的内容被引用的话将依旧存在于内存中,这将导致内存泄漏。

  • list.length = 0 删除数组里的所有内容,也将影响到其他引用。

然而,如果你复制了一个数组(A 和 Copy-A),如果你用 list.length = 0 清空了它的内容,复制的数组也会清空它的内容。

考虑一下将会输出什么:

var foo = [1,2,3];
var bar = [1,2,3];
var foo2 = foo;
var bar2 = bar;
foo = [];
bar.length = 0;
console.log(foo, bar, foo2, bar2);

//[] [] [1, 2, 3] []

前端面试题-如何机智地回答浏览器兼容性问题

如何机智地回答浏览器兼容性问题

前言

面试官常常会问:“来谈谈浏览器兼容性的问题吧”,“你对浏览器的兼容性有了解过吗”。

虽然面试官的问题十分的笼统,浏览器的兼容性无非还是样式兼容性(css),交互兼容性(js),浏览器hack三个方面。

样式兼容性(css)方面

  1. 因为历史原因,不同的浏览器样式存在差异,可以通过 normalize.css 来抹平差异,也可以定制自己的 reset.css,例如通过通配符选择器,全局设置重置样式:
* {margin: 0; padding: 0;}
  1. 在css3还没有成为真正的标准时,浏览器厂商就开始支持这些属性的使用了。css3样式语法还存在波动时,浏览器厂商提供了针对浏览器的前缀,直到现在还是有部分的属性需要加上浏览器前缀。在开发过程中我们一般通过IDE开发插件、css 预处理器以及前端自动化构建工程帮我们处理。

浏览器内核与前缀的对应关系如下:

内核 主要代表的浏览器 前缀
Trident IE浏览器 -ms
Gecko Firefox -moz
Presto Opera -o
Webkit Chrome和Safari -webkit
  1. 在还原设计稿的时候我们常常会需要用到透明属性,所以解决IE9以下浏览器不能使用opacity:
 opacity: 0.5;
 filter: alpha(opacity = 50); //IE6-IE8我们习惯使用filter滤镜属性来进行实现
 filter: progid:DXImageTransform.Microsoft.Alpha(style = 0, opacity = 50); //IE4-IE9都支持滤镜写法progid:DXImageTransform.Microsoft.Alpha(Opacity=xx)

交互兼容性(js)

  1. 事件兼容的问题,我们通常需要会封装一个适配器的方法,过滤事件句柄绑定、移除、冒泡阻止以及默认事件行为处理

     var  helper = {}
    
     //绑定事件
     helper.on = function(target, type, handler) {
         if(target.addEventListener) {
             target.addEventListener(type, handler, false);
         } else {
             target.attachEvent("on" + type,
                 function(event) {
                     return handler.call(target, event);
                 }, false);
         }
     };
    
     //取消事件监听
     helper.remove = function(target, type, handler) {
         if(target.removeEventListener) {
             target.removeEventListener(type, handler);
         } else {
             target.detachEvent("on" + type,
             function(event) {
                 return handler.call(target, event);
             }, true);
         }
     };
  2. new Date()构造函数使用,'2018-07-05'是无法被各个浏览器中,使用new Date(str)来正确生成日期对象的。 正确的用法是'2018/07/05'.

  3. 获取 scrollTop 通过 document.documentElement.scrollTop 兼容非chrome浏览器

     var scrollTop = document.documentElement.scrollTop||document.body.scrollTop;

浏览器 hack

img

  1. 快速判断 IE 浏览器版本

     <!--[if IE 8]> ie8 <![endif]-->
     
     <!--[if IE 9]> *气的 ie9 浏览器 <![endif]-->
  2. 判断是否是 Safari 浏览器

     /* Safari */
     var isSafari = /a/.__proto__=='//';
  3. 判断是否是 Chrome 浏览器

     /* Chrome */
     var isChrome = Boolean(window.chrome);

你不知道的CSS(一)

~ / + 兄弟选择器来美化表达元素

**选择器解析**
- `~` 选择器:查找某一个元素的后面的所有兄弟元素
- `+` 选择器:查找某一个元素的后面紧邻的兄弟元素
  • switch 开关

  • radio 美化

demo:

  <div class="container goods-info">
    <div class="row goods-tags">
      <div class="col-md-2 tag-label">选择版本</div>
      <div class="col-md-10">
        <div class="tags-select">
          <label class="tag-select">
            <input type="radio" name="version" value="1">
            <span class="name">全网通(2GB 16GB)</span>
          </label>
          <label class="tag-select">
            <input type="radio" name="version" value="2">
            <span class="name">全网通(3GB 32GB)</span>
          </label>
          <label class="tag-select">
            <input type="radio" name="version" value="3" disabled>
            <span class="name">联通版(2GB 16GB)</span>
          </label>
        </div>
      </div>
    </div>
    <div class="row goods-tags">
      <div class="col-md-2 tag-label">购买方式</div>
      <div class="col-md-10">
        <div class="tags-select">
          <label class="tag-select">
            <input type="radio" name="bye-type" value="1">
            <span class="name">官方标配</span>
          </label>
          <label class="tag-select">
            <input type="radio" name="bye-type" value="2">
            <span class="name">移动优惠购</span>
          </label>
          <label class="tag-select">
            <input type="radio" name="bye-type" value="3" disabled>
            <span class="name">联通优惠购</span>
          </label>
          <label class="tag-select">
            <input type="radio" name="bye-type" value="4">
            <span class="name">电信优惠购</span>
          </label>
        </div>
      </div>
    </div>
  </div>
.goods-info {
  margin-top: 50px;
  margin-bottom: 50px;

  .tag-label {
    padding-top: 7px;
  }

  .goods-tags {
    margin-top: 2rem;
  }
}

.tags-select {
  font-size: 0;

  > .tag-select {
    display: inline-block;
    font-size: 14px;
    margin: 5px;
    position: relative;
    font-weight: normal;

    .name {
      display: block;
      line-height: 20px;
      padding: 8px 10px;
      border: 1px solid #ccc;
      cursor: pointer;
    }

    input[type='radio'] {
      position: absolute;
      opacity: 0;
      z-index: -1;

      &:checked + .name {
        border-color: #e3393c;
      }

      &:disabled + .name {
        background: #eee;
        color: #999;
        cursor: not-allowed;
      }
    }
  }
}
  • checkbox 美化

font-size: 0 来清除间距

inline-block 的元素之间会受空白区域的影响,也就是元素之间差不多会有一个字符的空隙。你可以利用元素浮动float, 或者压缩html、清除元素间的空格来解决。但最简单有效的方法还是设父元素的 font-size 属性为 0

demo:

* {
  box-sizing: border-box;
}

.items {
  font-size: 0;

  > .item {
    display: inline-block;
    width: 25%;
    height: 50px;
    border: 1px solid #ccc;
    text-align: center;
    line-height: 50px;
    background-color: #eee;
    font-size: 16px; // 不要忘了给子元素设置字号
  }
}
  <div class="items">
    <div class="item">1</div>
    <div class="item">2</div>
    <div class="item">3</div>
    <div class="item">4</div>
  </div>

boder 来绘制三角形

**原理:**
只需要将其他三个边的颜色设置为透明(`transparent` 或者 `rgba(0, 0, 0, 0)`),就会只保留一个三角形了。

## 用垂直方向的 `padding` 来实现等比缩放的盒子

固定图片百分比是一个针对响应式布局很有效的方案。简单来说,就是根据容器的宽度,按照宽高比例自动计算出容器的大小,并且让容器内的如 `img` 等子元素自适应宽高。

假设图片的比例是 4:3:
图片父容器宽度 100%,父容器的高度百分比为:`100 * 3 / 4 = 75%`;图片 `absolute` 并且完全铺满父容器。

```scss
.image-aspect-ratio {
  width: 100%;
  position: relative;
  padding-top: 75%;

  > img {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
  }
}

 <figure class="image-aspect-ratio">
    <img src="http://via.placeholder.com/640x384">
 </figure>

pointer-event 来禁用事件

pointer-event 属性更像是一个 JavaScript 事件,利用该属性,可以做以下事情:

  • 阻止任何点击动作的执行
  • 使链接显示为默认光标(cursor: default)
  • 阻止触发 hoveractive 状态
  • 阻止 JavaScript 点击事件的触发
// 使用该类,任何点击事件将无效
.disabled { pointer-events: none;} 

max-width来防止图片撑破容器

img {
    display: inline-block;
    max-width: 100%;
}

未知高度容器的多种垂直居中方法

在已知父子高度的情况下,实现垂直居中是很容易的事。marginpaddingabsolute + 负margin,甚至 line-height 都是可行的方案。

这里主要介绍在父容器高度固定,子容器高度自适应的情况下,来实现其垂直居中于父盒子的几种方案。

html结构:

  <div class="container-fluid">
    <div class="row">
      <!-- :before -->
      <div class="col-md-6">
        <div class="vh-modal vh-modal-1">
          <div class="vh-modal-content">
            <h3 class="vh-modal-title">模态框</h3>
            <div class="vh-modal-body">
              <h4>(伪)元素占位</h4>
              <p>使用(伪)元素占位法可以实现子元素在父元素上的垂直居中,然后利用父元素设置
                <code>text-align: center</code>实现水平居中,这是一种有效的实现水平垂直居中的方案</p>
            </div>
            <div class="vh-modal-foot">
              <button class="btn btn-primary">确定</button>
            </div>
          </div>
        </div>
      </div>

      <!-- transform -->
      <div class="col-md-6">
        <div class="vh-modal vh-modal-2">
          <div class="vh-modal-content">
            <h3 class="vh-modal-title">模态框</h3>
            <div class="vh-modal-body">
              <h4>absolute + transform</h4>
              <p>
                <span>利用
                  <code>absolute</code>绝对定位和css3的
                  <code>transform</code>位移属性配合,也可以实现子元素在父元素上的水平垂直居中。但是,如果子元素已经在高度上超过视窗高度,那它的顶部会被浏览器窗口裁掉。</span>
              </p>
            </div>
            <div class="vh-modal-foot">
              <button class="btn btn-primary">确定</button>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div class="row">
      <!-- table -->
      <div class="col-md-12">
        <div class="vh-modal vh-modal-3">
          <div class="vh-modal-cell">
            <div class="vh-modal-content">
              <h3 class="vh-modal-title">模态框</h3>
              <div class="vh-modal-body">
                <h4>table-cell</h4>
                <p>
                  利用div模拟table属性,也是一种很实用的实现子元素垂直居中的方法,确定是table的不可控属性以及要给子元素再包裹一层父元素
                </p>
              </div>
              <div class="vh-modal-foot">
                <button class="btn btn-primary">确定</button>
              </div>
            </div>
          </div>

        </div>
      </div>
    </div>

    <div class="row">
      <!-- flex -->
      <div class="col-md-6">
        <div class="vh-modal vh-modal-4">
          <div class="vh-modal-content">
            <h3 class="vh-modal-title">模态框</h3>
            <div class="vh-modal-body">
              <h4>flex</h4>
              <p>
                在移动端中优先推荐
                <code>flex</code>方案,目前,除了部分机型的UC浏览器,
                <code>flex</code>在手机浏览器中已经得到了很好的支持。
              </p>
            </div>
            <div class="vh-modal-foot">
              <button class="btn btn-primary">确定</button>
            </div>
          </div>
        </div>
      </div>

      <!-- flex+margin:auto -->
      <div class="col-md-6">
        <div class="vh-modal vh-modal-5">
          <div class="vh-modal-content">
            <h3 class="vh-modal-title">模态框</h3>
            <div class="vh-modal-body">
              <h4>flex+margin:auto</h4>
              <p>
                当子元素处于
                <code>flex</code>盒模型中,仅仅给子元素设置一个
                <code>margin:auto</code>的属性也完全可以实现水平和垂直方向的垂直居中,很神奇对吧?
              </p>
            </div>
            <div class="vh-modal-foot">
              <button class="btn btn-primary">确定</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  • (伪)元素占位方案(推荐
    利用 (伪)元素和 display: inline-block 的方案来实现垂直居中。
.vh-modal-1 {
  text-align: center;
  font-size: 0;

  &::before,
  > .vh-modal-content {
    display: inline-block;
    vertical-align: middle;
    font-size: 14px;
  }

  &::before {
    content: '';
    height: 100%;
  }
}

default

如上图中的 ::before 你也可以使用一个真实的元素代替。

  • absolute + transform 方案

使用 absolute 决定定位子元素,并且设置其 top:50%;left:50%,然后再利用 css3transform: translate(-50%, -50%); 设置负值偏移回来也是一种有效的垂直居中方案,但是要注意其兼容性以及不要将子元素置于父容器半个像素的位置上(如 500.5px),否则子容器会出现模糊

.vh-modal-2 {
  > .vh-modal-content {
    // 尽可能的不要让该元素的宽度出现奇数,否则可能会导致模糊
    display: inline-block; // 为了自适应宽度,也可以固定宽度
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
}

transform 法还有一个缺点,就是当子元素容器高度超出视窗高度的时候,它会被直接截断(如下图),而不是想象中的随着浏览器滚动到顶部而滚动显示完全(模态框的头部被截掉了)。

default

  • table-cell 方案
    使用div来模拟table的行为也可以实现垂直居中。缺点是要在子容器外层再包裹一个父元素vh-modal-cell用来模拟table-cell
// table-cell
.vh-modal-3 {
  display: table;
  width: 100%;

  .vh-modal-cell {
    display: table-cell;
    vertical-align: middle;
    text-align: center;
  }

  .vh-modal-content {
    display: inline-block;
  }
}
<div class="vh-modal vh-modal-3">
  <div class="vh-modal-cell">
    <div class="vh-modal-content">
       ...
    </div>
  </div>
</div>
<!-- 
模拟了table布局
<table style="width: 100%;">
  <tr>
      <td style="text-align: center; vertical-align: middle;">
          类似于直接使用table布局
      </td>
  </tr>
</table>
-->
  • 基于 flex 的方案 强烈推荐

毫无疑问,flex 盒模型是最佳的实践方案。目前几乎所有现代浏览器都支持 flex 布局,尤其是移动端(部分机型UC浏览器效果太差)。基于 flex 盒模型的水平垂直居中有如下两种方案:

<div class="vh-modal vh-modal-4(5)">
          <div class="vh-modal-content">
            ...
          </div>
</div>

align-items & justify-content 方案

.vh-modal-4 {
  display: flex;
  align-items: center;
  justify-content: center;

  > .vh-modal-content {
  }
}

flex + margin 方案
这个方案是最神奇的,仅仅给子元素设置了 margin: auto; 属性,一切就这么发生了。

.vh-modal-5 {
  display: flex;
  margin: 0;

  > .vh-modal-content {
    margin: auto;
  }
}

counter 来模拟/装饰有序清单

default

类似于截图中这种多层级的数字,我们大概第一反应就是使用 JavaScript 循环列表,利用其 index 拼接而成的。事实上,仅仅使用 css 的 counter 属性也可以实现该功能,甚至实现起来更高效。

ol {
  counter-reset: decimal; /* 为每个ol元素创建新的计数器实例, 重置计数器成0 */
  list-style-type: none; // 去掉默认的list-style
  margin-left: 2rem;

  li {
    &::before {
      counter-increment: decimal; /* 只增加计数器的当前实例 */
      content: counters(decimal, '.') ' '; /* 显示计数器,并且为所有计数器实例增加以"."分隔的值 */
    }
  }
}

CSS计数器是CSS2.1中自动计数编号部分的实现。作为由CSS维护的变量,counter属性还有很多有趣的使用场景,具体就不展开了。请参考MDN上的 使用CSS计数器章节

table-layout 来控制表格单元格宽度

你也许遇到过给表格设置了宽度,但是不起作用的问题。这是因为单元格的宽度是根据其内容进行调整的。刨根到底,是因为表格有个叫做 table-layout 的属性,其浏览器默认值是 auto 在作怪。当我们把这个值设置为 fixed 的时候,我们给 th/td 标签设置的宽度就起作用了。用法很简单:

table {
  table-layout: fixed;
  width: 100%;
}
![](https://img.smohan.net/9563fb79d019dcbc0e81875ca258c8ad.png?imageView2/1/interlace/1/q/100)

截图是设置table-layout: fixed;前后对比图,左边用蓝色标注的是默认行为的表格,右边是设置了table-layout: fixed;后的样式。显而易见的,默认情况下,单元格宽度受其内容约束。而设置了table-layout: fixed;后,其单元格宽度变得可控了。

.table {
  width: 100%;

  &.fixed {
    table-layout: fixed;
  }

  td.overflow {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}
  <div class="container-fluid">
    <!-- row -->
    <div class="row">
      <div class="col-md-6">
        <table class="table">
          <thead>
            <tr>
              <th width="10%">#</th>
              <th width="20%">用户</th>
              <th width="70%">内容</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>1</td>
              <td class="overflow">[email protected]@[email protected]</td>
              <td>通过为表格中的一行或一个单元格添加颜色而赋予不同的意义只是提供了一种视觉上的表现,并不能为使用辅助技术 -- 例如屏幕阅读器 -- 浏览网页的用户提供更多信息。因此,请确保通过颜色而赋予的不同意义可以通过内容本身来表达(即在相应行或单元格中的可见的文本内容);或者通过包含额外的方式
                -- 例如应用了 .sr-only 类而隐藏的文本 -- 来表达出来</td>
            </tr>
          </tbody>
        </table>
      </div>
      <div class="col-md-6">
        <table class="table fixed">
          <thead>
            <tr>
              <th width="10%">#</th>
              <th width="20%">用户</th>
              <th width="70%">内容</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>1</td>
              <td class="overflow">[email protected]@[email protected]</td>
              <td>通过为表格中的一行或一个单元格添加颜色而赋予不同的意义只是提供了一种视觉上的表现,并不能为使用辅助技术 -- 例如屏幕阅读器 -- 浏览网页的用户提供更多信息。因此,请确保通过颜色而赋予的不同意义可以通过内容本身来表达(即在相应行或单元格中的可见的文本内容);或者通过包含额外的方式
                -- 例如应用了 .sr-only 类而隐藏的文本 -- 来表达出来</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>

caret-color 来定义光标的样式

在文本框中 input/textarea 中如果要改变光标的颜色,可以通过设置文本的颜色 color: #f00 来搞定。但是假如我们只想改变光标的颜色,而不想改变文本的颜色的话,caret-color 属性是屎一个实现方案。
default

  <input type="text" value="I have a custom caret color!">
input {
  padding: 20px;
  font-size: 18px;
  width: 100%;
  display: block;
}
input,
textarea,
[contenteditable] {
  caret-color: red;
}

user-select 来禁用文本选中

在现在浏览器中,只需要一句 user-select: none 的css样式就可以解决。IE6-9不支持 该属性,可以通过给 body 添加 onselectstart="return false;" 的内联 JavaScript 语句搞定。

body {
  user-select: none;
}

参考文档

  1. 你不知道的CSS(一)

javascript多维数组扁平化

下面是将多位数组转化为单一数组的三种不同方法:
对于此数组:

var myArray = [[1, 2],[3, 4, 5], [6, 7, 8, 9]];

我们需要的结果是:


[1, 2, 3, 4, 5, 6, 7, 8, 9]

  • 解决方案1: 使用 concat()apply()
var myNewArray = [].concat.apply([], myArray)
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 解决方案2:使用 reduce()

var myNewArray = myArray.reduce(function (prev, curr) {
  return prev.concat(curr)
})
// [1, 2, 3, 4, 5, 6, 7, 8, 9]

  • 解决方案3: for循环

var myNewArray3 = [];
for (var i = 0; i < myArray.length; ++i) {
  for (var j = 0; j < myArray[i].length; ++j)
    myNewArray3.push(myArray[i][j]);
}
console.log(myNewArray3);
// [1, 2, 3, 4, 5, 6, 7, 8, 9]

  • 解决方案4: 使用es6的展开运算符

var myNewArray4 = [].concat(...myArray)
console.log(myNewArray4)
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 解决方案5: 使用es6自带的flat
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// 使用 Infinity 作为深度,展开任意深度的嵌套数组
arr3.flat(Infinity);

对于无限嵌套的数组请使用 Lodash 的 flattenDeep()

关于CSS变量的一些总结

定义 CSS 变量

CSS 中,一个 CSS 变量是任意一个以两个破折号开头的“属性”值,

.block {
  color: #8cacea;
  --color: blue;
}

CSS 变量也称作“自定义属性”

CSS 变量作用域

CSS 变量都有一个作用域。

:root {
  --main-color: red;
}

:root 选择器能让我们选择 DOM 树里最顶级的元素,也就是文档树。所有这样定义的变量,也就相当于全局变量了。
PS: 如何在局部和全局范围内定义变量

使用 CSS 变量

变量定义且被赋值后,你就可以使用它了。
需要使用 var() 这个函数引用变量。

:root {
  --font-size: 20px;
}
.test {
  font-size: var(--font-size);
}

CSS 的变量需要非常小心谨慎,如果需要你还能做数学运算。多数情况下你应该把它们当作属性来使用。

/* 下面这样是不对的 */
.margin {
  --side: margin-top;
  var(--side): 20px;
}

这样定义会抛出无效属性名的语法错误
你也无法直接使用数学运算功能。运算功能需要使用 calc() 函数。

.margin {
  --space: 20px * 2;
  font-size: var(--space); // 这不是 40px
}

如果你一定需要使用数学计算,那么请使用 calc() 函数,如下:

.margin {
  --space: calc(20px * 2);
  font-size: var(--space); /* 等于 40px */
}

值得一提的属性

有一些行为是值得提醒注意的。

1. 自定义属性是普通属性值,因此它们可以定义在任意元素上。

在段落 p 元素,section,aside 元素或者 root 根元素,甚至是伪元素上使用变量,都是可以的。
它们和普通属性一样工作

2.CSS 变量和普通 CSS 继承和叠加的规则相同

div {
  --color: red;
}
div.test {
  color: var(--color);
}
div.ew {
  color: var(--color);
}

和普通变量相同,--color的值也会从其他的 div 元素继承下来。

3. CSS 变量也可以和 @media 或其他条件选择的规则同时使用

和其他属性一样,你也可以使用 @media 或其他的条件规则里使用变量。
例如,下面的代码改变 变量的值,在不同的设备上使用不同值。

:root {
  --gutter: 10px;
}
@media screen and (min-width: 768px) {
  -gutter: 30px;
}

4. CSS 变量可以在 HTML 的 style 属性中使用

你也可以在内联样式里使用变量,它们也正常工作。

<html style="--color: red;">

body {
  color: var(--color);
}

PS: CSS 变量大小写敏感。

/* 这是两个不同的变量 */
:root {
  --color: blue;
  --COLOR: red;
}

解决多重定义

和其他属性是一样,重定义变量也遵循标准级联规则。
下面来看个例子:

/* 变量定义 */
:root {
  --color: blue;
}
div {
  --color: green;
}
#alert {
  --color: red;
}

/*使用变量*/
* {
  color: var(--color);
}

有了上面的定义,不同的元素的值是什么样的呢?

<p>我的颜色是?</p>
<div>我的呢?</div>
<div id="alert">
  我的颜色是?
  <p>颜色?</p>
</div>

第一个 p 元素是 blue, 没有任何 --color 变量定义是在 p 元素的,因此它会继承自根元素 :root
第一个 div 是绿色 green。在div上有一个颜色变量定义。

div {
  --color: green;
}

使用 ID 值为 alert 定义的 div, 不是绿色,而是红色 red

#alert {
  color: red;
}

解决循环依赖

循环依赖通常会在下面几种情况下发生:
1

参考文档

  1. 关于 CSS 变量你需要知道的一切

ES6学习笔记-私有方法和私有属性

现有的方法

    1. 在命名上加以区别
class Widget {
	// 共有方法
    foo(baz) {
        this._bar(baz);
    } 
    
    // 私有方法
    _bar(baz) {
        return this.snaf= baz
    }
}

上面代码中, _bar 方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。

    1. Symbol

利用 Symbol 值 的唯一性,将私有方法的名字命名为一个 Symbol 值。

class Fruit {
    constructor() {
        const number = Symbol('number');
        class F {
            constructor() {
                this[number] = 1;
            }
            
            getNumber() {
                return this[number];
            }
            
            setNumber(num) {
                this[number] = num;
            }
        }
        
        return new F()
    }
}

const apple = new Fruit()
apple.getNumber() // 1
apple.setNumber(5)
apple.getNumber(5) // 5
apple[number] // Uncaught ReferenceError: number is not defined

私有属性提案

与私有方法一样,ES6不支持私有属性。目前,有一个提案,为 class 加了私有属性。方法是在属性名之前,使用 #表示。

class Point {
    #x;
    constructor(x = 0) {
        #x = +x; // 写成 this.#x 亦可
    }
    get x() {
        return #x;
    }
    set x(value) {
        #x = +value;
    } 
}

上面代码中,#x 就表示私有属性 x ,在 Point 类之外是读取不到这个属性的。还可以看到,私有属性与实例的属性是可以同名的(比如,#xget x()

ElementUI bug汇总

  1. 侧边栏不会刷新

解决方案:给 $route.query 设置一个任意值即可,如:

const qparams = this.$route.query;
    qparams.tmp_randomTime = isNaN(parseInt(qparams.tmp_randomTime))
      ? ''
      : parseInt(qparams.tmp_randomTime);
  1. 分页ajax拉取表格数据,el-pagination 中的 page无法正确高亮

解决方案:total(总条数)默认值必须设置为 null。例如:

      searchForm: {
        page: 1,
        size: 10,
        total: null
      },
  1. 列表页跳转到详情页,再从详情页回到列表页,保留原来的滚动条高度

解决方案:在列表页跳转前,在params中携带 scrollTop 参数,返回时再返回即可。例如:

params.scrollTop =
        document.querySelector('.main-container').scrollTop || 0;
  1. ElementUI el-form 内嵌 vue-preview 会导致vue-preview点击关闭按钮或者切换到下一张图片时路由切换
    解决方案:
    不要在 el-form 内嵌 vue-preview, 并且配置 vue-preview的参数 history: false
Vue.use(VuePreview, {
  mainClass: 'pswp--minimal--dark',
  barsSize: {
    top: 0,
    bottom: 0
  },
  captionEl: false,
  fullscreenEl: true,
  shareEl: false,
  bgOpacity: 0.85,
  tapToClose: false,
  tapToToggleControls: false,
  history: false
});

el-date-picker 内嵌在 el-dropdown 下,当选择完日期后,el-dropdown也会跟随着关闭

解决方案:修改 el-dropdownhide()方法

      hide () {
        if (this.triggerElm.disabled) return;
        // console.log('dropdown_showDatePicker: ', this.showDatePicker)
        if (this.showDatePicker) return; // 展开事件组件时,禁止关闭下拉弹框
        clearTimeout(this.timeout);
        this.timeout = setTimeout(() => {
          this.visible = false;
        }, this.hideTimeout);
      },

回车自动提交表单

详情:做列表搜索的时候当表单只有单个输入框时,回车会自动提交表单

解决:(组织表单提交)

<el-form :inline="true" :model="params" @submit.native.prevent>
</el-form>

监听 input 回车

<el-input v-on:keyup.enter.native="login"></el-input>

table组件expand每次只展开一项

解决:但是我在Stack Overflow找了个更简单的方法,要直接操作table树๑乛◡乛๑,只需要用到expand的方法

<el-table @expand="handleExpandRow" ref="row_table">
</el-table>
//method:
handleExpandRow(row,expanded){
    this.$refs.row_table.store.states.expandRows = expanded ? [row] : [];
}

附上Stack Overflow原地址:大神在此
————

以上是v1.4版本的,由于v2.0版本修改了返回参数,仿照上例修改了新的:

handleExpandRow(row,expandRows){
    this.$refs.raw_table.store.states.expandRows = expandRows.length !== 0 ? [row] : [];
}

其他

  1. 『表单开发』一次即通关的5个技巧

前端面试题-数组

1.如何判断一个变量是否为数组?

不可靠的检测数组方式:

  • 为什么不用 typeof ?
var list = [1, 2, 3]
typeof list // 'object'

Array继承于Object, 所以 typeof 会直接返回 object, 所以不可以用 typeof 来检测。

  • 为什么不用 instanceof ?
var list = [1, 2, 3]
list instanceof Array // true

instanceof 表面上看确实是返回了true,但其实并不可靠。原因是Array实质是一个引用,用instanceof方法(包括下面的constructor)都是利用引用地址进行比较的方法来确定的,但是在iframe嵌套的情况下,每一个Array的引用地址都是不同的,比较起来结果也是不确定的,所以这种方法有其局限性。

  • 为什么不同 constructor 方法?
var list = [1, 2, 3]
list.constructor === Array // true

原因同上。

可靠的检测数组方式:

  • 利用Object的toString方法
var list = [1, 2, 3]
Object.prototype.toString.call(list) // [object Array]
  • 利用ES6的Array.isArray()方法
var list = [1, 2, 3]
Array.isArray(list) // true

2.数组的原生方法有哪些?

会改变自身的方法:

Array.prototype.copyWithin():在数组内部,将一段元素序列拷贝到另外一段元素序列上,覆盖原有的值。
Array.prototype.fill():将数组中指定区间的所有元素的值,都替换成某个固定的值。
Array.prototype.pop():删除数组最后一个元素,并返回这个元素。
Array.prototype.push():在数组的尾部增加一个或多个元素,并返回数组的新长度。
Array.prototype.reverse():颠倒数组中元素的排列顺序,即原先的第一个变为最后一个,最后一个变为第一个。
Array.prototype.shift():删除数组的第一个元素,返回这个元素。
Array.prototype.sort():对数组元素进行排序,并返回当前数组。
Array.prototype.splice():在任意的位置给数组添加或删除任意个元素。
Array.prototype.unshift():在数组的开头增加一个或多个元素,并返回数组的新长度。

不会改变自身的方法:

Array.prototype.concat():返回一个由当前数组和其他若干个数组或若干个非数组值组合而成的新数组。
Array.prototype.includes():判断当前数组是否包含某指定的值,如果是返回true,否则返回false。
Array.prototype.join():连接所有数组元素组成一个字符串。
Array.prototype.slice():抽取当前数组中的一段元素组合成一个新的数组。
Array.prototype.toSource():返回一个表示当前数组字面了的字符串。
Array.prototype.toString():返回一个由所有数组元素组合而成的字符串。
Array.prototype.toLocaleString():返回一个由数组元素组合而成的本地化后的字符串。
Array.prototype.indexOf():返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。
Array.prototype.lastIndexOf():返回数组中最后一个(从右边第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。

遍历方法:

Array.prototype.forEach():为数组中的每个元素执行一次回调函数。
Array.prototype.entries():返回一个对象迭代器对象,该迭代器会包含所有数组元素的键值对。
Array.prototype.every():如果数组中的每个元素都满足测试函数,则返回true,否则返回 false。
Array.prototype.some():如果数组中至少有一个元素满足测试函数,则返回true,否则返回false。
Array.prototype.filter():将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回。
Array.prototype.find():找到第一个满足测试函数的元素并返回那个元素的值。如果找不到,则返回undefined。
Array.prototype.findIndex():找到第一个满足测试函数的元素并返回那个元素的索引,如果找不到,则返回 -1。
Array.prototype.keys():返回数组迭代器对象,该迭代器会包含所有数组元素的健。
Array.prototype.map():返回一个由回调函数的返回值组成的新数组。
Array.prototype.reduce():从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。
Array.prototype.reduceRight():从右到左为每个元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。
Array.prototype.values():返回数组迭代器,该迭代器会包含所有数组元素的值。
Array.prototype[@@iterator](): 和上面的 values() 方法是同一个函数。

3.如何将一个类数组变量转为数组?

  • Array.prototype.slice.call()

  • Array.from()

4.说一说ES6中对于数组有哪些扩展?

  • 增加了扩展运算符(spread) ...

  • 增加了两个方法,Array.from() 和 Array.of() 方法

  • 增加了一些实例方法,如 copyWithin()、fill()、entries()、keys()、values()、includes() 等。

5.数组去重,你能说出多少种方法?

  • 利用一个空Object来实现
function unique(arr) {
  var tmp = {}, res = [];
  arr.forEach(function(item) {
    if (!tmp[item]) {
      res.push(item);
      tmp[item] = true;
    }
  })
  return res;
}
var list = [0, 0, 1, 2, 3, 6, 6]
console.log(unique(list)) // [0, 1, 2, 3, 6]
  • 利用ES6的Set数据结构
var list = [0, 0, 1, 2, 3, 6, 6]
console.log([...new Set(list)]) // [0, 1, 2, 3, 6]

6.你知道Array.prototype的类型是什么吗?

Array.prototype 是一个数组,不过 length 为 0

7.如何"打平"一个嵌套数组, 如[1, [2, [3]], 4, [5]] => [1, 2, 3, 4, 5]?你能说出多少种方法?

    1. 利用 Array.prototype.toString()方法(元素为数字)
var list = [1, [2, [3]], 4, [5]]
console.log(list.toString()) // 1, 2, 3, 4, 5

原理:toString 方法返回一个字符串,该字符串由数组中的每个元素的 toString() 返回值经调用 join() 方法连接(由逗号隔开)组成。

  • 2.利用Array.prototype.join()方法(元素为数字)
var list = [1, [2, [3]], 4, [5]]
console.log(list.join()) // 1, 2, 3, 4, 5

原理:join 方法会让所有的数组元素转换成字符串,再用一个分隔符将这些字符串连接起来。如果元素是undefined或null,则会转化成空字符串。

    1. 利用JSON.parse()方法(元素为数字)
var list = [1, [2, [3]], 4, [5]]
JSON.parse(`[${arr}]`) // [1, 2, 3, 4, 5]

PS: 如果觉得上面输出的不是一个数组,可以稍微加工一下。

var list = [1, [2, [3]], 4, [5]]
JSON.parse(`[${list.toString()}]`)
JSON.parse(`[${list.join()}]`)
JSON.parse(`[${arr}]`)

8.如何克隆一个数组?你能说出多少种?

  • concat()
var arr1 = [1, 2, 3]
var arr2 = arr1.concat();
  • slice
var arr1 = [1, 2, 3]
var arr2 = arr1.join();

原理:数组本质上也是Object,直接赋值的话,只是将引用赋值给另一个变量,最终会导致被复制的变量也会随着原来的数组变化而变化。

9.说一说Array.prototype.sort()方法的原理?(追问:不传递参数会如何?)

语法:sort方法接收一个 "比较参数" 作为参数。

如果调用该参数时没有使用参数, 将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),再以便进行比较。

如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这个两个值得相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:
若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
若 a 等于 b, 则返回 0。
若 a 大于 b, 则返回一个大于 0 的值。

10.找出Array中的最大元素,你能说出几种方法?

  • 实现一个冒泡算法

  • 循环遍历一遍:

var list = [1, 100, 23, 65, 43, 2, 9]
var max = list[0]
for(var i = 1; i < list.length; i++) {
  max = Math.max(max, list[i])
}
console.log(max) // 100
  • reduce
var list = [1, 100, 23, 65, 43, 2, 9]
function Max(prev, next) {
  return Math.max(prev, next)
}
console.log(list.reduce(Max))  // 100
  • apply
var list = [1, 100, 23, 65, 43, 2, 9]
Math.max.apply(null, list) // 100
  • ES6 ...
    使用ES6的扩展运算符:
var list = [1, 100, 23, 65, 43, 2, 9]
Math.max(...list) // 100
  • 利用Array的sort方法先排序再取值
var list = [1, 100, 23, 65, 43, 2, 9]
list.sort((a, b) => {return a - b})
list[list.length - 1] // 100
  • eval
    Math.max 支持传多个参数来进行比较,那么我们如何将一个数组转换成参数传进 Math.max 函数呢?

eval 便是一种:

var list = [1, 100, 23, 65, 43, 2, 9]
var max = eval("Math.max(" + list+ ")")
console.log(max) // 100

解析:
因为 发生了隐式类型转换
例如:

var list = [1, 100, 23, 65, 43, 2, 9]
console.log(list+'') // 1,100,23,65,43,2,9

其实

var max = eval("Math.max("+ list+")")

就相当于

var max = eval("Math.max(1,100,23,65,43,2,9)")

参考文档

  1. 关于数组的前端面试题

new操作符具体干了什么呢?

   1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
   2、属性和方法被加入到 this 引用的对象中。
   3、新创建的对象由 this 所引用,并且最后隐式的返回 this 

var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj); 

计算数组中的最大值/最小值

  • Function.prototype.apply() 可以让提供的 this 与数组参数来调用函数

var numbers = [1,2,3,4]
Math.max.apply(null, numbers) // 4
Math.min.apply(null, numbers) // 1

  • es6 展开运算符
var numbers = [1,2,3,4]
Math.max(...numbers) // 4
Math.min(...numbers) // 1

移动端适配-rem

设计稿: iphone尺寸(750 * 1334 )

  (function(doc, win) {
    var docEl = doc.documentElement,
    resizeEvt = 'orientationchange' in window ? 'orientationchange': 'resize',
    recalc = function() {
        var clientWidth = docEl.clientWidth || 320;
        var width = (clientWidth <= 320) ? 320: ((clientWidth >= 750) ? 750: clientWidth);
        var fontSize = 100 * (width / 375);
        docEl.style.fontSize = fontSize + 'px'
    };
    if (!doc.addEventListener) return;
    win.addEventListener(resizeEvt, recalc, false);
    recalc()
})(document, window);

具体使用方法:
根元素(html)在iphone下为 100px
div 在设计稿上宽高为-> 60px * 44px, 转为rem单位的过程: 宽: (60 / 2 / 100)rem -> 0.3rem; 高: (40 / 2/ 100)rem。

聊聊 vue 中的 watcher

大家对于 watch 应该不陌生,项目中都用过下面这种写法:

watch: {
  someProp() {
    // do something
  }
}

// 或者
watch: {
  someProp: {
    deep: true,
    handler() {
      // do something
    }
  }
}

上面的写法告诉vue, 我需要监听 someProp 属性的变化,于是vue在内部就会为我们创建一个watcher对象。

然而在vue中,watcher的功能并没有这么单一,先上段代码:

<template>
  <div>
    <p>a: {{ a }}</p>
    <p>b: {{ b }}</p>
    <button @click="increment">+</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      a: 1
    }
  },
  computed: {
    b () {
      return this.a * 2
    }
  },
  watch: {
    a () {
        console.log('a is changed')
    }
  },
  methods: {
    increment () {
      this.a += 1
    }
  },
  created () {
    console.log(this._watchers)
  }
}
</script>

在线demo

上面代码非常简单,我们现在主要关注 created 钩子中打印的 this._watchers, 如下:

分别展开三个 watcher,观察每一个 expression,从上到下分别为:

- b() {      return this.a * 2;    }
- "a"
- function () {      vm._update(vm._render(), hydrating);    }

上面三个 watcher 代表了三种不同功能的 watcher, 我们将其按功能分为三类:

-  `watch` 中定义的,用于监听属性变化的 watcher (第二个)
- 用于 `computed` 属性的 watcher (第一个)
- 用于页面更新的 watcher (第三个)

normal-watcher

我们在 watch 中定义的 ,都属于这种类型,即只要监听的属性变化了,都会触发定义好的回调函数

computed-watcher

每一个 computed 属性,最后都会生成一个对应的 watcher 对象,但是这类 watcher 有个特点,就拿上面的 b 举例:

属性 b 依赖 a, 当 a 改变的时候,b 并不会立即重新计算,只有之后其他地方需要读取 b 的时候,它才会真正计算,即具备 lazy(懒计算)特性

render-watcher

每一个组件都会有一个 render-watcher, function () {↵ vm._update(vm._render(), hydrating);↵ },当 data/computed 中的属性改变的时候,会调用该 render-watcher 来更新组件的视图。

三种watcher的执行顺序

除了功能上的区别,这三种 watcher 也有固定的执行顺序,分别是:

computed-watcher -> normal-watcher -> render-watcher

这样安排是有原因的,这样就尽可能的保证,在更新组件视图的时候,computed 属性已经是最新值了,如果 render-watcher 排在 computed-watcher 前面,就会导致页面更新的时候 computed 值为旧数据。

js 实现无缝轮播图原理以及实践

js 实现无缝轮播图原理以及实践

无缝轮播的原理

问题: 在第一张或者最后一张图片切换时,会显示空白

解决方案: 使用无限滚动的技巧,即实现循环无缝切换。

1)在页面图片列表的开始加上最后一张图片的附属图,在最后加上第一张图片的附属图
2)每次切换时判断切换后的位置是否大于 -1610px 或是小于 -4830px (加上附属图总大小是 8050, 每张大小是 1610px, 总5张), 即是否切换到附属图的位置:
如果大于 -1610px, 就把图片重新定位到真正的最后一张图的位置:-4830px;
如果小于 -4830px, 就把图片重新定位到真正的第一张图的位置:-1610px;

实践

html:




参考文档

  1. https://segmentfault.com/a/1190000006265894

前端面试题-数据类型

1.JavaScript中的数据类型都有哪些?

数据类型有两种:原始数据类型(又称基础数据类型、可变数据类型)和复杂数据类型(又称对象、不可变数据类型)。
原始数据类型包括:undefined,null,数字,字符串,布尔类型,Symbol(ES6 新加入的类型)
复杂数据类型包括:对象
其中对象包括:内部对象(Array、String等),宿主对象(window)和自定义对象。

2.如何判断一个变量是否为数组?

  • Object.prototype.toString.call()

  • Array.isArray()

3.undefined 和 null 的相同与不同?

相同点:
1.这两个数据类型都只有一个值
2.都没有方法
3.都表示"无"
4.转化为布尔值时,都是false

不同点:

  1. null 是一个关键字,而 undefined 不是一个关键字。所以 var undefined = 1; 这是可以的,虽然这里 undefined 的值是不会改变的。
    var null = 1; // 这样是报错的。
  2. null的实质是一个空的对象,而undefined是window的一个属性。
  3. 进行数字类型转换时,null 返回 0, undefined 返回 NaN。

4.隐式类型转换的时候,JavaScript底层都做了哪些处理?

对象转成原始数据类型时,先调用对象的 valueOf 方法,如果返回结果不是原始数据类型的值,再调用 toString 方法。
原始类型转原始类型时,直接调用对应的构造函数进行转换。

5.有哪些值转化为布尔值后为false?

包括下面这六种:

0, -0, undefined, null, 空字符串, NaN

6.讲讲ES6中的Symbol?

Symbol 是 ES6 新加的一个原始类型,它的每个值都是唯一的,即使是用两个完全一样的变量构建出来的Symbol也不相等。

原始类型传入 Symbol 方法时,会进行转换成 字符串 再转成 Symbol 类型值;
如果是对象的话,会先调用对象的 toString 方法再转成 Symbol 类型的值。

Symbol最大的用处是用来消除 "魔术字符串" 的。

7.如何把字符串转换为数组?

  • split()

  • Array.from()

8.如何把类数组变量转换为数组,如函数内部的 arguments 对象,selector 返回的 DOM 列表。

  • Array.prototype.slice.call()

  • Array.from()

Array.from()的详解:

Set类型的转换

let s = new Set(['foo', window])
Array.from(s);
// ["foo", window]

Map类型的转换

let m = new Map([[1, 2], [2, 4], [4, 8]])
Array.from(m)
// [[1, 2], [2, 4], [4, 8]]

类数组的值

function f() {
  console.log(arguments)
  return Array.from(arguments)
}
f(1, 2, 3)

Array.from() 的第二个参数 mapFn 也很有用处,可以对传入的类数组值进行定定制化修改

Array.from([1, 2, 3], x => x + x);
// [2, 4, 6]
Array.from({length: 5}, (v, i) => i)
// [0, 1, 2, 3, 4, 5]

参考文档

事件循环机制

事件循环机制

js中存在一个叫做执行栈的东西。JS的所有同步代码都在这里执行,当执行一个函数调用时,会创建一个新的执行环境并压入到栈中开始执行函数中的代码,当函数中的代码执行完毕后将执行环境从栈中弹出,当栈空了,也就代表执行完毕。

这里有一个问题是代码中不只是同步代码,也会有异步代码。当一个异步任务执行完毕后会将任务添加到任务队列中。例如:

setTimeout(_ => {}, 1000)

代码中 setTimeout 会在一秒后将回调函数添加到任务队列中。事实上异步队列也分两种类型:微任务、宏任务。

微任务和宏任务的区别是,当执行栈空了,会检查微任务队列是否有任务,将微任务队列中的任务依次拿出来执行一遍。当微任务队列空了,从宏任务队列中拿出来一个任务去执行,执行完毕后检查微任务队列,微任务队列空了之后再从宏任务队列中拿出来一个任务执行。这样持续的交替执行任务叫做 事件循环

属于微任务(microtask)的事件有以下几种:

  • Promise.then
  • MutationObserver
  • Object.observe
  • process.nextTick

属于宏任务(macrotask)的事件有以下几种:

  • setTimeout
  • setInterval
  • setImmediate
  • MessageChannel
  • requestAnimationFrame
  • I/O
  • UI交互事件

参考文档

  1. 为什么Vue使用异步更新队列?

UI设计稿全自动切图和标注的工具推荐

自动切图工具

  这里切图推荐一个插件:Cutterman,更多切图工具介绍请移步:扶朕起来,朕还能切

Cutterman 是个国产的切图工具,广告语就叫“最好用的切图工具”

Cutterman致力于改善设计师的工作效率,为设计师提供优秀、高效、实用的技术解决方案, 解放双手。让创意不再有界限, 让设计更专注!

  • 支持多倍图
  • 多种格式
  • 多图层导出
  • 可以设置固定大小
  • 操作简单
  • 免费,只需要注册个账号即可

一键切图,真正解放双手

  Cutterman能够让你只需要点击一个按钮,就自动输出你需要的各种各样的图片,快到没有朋友!

img






几款强大的标注工具

1、标你妹

官方网站:http://www.biaonimeia.com/
是否免费:免费
登录方式:直接微信扫一扫
介绍:
操作:登录成功之后,新建一个项目上传PSD,然后就可以进行标注。
操作预览图:

img






2、蓝湖App

  一款全中文免费的自动标注的神器!彻底解放设计师的双手,上传文件就能蹭蹭蹭的自动标注!
  现在,这款叫蓝湖的设计师标注神器,最新版开始支持“自动标注”的功能
  只需下载“蓝湖”App,即可实现:从Sketch一键导出设计图→自动生成标注→自动共享给团队→团队相关成员自动收到提醒等一系列自动化功能。

官方网站:https://www.lanhuapp.com/
是否免费:免费
登录方式:下载APP,注册账号登录
介绍:

蓝湖是一款产品设计师的协作平台,帮助设计师更快地完成工作。蓝湖通过帮助设计师更好地向团队展示设计图,描述页面之间的跳转关系。蓝湖还支持从Sketch一键分享、在线协作…

操作:登录成功之后,新建一个项目上传,然后就可以进行标注。
操作预览图:

img

“自动标注”功能可以完整而清晰地将Sketch设计图中每个元素的尺寸、位置、颜色、间距、字号 等样式信息自动同步到蓝湖,团队内的工程师等同事可以随时查看。
如果设计图出现改动和更新,蓝湖也能自动添加新版本。

img

如今设计师的工具那么多,这一款工具的优势在哪里呢

1.所有功能完全免费,没有任何项目或团队成员数量限制。
2.中文的!中文的!中文的!
3.无与伦比的快!在国内的服务器+蓝湖工程师呕心沥血优化的算法,使蓝湖的“自动标注”的速度嗖嗖的!
4.蓝湖还整合了设计图流程的展示,设计图历史版本管理,多种情况和状态的设计图管理等功能。

img

5.设计师不但可以为每张设计图添加备注文档,其他团队成员还可以针对设计图发表评论,方便团队在线高效沟通。(内心竟有点小小的惶恐…)
6.在蓝湖上,还可以基于设计图快速制作一个高保真的交互原型,让工程师不用再跑来问你“这个按钮跳到哪啊”,该原型还可以在蓝湖手机端App和微信上进行操作和预览。

img

蓝湖主体功能是Web端网页平台,不需要下载,直接注册就可以免费使用。






3、Sketch/PS + Zeplin

  随着sketch的普及(sketch是啥,能吃吗?自行谷歌、必应),国内外很多项目团队都陆续用起了sketch+zeplin的开发模式。不过话说回来,sketch真的有那么好用吗?很多小伙伴们表示用ps好几年了,要我重新学一个软件,臣妾做不到啊!~

  其实刚进公司的时候也是这种心情的,没用过mac更没用过sketch,完全的小白用户,当时内心几乎是奔溃的。但是自从接触sketch后,真的是爱不释手,都不想用回ps了。

官方网站:https://www.zeplin.io/
PS导出zeplin官方视频教程:https://www.youtube.com/watch?v=0cVbzq-Q43M
Sketch导出zeplin 官方视频教程:https://www.youtube.com/watch?v=o9kOpAaDpQU
是否免费:免费
登录方式:下载APP,注册账号登录
介绍:

在使用 Zeplin 之前,最早是使用马克曼(手动标注,这里不做推荐)进行标注的,也就是直接在输出效果图上量尺寸;使用 Sketch 插件 Measure 之后,可以在画板中生成尺寸标注信息,导出标注图提供给开发同学使用。无论是马克曼还是 Measure,最后的交付物是一致的,马克曼和接下来要介绍的Measure这种原始的标准就是已经破坏了原本的视觉效果图,标注的信息一定会对原设计稿形成遮挡,因此一般效果图和标注图要分开给,开发也经常需要在两个图之间切换,图片管理不太方便。

zeplin 主要就是为了解决上述问题的,使用它之后,可以在 Sketch 中一键导入 Artboard,在设计师做好图层管理(命名、分组)的前提下,它可以自动生成标注信息(并且可以标注为 pt 或 dp),允许添加注释形成类 prd 文档,并且自动提取 Style Guide,同时还允许添加项目组成员,提供给团队组查看项目。

操作:登录成功之后,新建一个项目上传素材,然后就可以进行操作。
官方预览图:

img

介绍之后回答两个基本问题

①sketch支持windows吗?

答:不好意思,目前没有!设计师为了提升工作效率,就算吃土一两个月也要买台mac。不过windows用户除了装mac虚拟机外,现在ps也支持zeplin插件了,只要安装个插件,没有mac也照样可以任性的告别切图和标注。

②zeplin支持windows吗?

答:真够意思,这个必须有!不久前只有mac版,不过zeplin团队怎么会放弃windows那么大块的市场呢。真是喜大普奔,现在zeplin也支持windows了,从此开发哥哥再也不会抱怨网页端的zeplin打开速度超级慢了。

好了,废话不多说,直接进正题。

③sketch+zeplin的优势
1、sketch支持多画板,便于同时预览,占用内存较ps小很多;
2、sketch支持导出flinto,便于制作交互动效原型;
3、zeplin解放设计师的双手,从此告别切图和标注;
4、zeplin降低工程师的沟通成本,提高设计还原度。

更多细节已经安装方法导出技巧请移步:APP标注无烦恼!告别切图标注-Sketch/PS+Zeplin
这里这介绍工具,由于篇幅有限,并不详细教你怎么用,工具多用用就会了,熟能生巧。
更多关于Zeplin的体验和细节请移步:Zeplin 的使用体验如何?





4、Sketch Measure

中文文档:http://sketch.im/plugins/1
github:https://github.com/utom/sketch-measure
官方网站:http://utom.design/measure/
官方使用教程:http://utom.design/measure/how-to.html
是否免费:免费
登录方式:下载APP,注册账号登录
介绍:

Sketch Measure是一款可用于标注和设计规范的工具,支持Sketch 3.5版以上。Measure帮你解放你的双手…

1.创建叠加

img

2.度量尺寸

img

3.度量边距

img

4.获取属性

img

5.添加注释

img

操作:登录成功之后,新建一个项目上传PSD,然后就可以进行标注。
官方DEMO预览地址:http://utom.design/news/#artboard0
Demo操作预览图:

img

关于Sketch Measure的使用教程,这里也不多细说,这里抛砖引玉的介绍一下,想要了解和使用请移步:Sketch Measure切图标注插件使用教程

下面谈一谈Zeplin和Sketch Measure的区别,纯属引用,表示没用过Sketch Measure:

img

Zeplin:

①Zeplin注册免费,只能保留一个Active项目,“STARTER”17刀/月,3个Active项目,“GROWING BUSINESS”26刀/月,12个Active项目。“ORGANIZATION”每个用户6.75刀/月。
②支持MAC的Sketch和PS,以及PC的PS。(最大的优点)
③数据必须上传到网络上,可以用客户端查看也可以网页查看,必须是注册用户。(很麻烦,有些公司不允许上传就没办法了)
④自动生成styleguide。(非常棒)
⑤切图需要查看相应页面时,从切图栏下载。(我用的并不多,也可能有其他方式)

Sketch Measure:

①完全免费。
②只支持MAC Sketch,但查看不受限制。
③数据保存在本地(html文件),方便打包后发邮件,缺点是每次更新都要再发一遍,管理麻烦。
④没有Zeplin智能,没有自动styleguide,但是有类似AssistorPS一样的手动标注。
⑤有“颜色命名”但比styleguide差很多,希望以后能更新类似功能。自动打包输出切图,支持iOS和Android的命名方式。

5. iDoc

这是一款在线的智能标注工具,属于国产软件,可以及时动态查看设计稿的相关标注信息,是最近新出的软件。

官方网站:https://idoc.mockplus.cn/
是否免费:免费
登录方式:下载APP,注册账号登录
操作预览图:

default

优点:

  1. 开发可以知己诶复制元素代码;
  2. 标注比较智能,也支持百分比标注和多选标注;
  3. 在线的,不占内存,一个插件搞定切图和标注;
  4. 支持Win/Mac,支持 PS/Sketch/XD;

缺点:

  1. 对于图层样式,只能支持常用图层样式:比如外阴影、内阴影、描边和投影等;

6、其他诸如PxCook(像素大厨),cutterman的parker(收费)






以上五个推荐软件的看法:

标你妹适合小型的个人的一些项目,对于新手来说,学习成本基本为0,非常方便,web端没有平台限制;

蓝湖国内的良心之作,速度很快,适合个人和企业协同合作开发;

Zeplin适合小型的团队,还带有一部分协作办公的功能(留言和更新状况),要求前端也能适应这种新的方式;

Sketch Measure更传统一些,本地文档、打包切图等等,更适合有自己办公流程的大公司,仅仅支持Mac。

iDoc国内的良心之作,值得一试;

处理 Websocket 超时问题

在 websocket 连接被建立后,如果一段时间未活动,服务器或防火墙可能会超时或终止连接。想要解决这个问题, 我们可以周期性地给服务器发消息。我们需要两个方法实现:一个来确保连接不会中断,,另一个用来取消此设定。同我们也需要一个 timerID 变量.

让我们来看一下具体实现:

var timerID = 0; 
function keepAlive() { 
    var timeout = 20000;  
    if (webSocket.readyState == webSocket.OPEN) {  
        webSocket.send('');  
    }  
    timerId = setTimeout(keepAlive, timeout);  
}  
function cancelKeepAlive() {  
    if (timerId) {  
        clearTimeout(timerId);  
    }  
}

现在我们实现了我们需要的两个方法,我们可以在 onOpen() 的最后面调用 keepAlive() ,在 onClose() 的组后面调用 cancelKeepAlive()

前端面试题-React

1. 说说React, 为什么选择React?

(一) React特点

  1. 高效、虚拟DOM, 最大限度地减少与DOM的交互
  2. 服务器端渲染
  3. 组件化编码
  4. 声明式设计
  5. 灵活
  6. JSX
  7. 单向响应的数据流

(二) 为什么选择React

  1. 相比其他如 Vue、Angular的双向数据绑定的框架,个人比较喜欢这种比较流程化的单向数据流形式,因为可以更好地预测数据的变化;
  2. React社区完善,Facebook支撑,并拥有强大的全家桶
Vue 和 React 的区别?
[https://www.zhihu.com/question/31585377](https://www.zhihu.com/question/31585377)

你对React生命周期了解吗?

(一) 装载过程

  • 当组件第一次被渲染时,依次调用的函数:
    • static propTypes(createClass创建的话:propTypes)
    • static defaultProps(createClass创建的话: getDefaultProps)
    • constructor(初始化state, createClass创建的话: getInitalState)
    • componentWillMount
    • render
    • componentDidMount

(二) 更新过程

  • 更新过程会依次调用以下生命周期函数,其中render函数和"装载"过程一样
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidUpdate
  • 并不是所有的更新过程都会执行全部函数

(三) 卸载过程

  • React组件的卸载过程只涉及一个函数 componentWillUnMount
  • 当React组件要从DOM树上删除之前,对应的 componentWillUnMount函数会被调用,所以这个函数适合做一些清理性的工作。

什么时候使用 Class Component 而非 Functional Component?

如果你的组件有state或者使用了生命周期函数,那么请使用 Class Component。否则,使用 Functional Component。

refs 是什么,还有为什么它很重要?

Refs是你访问DOM元素或者组件实例的一个安全门。为了使用它们,你可以在组件上加上一个 ref 属性,ref的值是一个回调函数,这个回调函数接受底层的DOM元素或者被挂载的组件实例作为它的第一个参数。

class CustomForm extends Component {
  handleSubmit = () => {
    console.log("Input Value: ", this.input.value)
  }
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type='text' ref={input => this.input = input} />
        <button type='submit'>Submit</button>
      </form>
      )
  }
}

从上述所知,我们的输入框有一个ref属性,它的值是一个函数。这个函数接受这个input对应的真实DOM元素,我们绑定到this后得到该实例以在handleSubmit这个方法里访问它。

人们常常会误解,为了使用refs必须使用 Class Component, 单实际refs还可以通过闭包在 Functional Component 中使用。

function CustomForm({handleSubmit}) {
  let inputElement
  return (
    <form onSubmit={() => handleSubmit(inputElement.value)}>
      <input type='text' ref={input => inputElement = input} />
      <button type='submit'>Submit</button>
    </form>
    )
}

什么时候keys 而且为什么他们很重要?

keys负责帮助React跟踪列表中哪些元素被改变/添加/删除。

在生命周期的哪个阶段发生ajax请求而且为什么?

Ajax请求应该在 componentDidMount 函数中进行。

参考文档

  1. 关于React面试题汇总
  2. 【react面试题】不可错过的react 面试题 「务必收藏」

docker笔记

本文的代码都在 https://github.com/riskers/docker-demo 可以找到。

  1. 一个 Node 应用
  2. docker化一个应用
  3. 发布一个镜像
  4. docker-compose
  5. 多容器应用

Docker 存在的意义

  • 加速本地开发和构建,开发人员可以构建、运行并分享Docker容器,容器可以在开发环境中构建,然后轻松地提交到测试环境中,并最终进入生产环境
  • 能够让独立服务或应用程序在不同环境中,得到相同的运行结果。
  • 用 Docker 创建隔离环境进行测试
  • Docker 可以让开发者先在本机上构建一个复杂的程序测试,而不是一开始就在生产环境进行测试

对于我们前端来说,这样比较好理解:构建好的镜像只要就是 package.json,里面只要写好需要的包名和版本号,任何人拿到这份文件都可以获取对应的包,并且能够运行这个程序。但是,我们忘了一个很重要的点,Node 和 npm 也是有版本的啊!举个例子,Node 在 7.x 以上才支持 async ,如果我们的服务器上 Node 的版本是 4.x ,那么部署上去肯定是不行的,然后查个半天 BUG ,才发现是 Node 版本的问题。Docker 就是帮我们解决这种问题的,它能够把 Node 版本也能够像 package.json 那样记录在配置文件中。

Docker概念

  • Docker 客户端和服务器: https://docs.docker.com/engine/installation/
  • Docker 镜像: 用户基于镜像来运行自己的容器,可以把镜像当做容器的『源代码』,镜像体积很小,易于分享、存储和更新
  • Registry: Docker 用 Registry 保存用户构建的镜像,Registry 分为公共和私有两种:
    • Docker 公司运营的公共 Registry 叫做 Docker Hub,我们可以在上面注册账号,分享并保存自己的镜像。
    • 也可以在 Docker Hub 保存自己的私有镜像或者架设自己私有的 Registry
  • Docker 容器: 把应用程序或服务打包放进去,容器是基于镜像启动的,容器中可以运行一个或多个进程。
    • 镜像是 Docker 生命周期中的构建或打包阶段
    • 容器则是启动或执行阶段

Docker 常用命令

容器

  • 判断 docker 是否能正常工作

    docker info # 返回所有容器和镜像的数量、基本配置等
    
  • 运行容器

    docker run image-name
    

    比如 docker run ubuntu,会先检查本地是否存在 ubuntu 镜像,如果本地没有该镜像的话,那么 Docker 就会查看 Docker Hub 中是否有该镜像,找到的话就会下载该镜像并将其保存到本地。

    随后,Docker 在文件系统内部用这个镜像创建了一个新容器,该容器拥有自己的网络、IP地址,以及一个用来和宿主机进行通信的桥接网络接口。

    img

  • 使用容器

    docker run -t -i ubuntu 会进入容器,然后 exit 离开容器,容器就停止运行了。

    -i: Keep STDIN open even if not attached
    -t: Allocate a pseudo-tty

    但容器还是存在的,docker ps -a 查看所有容器(正在运行的、已经停止的)

    docker ps 只会列出正在运行的容器。

  • 容器命名

    Docker 会为每一个容器自动生成一个随机 id,我们也可以自己为容器指定名称。

    docker run --name your_container_name -i -t ubuntu
    

    容器名称不允许同名,可以使用 docker rm 删除同名容器。

  • 重新启动已经停止的容器

    docker start your_container_name / your_container_id
    docker restart your_container_name / your_container_id
    
  • 获取信息

    docker inspect your_container_name / your_container_id
    
  • 附着到容器(重新启动并运行一个交互式会话shell)

    docker attach your_container_name
    
  • 守护容器

    docker run -d your_container_name #创建守护容器
    docker top your_container_name #查看容器内进程
    docker exec your_container_name touch a.txt #在容器内部运行进程
    docker stop your_container_name #停止容器
    
  • 删除容器

    docker rm your_container_name/id
    

镜像

img

  • 列出镜像

    docker images
    
  • 拉取镜像

    docker pull
    

    docker store 上每个镜像都有很多个标签
    docker pull ubuntu:12.04 表示拉取 12.04 这个标签

  • 构建镜像(Dockerfile + docker build)

    一般来说,我们不是真正地创建新镜像,而是基于一个已有的基础镜像,构建新镜像而已。

    FROM ...
    
    RUN ...
    
    # 指定容器内的程序将会使用容器的指定端口
    # 配合 docker run -p
    EXPOSE ...
    
    • RUN: 指定镜像被构建时要运行的命令
    • CMD: 指定容器被启动时要运行的命令
    • ENTRYPOINT: 同 CMD ,但不会被 docker run -t 覆盖
    • WORKDIR: CMD/ENTRYPOINT 会在这个目录下执行
    • VOLUME
    • ADD
    • COPY
    docker history images [name]
    
  • 从新镜像启动容器

    docker run -d -p 4000:80 --name [name]
    

    可以在 Dokcer 宿主机上指定一个具体的端口映射到容器的80端口上

  • 推送镜像

    docker push [user_name]/[image_name]
    
  • 删除镜像

    docker rmi [user_name]/[image_name]
    

其他

  1. 9102 年了,学点 Docker 知识

浏览器页面事件

在做业务开发的时候,碰到过这样的问题:

部分浏览器,如 Safari 中,从后一个页面返回到上一个页面的时候,页面并不会进行刷新,而是维持着该页面最后的状态。这样在用户不知道已经提交过的情况下,就有可能再次提交,导致用户体验不佳等情况

那么该如何解决这种问题呢?我们很容易会联想到如果可以像原生 App 开发中一样,监听到页面的生命周期,那这个问题自然就迎刃而解

那么浏览器中有没有页面的生命周期方法呢?其实并没有,但是却有类似功能的事件来帮助我们解决问题

浏览器页面事件

浏览器的页面事件有下面几种:

  • onload
  • onpageshow
  • onunload
  • onpagehide
  • onbeforeunload

下面对这些事件进行说明:

onload

该事件在页面第一次加载时触发,如果页面是从浏览器缓存中读取的,则不会触发

window.onload = (e) => {
    alert('page onload')
}

onpageshow

该事件在页面每次加载时都会触发,也就是说,即使页面是从浏览器中读取的,也会触发这个事件

机智的小伙伴一定想到了,我们可以用这个方法来判断返回后的页面是不是直接读取的缓存中的页面

但是还有一个问题,onpageshow 虽然每次都会触发,但是该如何判断是不是从缓存中读取的呢?请看下面的代码:

window.onpageshow = (e) => {
    if (e.persisted) {
      alert('page read from cache')
      window.location.reload()
}

上面代码中,通过事件对象的属性 persisted 来判断是否从缓存中读取,它会返回 truefalse

但是并不是所有浏览器都需要添加这个方法,现在很多浏览器在返回后都会自动刷新,所以我们只需要对那些不会刷新的浏览器的来使用这个方法方法就可以了

onpagehide 与 onunload

这两个事件都是离开页面时触发的事件,区别在于 onpagehide 触发后依然可以对页面进行缓存,而 onunload 触发后无法缓存页面

如果同时使用这两个方法的话,会发现 onpagehide 会先于 onunload 被触发,并且在这两个事件中都无法使用 alert

onbeforeunload

页面关闭之前触发的事件,先于上面的两个离开页面事件被触发,同样无法使用alert

浏览器页面事件的触发顺序

onload -> onpageshow -> onbeforeunload -> onpagehide -> onunload

div垂直居中

  • 垂直居中(absolute)

div {
    position: absolute;
    width: 300px;
    height: 300px;
    margin: auto;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
}

  • 水平垂直居中一(relative/absolute)

确定容器的宽高 宽500 高300 的层
设置层的外边距


div {
    position: relative;        /* 相对定位或绝对定位均可 */
    width:500px;
    height:300px;
    top: 50%;
    left: 50%;
    margin: -150px 0 0 -250px;         /* 外边距为自身宽高的一半 */
    background-color: pink;         /* 方便看效果 */

}

  • 水平垂直居中二(transform)

未知容器的宽高,利用 transform 属性


div {
    position: absolute;        /* 相对定位或绝对定位均可 */
    width:500px;
    height:300px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: pink;         /* 方便看效果 */

}

  • 水平垂直居中三(flex)

.container {
    display: flex;
    align-items: center;         /* 垂直居中 */
    justify-content: center;    /* 水平居中 */

}
.container div {
    width: 100px;
    height: 100px;
    background-color: pink;        /* 方便看效果 */
}

参考文档

  1. 用 CSS 实现元素垂直居中,有哪些好的方案?

CSS布局之-等高分栏布局小结

CSS布局之-等高分栏布局小结

1. 方法一:万能的flex

它之所以能实现等高布局,跟一个flex的css属性有关系,这个属性是:align-item。它的默认值是: stretch, 在flex item元素比如layout__main或layout__aside的高度未定义或者为auto的情况下,会拉伸flex item元素的高度或宽度,铺满flex的交叉轴。

2. 方法二:使用table或者伪table

1)可用于伪table表现的display属性值有:

2)当把一个元素的display属性设置成以上列出的值后,就可以把这个元素看成与该属性对应的表格元素,比如 table-cell对应的就是td;同时这个元素会拥有跟表格一样的特性,比如display:table或者inline-table的元素可以使用table-layout,border-collapse和border-spacing这三个原本只有table才能生效的属性;display:table-cell 的元素跟 td 一样,对宽高敏感,对margin值无反应,对padding有效

3)关于table-cell还有一点要说明的就是,它会被其他一些CSS属性破坏,例如 float, position: absolute, 所以这些属性不能同时使用。

4)跟直接使用表格元素不同的是,在使用表格元素的时候需要完全遵守表格元素嵌套结构,也就是下面这种:

<table>
    <thead>
        <th></th>
    </thead>
    <tbody>
        <tr>
            <td></td>
        </tr>
    </tbody>
    <tfoot>
        <th></th>
    </tfoot>
</table>

而使用伪table的哪些属性时,可以近单独使用某一个属性,浏览器会在这些元素的外层包裹缺失的框来保证伪table元素框嵌套结构的完整性,这些框跟常提到过的行框一样都是不可见的,网上有的文章里也把这种做法叫做匿名表格。下面这个代码中,tb-cell元素的外层没有加 display: table-row和display: table的元素:

.tb-cell {
    display: table-cell;
    padding: 10px;
    border: 1px solid #ccc;
}

<div class="tb-cell">这是第1个display: table-cell;的元素。</div>
<div class="tb-cell">这是第2个display: table-cell;的元素。</div>

但是看到的效果是(蓝色背景是它们父层的一个包裹元素: width: 800px;margin-left: auto;margin-right: auto):

因为浏览器自动在这两个元素的外层,加了跟能够跟tr和table起相同作用的框,来包含这两个元素形成的框,所以这两个元素看起来就跟实际的表格效果一样。假如浏览器没有做这个处理,这两个元素之间是不可能没有间隙的,中间会有一个因为换行符显示出来的空格。这种自动添加的框都是行内框,不是块级框

接下来看看如何通过这些伪table的属性来完成上文的分栏布局以及本文要求的等高分栏布局,玩法有很多:(本文相关源码下载

玩法一:模拟直接使用表格布局

这种方法的思路是布局时完全按照表格的嵌套层次来处理,把display: table, display: table-row, display: table-cell 都用上,相当于就是利用完整的table来做,比如说要实现上文的布局(3栏布局,2个侧边栏布局分别固定在左边和右边,中间是主体内容栏),就可以这么干:

<style type="text/css">
    .layout {
        display: table;
        width: 100%;
    }
    .layout__row {
        display: table-row;
    }
    .layout__col {
        text-align: center;
        display: table-cell;
    }
    .layout__col + .layout__col {
        border-left: 10px solid #fff;
    }
    .layout__main {
        background-color: #4DBCB0;
    }
    .layout__aside {
        width: 200px;
        background-color: #daf1ef;
    }
</style>

<div class="layout">
    <div class="layout__row">
        <aside class="layout__col layout__aside layout__aside--left">左侧边栏宽度固定</aside>
        <div class="layout__col layout__main">内容栏宽度自适应<br>高度增加一点,旁边的高度都会自动增加</div>
        <aside class="layout__col layout__aside layout__aside--right">右侧边栏宽度固定</aside>
    </div>
</div>

缺点是分栏之间的间隔不能用margin和padding来做,如果用margin,这个属性在display: table-cell的元素上根本不会生效;如果用padding,那像demo里面各栏的背景色就都会连到一块,做不出间隔的效果,如果在layout__col里面再嵌套一层,在这一层设置背景色的话,又会增加html的层次,也不是很好。我这里是投了个巧,用border处理了一下

玩法二:去掉display: table-row

前面说过,浏览器会用匿名表格的方式,添加缺失的框,所以玩法一中的代码,把layout-row完全去掉,一点都不影响布局效果:

<style type="text/css">
    .layout {
        display: table;
        width: 100%;
    }
    .layout__col {
        text-align: center;
        display: table-cell;
    }
    .layout__col + .layout__col {
        border-left: 10px solid #fff;
    }
    .layout__main {
        background-color: #4DBCB0;
    }
    .layout__aside {
        width: 200px;
        background-color: #daf1ef;
    }
</style>
<div class="layout">
    <aside class="layout__col layout__aside layout__aside--left">左侧边栏宽度固定</aside>
    <div class="layout__col layout__main">内容栏宽度自适应<br>高度增加一点,旁边的高度都会自动增加</div>
    <aside class="layout__col layout__aside layout__aside--right">右侧边栏宽度固定</aside>
</div>

玩法三:去掉 display: table

根据玩法二,可以试想一下能否再把display:table这一个属性去掉,反正浏览器还会在天津框来包裹:

<style type="text/css">
    .layout__col {
        text-align: center;
        display: table-cell;
    }
    .layout__col + .layout__col {
        border-left: 10px solid #fff;
    }
    .layout__main {
        background-color: #4DBCB0;
    }
    .layout__aside {
        width: 200px;
        min-width: 200px;
        background-color: #daf1ef;
    }
</style>
<div class="layout">
    <aside class="layout__col layout__aside layout__aside--left">左侧边栏宽度固定</aside>
    <div class="layout__col layout__main">内容栏宽度自适应<br>高度增加一点,旁边的高度都会自动增加</div>
    <aside class="layout__col layout__aside layout__aside--right">右侧边栏宽度固定</aside>
</div>

玩法四:去掉layout这一层包裹元素

如果网站比较简单,去掉layout这一层包裹元素也是可以的:

<header>顶部</header>
<aside class="layout__col layout__aside layout__aside--left">左侧边栏宽度固定</aside>
<div class="layout__col layout__main">内容栏宽度自适应<br>高度增加一点,旁边的高度都会自动增加</div>
<aside class="layout__col layout__aside layout__aside--right">右侧边栏宽度固定</aside>
<footer>底部</footer>
<style type="text/css">
    .layout__col {
        text-align: center;
        display: table-cell;
        line-height: 50px;
    }
    .layout__col + .layout__col {
        border-left: 10px solid #fff;
    }
    .layout__main {
        width: 3000px;
        background-color: #4DBCB0;
    }
    .layout__aside {
        width: 200px;
        min-width: 200px;
        background-color: #daf1ef;
    }
</style>

3. 方法三:使用绝对定位

做法一:所有栏都采用绝对定位

<header>顶部</header>
<div class="layout">
    <aside class="layout__aside layout__aside--left">左侧边栏宽度固定</aside>
    <div class="layout__main">内容栏宽度自适应</div>
    <aside class="layout__aside layout__aside--right">右侧边栏宽度固定</aside>
</div>
<footer>底部</footer>
<style type="text/css">
    .layout {
        height: 300px;
        position: relative;
    }
    .layout__aside, .layout__main {
        position: absolute;
        top: 0;
        bottom: 0;
    }
    .layout__main {
        left: 210px;
        right: 210px;
    }
    .layout__aside {
        width: 200px;
    }
    .layout__aside--left {
        left: 0;
    }
    .layout__aside--right {
        right: 0;
    }
</style>

效果:

这种布局方法的特点是:

1)主体内容栏是自适应的
2)所有栏完全等高,效果跟flex布局和伪table布局的效果一样

存在一个非常大的使用限制,就是父元素的高度没有办法通过它的内部元素给撑起来,要用的话,必须想办法让父元素有高度,适合做父元素高度可知或者全屏布局。

做法二:侧边栏绝对定位,主体内容栏保持流式布局

<div class="layout">
    <aside class="layout__aside layout__aside--left">左侧边栏宽度固定</aside>
    <div class="layout__main">内容栏宽度自适应<br>高度增加一点,旁边的高度都会自动增加</div>
    <aside class="layout__aside layout__aside--right">右侧边栏宽度固定</aside>
</div>
<style type="text/css">
    .layout {
        position: relative;
    }
    .layout__aside {
        position: absolute;
        top: 0;
        bottom: 0;
    }
    .layout__main {
        margin: 0 210px;
    }
    .layout__aside {
        width: 200px;
    }
    .layout__aside--left {
        left: 0;
    }
    .layout__aside--right {
        right: 0;
    }
</style>

这个方法的特点是:

1)主体内容栏是宽度自适应的
2)所有栏也是完全等高的

4. 方法四:借助边框,背景实现假等高

参考文档

  1. 等高分栏布局小结

react^@16.4.0声明生命周期函数

react^@16.4.0声明生命周期函数

创建时:

constructor -> static getDerivedStateFromProps -> render -> (更新DOM和Refs) -> componentDidMount

更新时:

static getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> (更新DOM和Refs) -> componentDidUpdate

卸载时

componentWillUnmount

捕获错误时

componentDidCatch

避免内容跳动

避免内容跳动

图片定宽高

最简单的方法是,给你将要加载的图片在样式里写好宽高,这样在加载完样式时就已经直到资源图片的占位。

padding-bottom

然而到了移动端,需要适配不同的屏幕,图片就不能写死宽高了,我们希望它撑满宽度,高度自适应。可以使用 padding-bottompadding-top 设置百分比来 hack。

@mixin aspect-ratio($width, $height) {
  position: relative;
  padding-bottom: ($height / $width) * 100%;
  img,
  video,
  iframe,
  object,
  embed {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
  }
}
.ratio-sixteen-nine {
  @include aspect-ratio(1600, 900);
}
<figure class="ratio-sixteen-nine">
  <img src="waterfall.jpg" alt="Waterfall in Iceland">
</figure>

tips: 当你设置 padding 或者 margin 垂直方向为百分比时,它们的参照物是包含块的宽度,不是高度!不是高度!不是高度!

动态内容

还有些时候,我们并不知道将要加载的内容是什么,无法提前获取资源的信息。这种情况也能稍微补救一下,比如说给资源设置一个 min-height 避免大幅度跳动、或者加上一个 transition ,加载前宽度设为 0, 加载完成之后,高度自适应,让用户知道接下来目光要转向何处。

font-weight 改变文字宽度

还有一种是修改文字粗细时,容易导致宽度增大,引起的文本跳动。

解决方法可以是使用伪元素来撑开宽度,然后将其隐藏。

.bar:hover {
  font-weight: bold;
}

.bar:after {
  display: block;
  content: attr(data-title);
  font-weight: bold;
  height: 1px;
  color: white;
  color: transparent;
  overflow: hidden;
  visibility: hidden;
}

推荐阅读

参考文档

  1. 避免内容跳动

hack在微信等webview中无法修改title的情况

setTimeout(function(){
    //利用iframe的onload事件刷新页面 
    document.title = 'test'; 
    var iframe = document.createElement('iframe');
    iframe.style.visibility = 'hidden'; 
    iframe.style.width = '1px'; 
    iframe.style.height = '1px'; 
    iframe.onload = function () { 
        setTimeout(function () {
            document.body.removeChild(iframe); 
        }, 0); 
    }; 
    document.body.appendChild(iframe);
},0);

深入理解 JSON

深入理解JSON

将JS数据结构转换为JSON字符串:J SON.stringify

语法:JSON.stringify(value[,replacer[, space]])

基本使用-一个参数

传入一个JSON格式的JS对象或数组:

// 对象
JSON.stringify({"name": "Good Man", "age": 18})
// '{"name": "Good Man", "age": 18}'

// 数组
JSON.stringify([1, 2, 3, "4"])
// '[1, 2, 3, "4"]'

第二个参数是一个函数或者是一个数组或者null或者未提供

  • 如果该参数是一个参数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理
var friend = {
  "firstName": "Good",
  "lastName": "Man",
  "phone": "1234567",
  "age": 18
}
var friendAfter = JSON.stringify(friend, function (key, value) {
  if (key === 'phone') {
    return "(000)" + value;
  } else if (typeof value === 'number') {
    return 10 + value;
  } else {
    return value; // 如果你把这个else分句(即是缺少最后的return value),那么结果会是 undefined
  }
})
console.log(friendAfter);
// {"firstName": "Good", "lastName": "Man", "phone": "(000)1234567", "age": 28}

如果上面的传入不是键值对的对象形式,而是数组形式的,比如上面传入的是 var friend = ["Jack", "Rose"], 如果是数组形式,那么 key 是索引值,而 value 是这个数组项,记得要在函数内部返回value,不然会出错。

  • 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的JSON字符串中,而这个数组中存在但是源JS对象中不存在的属性会被忽略,不会报错
var friend = {
  "firstName": "Good",
  "lastName": "Man",
  "address": "1234567",
  "age": 18,
  "phone": {"home": "1234567", "work": "7654321"}
}
var friendAfter = JSON.stringify(friend, ["firstName", "address", "phone"])
console.log(friendAfter);
// {"firstName": "Good", "address": "1234567","phone": {}}
// 由于 phone 对象的属性不在数组中,所以没有被序列化出来
  • 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化

参考文档

  1. 深入理解JSON
  2. 深度使用 JSON.stringify()

ES6学习笔记——Set 和 Map 数据结构

ES6学习笔记——Set 和 Map 数据结构

在 ES6 中新增了两种数据结构,它们分别是 Set 和 Map,我们可以分别将它们和 Array、Object 进行对比

Set

Set 的字面意思就是集合,与 Array 类似,都是一些成员的聚集

根据中学数学我们知道,集合中的所有元素都是唯一的,所以 Set 和 Array 之间最大的区别是:Set中所有的成员都是唯一的

const s1 = new Set()
s1.add(1)
s1.add(2)
s1.add(1)

// s1 的值为 {1, 2}

Set 函数可以接受一个可遍历的数据结构作为参数来初始化

const s2 = new Set([1,2,1,3,4])
// s2 的值为 {1, 2, 3, 4}

可遍历的数据结构有哪些呢?数组、类数组、含有 iterable 接口 的其他数据结构都是可遍历的

属性与方法

size

s2.size // 4

add(value)

向 Set 中添加一个值,返回 Set 结构本身

s2.add(5).add(6).add(7)

delete(value)

删除一个值,返回布尔值表示是否成功删除

s2.delete(7)

has(value)

Set 中是否包含某个值,返回布尔值

s2.has(1)

clear()

清除所有成员,无返回值

s2.clear()

遍历

可以通过forEach进行遍历

s1.forEach((value, key) => console.log(value, key))

由于 Set 没有键名,只有键值,所以 key 和 value 是同一个值

Map

Object 是键值对的集合,而其中的键只能使用字符串,而 Map 与 Object 类似,也是键值对的集合,但是 Map 的键可以是何种类型的值

const m1 = new Map()
const k = {key: 'value'}

m1.set(k, 'mapValue')
m1.get(k)

Map 可以接受一个数组作为参数,这个数组的成员是一个个表示键值对的数组

const m2 = new Map([
    ['name', 'xiaoming'],
    ['age', 18]
])

// {'name'=>'xiaoming', 'age'=>18}

属性与方法

size

m2.size // 2

set(key, value)

设置一个键名为 key 键值为 value 的成员,并返回整个 Map 结构,如果已经存在该键名,则会更新键值

m2.set('sex', 'boy')

get(key)

读取这个 key 对应的值,如果没有则返回 undefined

m2.get('sex')

has(key) / delete(key) / clear()

使用方法可参照上文 Set 对应的方法使用方式

遍历

keys() / values() / entries()

这三个遍历器生成函数分别返回 键名的遍历器、键值的遍历器、所有成员的遍历器

for (let key of m2.keys()) {console.log(key)}

for (let value of m2. values()) {console.log(value)}

for (let item of m2. entries()) {console.log(item[0], item[1])}
// 这里的 item 是一个数组,其中第一个值为键名,第二个值为键值

forEach()

m2.forEach((value, key) => console.log(value, key))

CSS 实现移动端 1px 线条的绘制

先放大,再缩小

方法1:

.box:before {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        right: 0;
        height: 1px;
        content: '';
        -webkit-transform: scaleY(.5);
        transform: scaleY(.5);
        background-color: #e2e2e2;
    }

方法2:

.box:before{
    content: "";
    pointer-events: none; /* 防止点击触发 */
    box-sizing: border-box;
    position: absolute;
    width: 200%;
    height: 200%;
    left: 0;
    top: 0;
    border:1px solid #000;
    -webkit-transform(scale(0.5));
    -webkit-transform-origin: 0 0;
    transform(scale(0.5));
    transform-origin: 0 0;
}

缺点:

  1. 占据了该元素的伪元素,代码量相对较多
  2. input元素没有伪元素

其他

  1. 7 种方法解决移动端 Retina 屏幕 1px 边框问题

Array.map(parseInt)错误解决方案

由来

如果想把数组里面的字符串为数字的话, 比较常用的是 mapparseInt 这两个方法。(然而执行的结果却不是我们想要的结果)

var a = ["1", "2", "3", "4", "5"]
var b = a.map(parseInt)
console.log(b) // [1, NaN, NaN, NaN, NaN]

原因

map 方法的 callback 函数有三个参数:currentValue,index,array
parseInt 方法却接收两个参数:currentValue,index, 并且用 index 作为 index进制

parseInt('1', 0) // OK => 1
parseInt('2', 1) // 不合法的进制(没有一进制的呀。。。)
parseInt('3', 2) // NaN, 二进制中没有 3
parseInt('4', 3) // NaN, 三进制中没有 4
parseInt('5', 4) // NaN, 四进制中没有 5

解决方法

    1. .map(parseFloat), 因为 parseFloat 只接收一个参数。
var c = a.map(parseFloat)
    1. .map(Number)
var d = a.map(Number)
    1. .map(num => parseInt(num)) , 只传递 currentValue 给 parseInt。
var e = a.map(num => parseInt(num))

React.Children

React.Children 是顶层API之一,为处理 this.props.children 这个封闭的数据结构提供了有用的工具。
this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点。

1、React.Children.map

object React.Children.map(object children, function fn [, object context])

使用方法:
React.Children.map(this.props.children, function (child) {
    return <li>{child}</li>;
})

其他方法
this.props.children.forEach(function (child) {
    return <li>{child}</li>
})

在每一个直接子级(包含在 children 参数中的)上调用 fn 函数,此函数中的 this 指向上下文。如果 children 是一个内嵌的对象或者数组,它将被遍历:不会传入容器对象到 fn 中。如果 children 参数是 null 或者 undefined,那么返回 null 或者 undefined 而不是一个空对象。

function Title({text, children}) {
  console.log('children: ',  children, React.Children.count(children))
  React.Children.map(children, function (child) {
    console.log('child: ', child)
    return child
  })
  return (
  <h1>
    { text }
    { children }
  </h1>);
}

React.render(
  <Title>
    <span>hello</span>
    <span>hello</span>
  </Title>,
  document.body
);

这里需要注意, this.props.children 的值有三种可能:如果
当前组件没有子节点,它就是 undefined;如果有一个子节点,数据类型就是 object;如果有多个节点,数据类型就是 array。所以
,处理 this.props.children 的时候要小心。

React 提供了一个工具方法 React.Children 来处理 this.props.children。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object

传入如下 ReactElement:

<Title>
  <span>hello</span>
  <span>hello</span>
</Title>
// 返回两个子节点,数据类型是 `array`

<Title></Title>
// 返回 undefined

<Title>
  null
</Title>
// 返回 null

2、React.Children.forEach

React.Children.forEach(object children, function fn [, object context])

类似于 React.Children.map(), 但是不返回对象。

3、React.Children.count

number React.Children.count(object children)

返回 children 当中的组件总数,和传递给 map 或者 forEach 的回调函数的调用次数一致。

不同的 ReactElement, 输出 count 值:

<Title>
  <span>hello</span>
  <span>hello</span>
</Title>
console.log(React.Children.count(this.props.children)) // 2

<Title></Title>
console.log(React.Children.count(this.props.children)) // 0

<Title>null</Title>
console.log(React.Children.count(this.props.children)) // 1

4、React.Children.only

object React.Children.only(object children)

返回 children 中仅有的子级。否则抛出异常。
这里仅有的子级,only方法接收的参数只能是一个对象,不能说多个对象(数组)。

console.log(React.Children.only(this.props.children[0]))
// 输出对象 this.props.children[0]

参考文档

  1. React.Children

前端面试题-事件

1. mouseover 和 mouseenter 两个事件有何区别?

二者的区别是 mouseenter 不会冒泡(bubble)。

详细解析一下:

当两者绑定的元素都没有子元素时,两者的行为是一直的。但是当两者内部都包含子元素时,行为就不一样了。

在 mouseover 绑定的元素中,鼠标每次进入一个子元素就会触发一次 mouseover 事件,而 mouseenter 只会触发一次。

mouseover 事件对应 mouseout事件
mouseenter 事件对应 mouseleave 事件

demo:

    <style type="text/css" media="screen">
    * {
        margin: 0;
        padding: 0;
    }

    .container {
        overflow: hidden;
    }

    .over {
        background-color: lightgray;
        padding: 20px;
        width: 40%;
        float: left;
    }

    .enter {
        background-color: lightgray;
        padding: 20px;
        width: 40%;
        float: right;
    }

    .over h2, .enter h2 {
      background-color: #fff;
    }
    </style>
</head>

<body>
    <div class="container">
        <div class="over">
          <!-- <h2 class="over-counter"></h2> -->
        </div>
        <div class="enter">
          <!-- <h2 class="enter-counter"></h2> -->
        </div>
    </div>
    <script type="text/javascript">
    function $(ele) {
        return document.querySelector(ele)
    }
    var x = 0,
        y = 0;
    var $over = $('.over');
    var $enter = $('.enter');
    $over.addEventListener('mouseover', function(e) {
        $over.innerText = ++x;
        // $('.over-counter').innerText = ++x;
    });
    $enter.addEventListener('mouseenter', function(e) {
        $enter.innerText = ++y;
        // $('.enter-counter').innerText = ++y;
    });
    </script>
</body>

2. 移动端的click时间行为与PC端有什么不同?如何屏蔽这个不同?

移动端的click事件会延迟300ms触发事件回调(只在部分手机浏览器上出现)。

解决办法:

引入 fastlick.js 来解决。它的原理是 fastlick 在检测到 touchend 事件的时候,会通过 DOM 自定义事件立即触发一个模拟 click 事件,并把浏览器在 300 毫秒之后真正触发的click事件阻止掉。

3. Event对象中, target和currentTarget的区别?

currentTarget是当前事件遍历DOM时,标识事件的当前目标。它总是引用事件处理程序附加到的元素(事件代理对象上),而不是 event.target, event.target标识事件发生的元素。

有个简单的验证方法,你会在下面的例子中看到 e.currentTarget 一直返回的是 body元素,而e.target则随着你点击的位置的不同而变化。

<body>
    <ul id="test">
        <li>
            <ul class="enter-sensitive">
                <li>item 1-1</li>
                <li>item 1-2</li>
            </ul>
        </li>
        <li>
            <ul class="enter-sensitive">
                <li>item 2-1</li>
                <li>item 2-2</li>
            </ul>
        </li>
    </ul>
    <script>
        document.body.addEventListener('click', function (e) {
            console.log(e.target, e.currentTarget)
        })
    </script>
</body>

4. 说一说什么是事件冒泡,如何阻止事件冒泡?如何阻止默认事件?

事件冒泡是指事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接受,然后逐级向上传播到较为不具体的节点(文档)。

阻止事件冒泡的方法:

调用当前时间对象的 stopPropagation() 方法
IE10及其以下 cancelBubble = true

function cancelBubble(e) {
  var evt = e ? e : window.event;
  if (evt.stopPropagation) { // W3C
     evt.stopPropagation();
  }else { // IE
     evt.cancelBubble = true;
  }
}

阻止默认事件:

调用当前事件对象的 preventDefault() 方法
IE下:returnValue = false

function preventDefaultAction(event){
  var event = window.event || event;
  if(document.all){
    // 支持IE
    event.returnValue = false;
  }else{
    // IE不支持
    event.preventDefault();
  }
}

5. 是否了解移动端的点击穿透,原理及解决方法?

点击穿透是指在移动端,由于click事件延迟300ms触发,那么如果300ms内,页面显示变化(主要是指DOM的隐藏和显示)的话,会出现实际点击元素触发了touch事件,而300ms后该位置的实际元素又被再次触发了click事件的情况。

避免方法:
引入 fastlick.js

6. 是否了解事件委托?

事件委托是指利用 "事件冒泡",止痛膏指定一个事件处理程序,来管理某一类型的所有事件。

7. 什么是事件循环?

8. css3中有哪些属性可以直接影响JS中的事件?(可以讲一下pointer-events和touch-action属性吗?)

参考文档

  1. 关于事件的前端面试题总结
  2. 前端面试题之mouseover和mouseenter的区别

img标签底部多出3-5像素问题

现象描述

img标签在HTML5和HTML4.0.1的严格模式渲染的时候,下面会有几像素的空白,如图所示:

image

原因

图片默认是行内元素(inline-block),行内元素默认是baseline(第三条线)对齐的,和底部会有一段距离,如图所示:

image

解决方法

解决方法有很多种,根据场景需要选择合适的处理方式

1. img标签增加vertical-align:top

img {
  vertical-align: top;
}

2. img标签的父标签增加font-size:0;

.parent {
  font-size: 0;
}

3. img标签增加display:block;

img {
  display:block;
}

After fixed

image

参考资料

  1. 著名的 img标签3像素问题
  2. img下几像素空白产生原因

vue1 vs vue2 常用用法总结

[TOC]

1.指令


循环

循环数组

<!-- Vue1 这么写-->
<li v-for="item in items">{{$index}} 条: {{item.message}}</li>
<div v-for="item in items" track-by="id"></div>

<!-- Vue2 这么写 -->
<li v-for="(item, index) in items">{{index}} 条: {{item.message}}</li>
<div v-for="item in items" v-bind:key="item.id"></div>

循环对象

<!-- Vue1 这么写 -->
<li v-for="(key, value) in obj"></li>

<!-- Vue2 这么写 -->
<li v-for="(value, key) in obj"></li>

循环数字

<!-- Vue1  0 开始, Vue2  1 开始 -->
<span v-for="n in 10">{{n}}</span>

条件

<!-- 如果ok为false, 不输出在HTML中 -->
<div v-if="ok">Yes</div>
<div v-else>No</div>

<!-- 如果ok为false, 只是 display: none 而已 -->
<h1 v-show="ok">Hello!</h1>

2. 事件绑定


<button v-on:click="say('hi')">点击</button>

<!-- 简写 -->
<button @click="say('hi')">点击</button>

<!-- 传入 event 对象 -->
<button @click="say('hi', $event)">点击</button>

<!-- 阻止单击事件冒泡 -->
<button @click.stop="doSth">点击</button>

<!-- 阻止默认行为 -->
<button @submit.prevent="doSth">单击</button>

<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>

<!-- 按键修饰符: 回车才会执行 -->
<input @keyup.13="submit"><!-- 13  keycode -->
<input @keyup.enter="submit">
<!-- 支持的全部按钮为 enter, tab, delete, space, up, down, left, right 字母 -->

3. 表单的双向绑定


<input type="text" v-model="message">
<!-- 自定义选中值。否则选中为value值, 不选为空 -->

<input
type="checkbox"
v-model="toggle"
v-bind:true-value="a"
v-bind:false-value="b"
>

4. 绑定属性


<div v-bind:class="{ 'class-a': isA, 'class-b': isB }"></div>

<!-- classArr是一个数组 -->
<div v-bind:class="classArr"></div>

<!-- 简写 -->
<div :class="{ 'class-a': isA, 'class-b': isB }"></div>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<img :src="imgSrc">
<a :href="baseURL + '/path'"></a>

在 Vue2 中, 如果属性值是变量, 必须用绑定属性的写法。

<!-- wrong -->
<img src="{{imgSrc}}">

<!-- right -->
<img :src="imgSrc">

5. 避免闪烁: v-cloak


[v-cloak] {
  display: none;
}
<div v-cloak>
{{message}}
</div>

不会显示 <div> 的内容, 直到编译结束。

6. 单向绑定


单向绑定的意思是, 即使绑定变量的值发生变化, 显式的内容仍旧就是最初绑定时候的值。

<!-- Vue1 这么写 -->
<span>This will never change: {{* msg}}</span>

<!-- Vue2 不支持 -->

7. 输出HTML


<!-- Vue1 这么写-->
<div>{{{ raw_html }}}</div> <!-- {{}} 中的 HTML 内容会转化为纯文本 -->
<!-- 或者 -->
<div v-html="raw_html"></div>


<!-- Vue2 这么写 -->
<div v-html="raw_html"></div>

8. 计算属性


<div id="demo">{{ fullName }}</div>
new Vue({
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

9. 自定义指令


Vue.directive('my-directive', {
  bind: function () {
    // 准备工作
    // 例如, 添加事件处理器或只需要运行一次的高耗任务
    this.el; // 添加指令的元素
    this.vm.$get(name); // 或得该指令的上下文 ViewModel
    this.expression; // 指令的表达式的值
  },
  update: function (newValue, oldValue) {
    // 值更新时的工作
    // 也会以初始值为参数调用一次
  },
  unbind: function () {
    // 清理工作
    // 例如, 删除 bind() 添加的事件监听器
  }
})
<div v-my-directive="someValue"></div>

10. 监听数据变化


new Vue({
  data: {
    firstName: 'Foo'
  },
  watch: {
    firstName: function (val, oldVal) {}
  }
})

11. 过滤器


{{ msg | capitalize }} // 'abc' => 'Abc'

常见内置过滤器

capitalize, uppercase, lowercase, json, limitBy, filterBy。所有见这里

Vue2 中把这些内置的过滤器都删除了。

自定义过滤器

Vue.filter('wrap', function (value, begin, end) {
  return begin + value + end
})
<!-- 'hello' => 'before hello after' -->

<!-- Vue1 这么写 -->
<span v-text="message | wrap 'before' 'after'"></span>

<!-- Vue2 这么写 -->
<span v-text="message | wrap('before', 'after')"></span>

this.$options.filters.filter名称 可以获取到具体的 filter

12. 生命周期相关的钩子函数


// Vue1
new Vue({
  created: function () {},
  beforeCompile: function () {},
  compiled: function () {},
  ready: function () {}, // DOM 元素已经加入到HTML中(可以在这里调用ajax获取数据)
  beforeDestroy: function () {},
  destroyed: function () {}
})

// Vue2
new Vue({
  created: function () {},
  mounted: function () {}, 相对于 Vue1 中的 ready
  beforeDestroy: function () {},
  destroyed: function () {}
})

13. 过渡效果


<!-- Vue1 这么写 -->
<div v-if="show" transition="my-transition"></div>

<!-- Vue2 这么写 -->
<transition v-bind:name="my-transition">
  <!-- ... -->
</transition>
/* 必须 */
.my-transition-transition {
  transition: all .3s ease;
}

/* .my-transition-enter 定义进入的开始状态 */
.my-transition-enter {}

/* .my-transition-leave 定义离开的结束状态 */
.my-transition-leave {}

14. 组件


Vue2 与 Vue1 的组件区别有点大。

Vue1

定义和调用组件

this.$parent 访问它的父组件。
this.$root 访问它的根组件。
this.$children 访问它的子组件。

15. 小技巧

Vue.set 和 Vue.delete

用于解决 不能检测到属性添加、属性删除的限制。

Vue.nextTick

// 修改数据
vm.msg = 'Hello'

// DOM 没有更新
Vue.nextTick(function () {
  // DOM 更新了
})

Vue 在检测到数据变化时是异步更新 DOM 的。vm 上也有 this.$nextTick

16. Vue 插件

vue-resource(Vue2推荐使用 axois)

拦截

Vue.http.interceptors.push(function(request, next) {
  var data = request.data;
  // 添加 url 前缀
  request.url = serverPrefix + request.url;
  // 加请求参数
  request.data.sessionid = localStorage.getItem('sessionid');

  next(function (response) {
    if(登陆超时){
      setTimeout(function () {
        router.go('/login');
      });
    } else {
      // modify response
      response.body = '...';
    }
  });
});

支持 Promise

Vue.http.post('/someUrl', [optinos])
  .then(function(res) {
    var data = res.data;
    return new Promise(function(resolve, reject) {
      if (!data.error) {
        reject()
      } else {
        resolve(data);
      }
    }.bind(this));
  })
  .then(function(res) {
    var data = res.data;
    return new Promise(function(resolve, reject) {
      Vue.http.post('/someUrl', data).then(function (res) {
        if(res.data){
          resolve();
        }
      });
    }.bind(this));
  }, failFn)
  .then(succFn, failFn);

JavaScript 一些小技巧

JavaScript 一些小技巧

清空和截断数组

最简单的清空和截短数组的方法就是改变 length 属性:

const arr = [11, 22, 33, 44, 55, 66]
// 截取
arr.length = 3
console.log(arr) // => [11, 22, 33]
// 清空
arr.length = 0
console.log(arr) // => []
console.log(arr[2]) // => undefined

使用对象结构模拟命名参数

以前,当我们希望向一个函数传递多个参数时,可能会采用配置对象的模式:

doSomething({ foo: 'hello', bar: 'Hey!', baz: 42 })
function doSomething(config) {
  const foo = config.foo !== undefined ? config.foo : 'Hi'
  const bar = config.bar !== undefined ? config.bar : 'Yo!'
  const baz = config.baz !== undefined ? config.baz : 13
}

这是一个古老但是有效的模式,有了ES2015的对象结构,你可以这样使用:

function doSomething({ foo = 'Hi', bar = 'Yo!', baz = 13 }) {}

如果你需要这个配置对象参数变成可选的,也很简单:

function doSomething({ foo = 'Hi', bar = 'Yo!', baz = 13 } = {}) {}

数组的对象解构

使用对象解构将数组项赋值给变量:

const csvFileLine = '1997,John Doe,US,[email protected],New York'
const { 2: country, 4: state } = csvFileLine.split(',')

注:本例中,2split之后的数组下标,country为指定的变量,值为 US

switch 语句中使用范围

这是一个在switch语句中使用范围的例子:

function getWaterState(tempInCelsius) {
  let state

  switch (true) {
    case tempInCelsius <= 0:
      state = 'Solid'
      break
    case tempInCelsius > 0 && tempInCelsius < 100:
      state = 'Liquid'
      break
    default:
      state = 'Gas'
  }
  return state
}

await多个async函数

await 多个 async 函数并等待他们执行完成,我们可以使用 Promise.all

await Promise.all([anAsyncCall(), thisIsAlsoAsync(), oneMore()])

创建纯对象

你可以创建一个 100% 的纯对象,这个对象不会继承 Object的任何属性和方法(比如 constructor, toString()等):

const pureObject = Object.create(null)
console.log(pureObject) //=> {}
console.log(pureObject.constructor) //=> undefined
console.log(pureObject.toString) //=> undefined
console.log(pureObject.hasOwnProperty) //=> undefined

格式化JSON代码

JSON.stringify不仅可以字符串化对象,它也可以格式化你的JSON 输出:

const obj = {
  foo: { bar: [11, 22, 33, 44], baz: { bing: true, boom: 'Hello' } }
}

// 第三个参数为格式化需要的空格数目
JSON.stringify(obj, null, 4)
// =>"{
// =>    "foo": {
// =>        "bar": [
// =>            11,
// =>            22,
// =>            33,
// =>            44
// =>        ],
// =>        "baz": {
// =>            "bing": true,
// =>            "boom": "Hello"
// =>        }
// =>    }
// =>}"

移除数组重复项

使用ES2015和扩展运算符,你可以轻松移除数组中的重复项:

const removeDuplicateItems = arr => [...new Set(arr)]
removeDuplicateItems([42, 'foo', 42, 'foo', true, true])
//=> [42, "foo", true]

注:只适用于数组内容为基本数据类型

扁平化多维数组

使用扩展运算符可以快速扁平化数组:

const arr = [11, [22, 33], [44, 55], 66]
const flatArr = [].concat(...arr) //=> [11, 22, 33, 44, 55, 66]

不幸的是,上面的技巧只能适用二维数组,但是使用递归,我们可以扁平化任意维度数组:

function flattenArray(arr) {
  const flattened = [].concat(...arr)
  return flattened.some(item => Array.isArray(item))
    ? flattenArray(flattened)
    : flattened
}

const arr = [11, [22, 33], [44, [55, 66, [77, [88]], 99]]]
const flatArr = flattenArray(arr)
//=> [11, 22, 33, 44, 55, 66, 77, 88, 99]

如何提高webpack的编译速度?

如何提高webpack的编译速度?

缩小文件搜索范围

优化 loader 配置

在module中,在使用 loader 时,可以通过 testincludeexclude 三个配置来命中 loader 要应用规则的文件。
以采用 ES6 的项目为例,在配置 babel-loader 时,可以这样:

module.exports = {
  module: {
    rules: [
      {
        // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
        test: /\.js$/,
        // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
        use: ['babel-loader?cacheDirectory'],
        // 只对项目根目录下的 src 目录中的文件采用 babel-loader
        include: path.resolve(__dirname, 'src'),
      },
    ]
  },
};

优化 resolve.modules 的配置

resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块。
resolve.modules 的默认目录是 [node_modules],含义是先去当前目录下的 ./node_modules 目录下去找想找的模块,如果没找到就去上一级目录 ../node_modules 中找,再没有就去 ../../node_modules 中找,以此类推,这和 Node.js 的模块寻找机制很相似。
当安装的第三方模块都放在项目根目录下的 ./node_modules 目录下时,没有必要按照默认的方式去一层层的寻找,可以指明存放第三方模块的绝对路径,以减少寻找,配置如下:

module.exports = {
  resolve: {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    // 其中 __dirname 表示当前工作目录,也就是项目根目录
    modules: [path.resolve(__dirname, 'node_modules')]
  },
};

优化 resolve.mainFields 配置

resolve.mainFields 用于配置第三方模块使用哪个入口文件。
安装的第三方模块中都会有一个 package.json 文件用于描述这个模块的属性,其中有些字段用于描述入口文件在哪里, resolve.mainFields 用于配置采用哪个字段作为入口文件的描述。
可以存在多个字段描述入口文件的原因是因为有些模块可以同时用在多个环境,针对不同的运行环境需要不同的代码。 以 isomorphic-fetch 为例,它是 fetch API 的一个实现,但可同时用于浏览器和 Node.js 环境。 它的 package.json 中就有2个入口文件描述字段:

{
  "browser": "fetch-npm-browserify.js",
  "main": "fetch-npm-node.js"
}

resolve.mainFields 的默认值和当前的 target 配置有关系,对于关系如下:

  • targetweb 或者 webworker 时,值是 ['browser', 'module', 'main']
  • target 为其他情况时,值是 ['module', 'main']

target 等于 web 为例,Webpack 会先采用第三方模块中的 browser 字段去寻找模块的入口文件,如果不存在就采用 module 字段,以此类推。

为了减少搜索步骤,在你明确第三方模块的入口文件描述字段时,你可以把它设置的尽量少。 由于大多数第三方模块都采用 main 字段去描述入口文件的位置,可以这样配置 Webpack:

module.exports = {
  resolve: {
    // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
    mainFields: ['main'],
  },
};

使用本方法优化时,你需要考虑到所有运行时依赖的第三方模块的入口文件描述字段,就算有一个模块搞错了都可能会造成构建出的代码无法正常运行。

优化 resolve.alias 配置

resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径。
在实战项目中,经常会依赖一些庞大的第三方模块,以React库为例,安装到 node_modules 目录下的 React 库的目录结构如下:

├── dist
   ├── react.js
   └── react.min.js
├── lib
   ... 还有几十个文件被忽略
   ├── LinkedStateMixin.js
   ├── createClass.js
   └── React.js
├── package.json
└── react.js

可以看到发布出去的 React 库中包含两套代码:

  • 一套是采用 CommonJS 规范的模块化代码,这些文件都放在 lib 目录下,以 package.json 中指定的入口文件 react.js 为模块的入口。
  • 一套是把 React 所有相关的代码打包好的完整代码放到一个单独的文件中,这些代码没有采用模块化可以直接执行。其中 dist/react.js 是用于开发环境,里面包含检查和警告的代码。dist/react.min.js 是用于线上环境,被最小化了。
    默认情况下 Webpack 会从入口文件 ./node_modules/react/react.js开始递归的解析和处理依赖的几十个文件,这会时一个耗时的操作。 通过配置 resolve.alias 可以让 Webpack 在处理 React 库时,直接使用单独完整的 react.min.js 文件,从而跳过耗时的递归解析操作。

相关 webpack 配置如下:

module.exports = {
  resolve: {
    // 使用 alias 把导入 react 的语句换成直接使用单独完整的 react.min.js 文件,
    // 减少耗时的递归解析操作
    alias: {
      'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
    }
  },
};

除了 React 库外,大多数库发布到 Npm 仓库中时都会包含打包好的完整文件,对于这些库你也可以对它们配置 alias。
但是对于有些库使用本优化方法后会影响到后面要讲的使用 Tree-Shaking 去除无效代码的优化,因为打包好的完整文件中有部分代码你的项目可能永远用不上。 一般对整体性比较强的库采用本方法优化,因为完整文件中的代码是一个整体,每一行都是不可或缺的。 但是对于一些工具类的库,例如 lodash,你的项目可能只用到了其中几个工具函数,你就不能使用本方法去优化,因为这会导致你的输出代码中包含很多永远不会执行的代码。

优化 resolve.extensions 配置

在导入语句没有带文件后缀时,webpack会自动带上后缀去尝试询问文件是否存在。 resolve.extensions 用于配置在尝试过程中用到的后缀列表,默认是:

extensions: ['.js', '.json']

也就是说当遇到 require('./data') 这样的导入语句时,Webpack 会先去寻找 ./data.js 文件,如果该文件不存在就去寻找 ./data.json 文件,如果还是找不到就报错。

如果这个列表越长,或者正确的后缀在越后面,就会造成尝试的次数越多,所以 resolve.extensions 的配置也会影响到构建的性能。 在配置 resolve.extensions 时你需要遵守以下几点,以做到尽可能的优化构建性能:

  • 后缀尝试列表要尽可能的小,不要把项目中不可能存在的情况写到后缀尝试列表中。
  • 频率出现最高的文件后缀要有限放在最前面,以做到尽快的退出寻找过程。
  • 在源码中写导入语句时,要尽可能的带上后缀,从而可以避免寻找过程。例如在你确定的情况下把 require('./data')写成require('./data.json')

相关 webpack 配置如下:

module.exports = {
  resolve: {
    // 尽可能的减少后缀尝试的可能性
    extensions: ['js'],
  },
};

优化 module.noParse 配置

module.noParse 配置项可以让 webpack 忽略对部分没有采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。原因是一些库,例如 jQuery、ChartJS,它们庞大又没有采用模块化标准,让 webpack 去解析这些文件耗时又没有意义。

在上面的优化 resolve.alias 配置中讲到单独完整的 react.min.js 文件就没有采用模块化,让我们来通过配置 module.noParse 忽略对 react.min.js 文件的递归解析处理,相关的webpack配置如下:

const path = require('path');

module.exports = {
  module: {
    // 独完整的 `react.min.js` 文件就没有采用模块化,忽略对 `react.min.js` 文件的递归解析处理
    noParse: [/react\.min\.js$/],
  },
};

注意被忽略掉的文件里不应该包含 import 、 require 、 define 等模块化语句,不然会导致构建出的代码中包含无法在浏览器环境下执行的模块化语句。

使用 DllPlugin

要给 Web 项目构建接入动态链接库的**,需要完成以下事情:

  • 把网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中去。一个动态链接库中可以包含多个模块。
  • 当需要导入的模块存在于某个动态链接库中时,这个模块不能被再次被打包,而是去动态链接库中获取。
  • 当需要导入的模块存在于某个动态链接库中时,这个模块不能被再次被打包,而是去动态链接库中获取。

为什么给 Web 项目构建接入动态链接库的**后,会大大提升构建速度呢? 原因在于包含大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码。 由于动态链接库中大多数包含的是常用的第三方模块,例如 reactreact-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。

接入 webpack

webpack 已经内置了对动态链接库的支持,需要通过2个内置的插件接入,它们分别是:

  • DllPlugin 插件:用于打包出一个个单独的动态链接库文件。
  • DllReferencePlugin插件:用于在主要配置文件中去引入 DllPlugin 插件打包好的动态链接库文件。

下面以基本的 React 项目为例,与其接入 DllPlugin, 在开始前先来看下最终构建出的目录结构:

├── main.js
├── polyfill.dll.js
├── polyfill.manifest.json
├── react.dll.js
└── react.manifest.json

其中包含两个动态链接库文件,分别是:

  • polyfill.dll.js 里面包含项目所有依赖的 polyfill,例如 Promise、fetch 等 API。
  • react.dll.js 里面包含 React 的基础运行环境,也就是 reactreact-dom 模块。
    react.dll.js 文件为例,其文件内容大致如下:
var _dll_react = (function(modules) {
  // ... 此处省略 webpackBootstrap 函数代码
}([
  function(module, exports, __webpack_require__) {
    // 模块 ID 为 0 的模块对应的代码
  },
  function(module, exports, __webpack_require__) {
    // 模块 ID 为 1 的模块对应的代码
  },
  // ... 此处省略剩下的模块对应的代码
]));

可见一个动态链接库文件中包含了大量模块的代码,这些模块存放在一个数组里,用数组的索引号作为 ID。 并且还通过 _dll_react 变量把自己暴露在了全局中,也就是可以通过 window._dll_react 可以访问到它里面包含的模块。

其中 polyfill.manifest.jsonreact.manifest.json 文件也是由 DllPlugin 生成出,用于描述动态链接库文件中包含哪些模块, 以 react.manifest.json 文件为例,其文件内容大致如下:

{
  // 描述该动态链接库文件暴露在全局的变量名称
  "name": "_dll_react",
  "content": {
    "./node_modules/process/browser.js": {
      "id": 0,
      "meta": {}
    },
    // ... 此处省略部分模块
    "./node_modules/react-dom/lib/ReactBrowserEventEmitter.js": {
      "id": 42,
      "meta": {}
    },
    "./node_modules/react/lib/lowPriorityWarning.js": {
      "id": 47,
      "meta": {}
    },
    // ... 此处省略部分模块
    "./node_modules/react-dom/lib/SyntheticTouchEvent.js": {
      "id": 210,
      "meta": {}
    },
    "./node_modules/react-dom/lib/SyntheticTransitionEvent.js": {
      "id": 211,
      "meta": {}
    },
  }
}

可见 manifest.json 文件清楚地描述了与其对应的 dll.js 文件中包含了哪些模块,以及每个模块的路径和 ID。

main.js 文件是编译出来的执行入口文件,当遇到其依赖的模块在 dll.js 文件中时,会直接通过 dll.js 文件暴露出的全局变量去获取打包在 dll.js 文件的模块。 所以在 index.html 文件中需要把依赖的两个 dll.js 文件给加载进去,index.html 内容如下:

<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
<div id="app"></div>
<!--导入依赖的动态链接库文件-->
<script src="./dist/polyfill.dll.js"></script>
<script src="./dist/react.dll.js"></script>
<!--导入执行入口文件-->
<script src="./dist/main.js"></script>
</body>
</html>

以上就是所有接入 DllPlugin 后最终编译出来的代码,接下来教你如何实现。

构建出动态链接库文件

构建输出的以下这四个文件:

├── polyfill.dll.js
├── polyfill.manifest.json
├── react.dll.js
└── react.manifest.json

和以下这一个文件:

├── main.js

是由两份不同的构建分别输出的。

动态链接库文件相关的文件需要由一份独立的构建输出,用于给主构建使用。新建一个 webpack 配置文件 webpack_dll.config.js 专门用于构建它们,文件内容如下:

const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')

module.exports = {
  // JS 执行入口文件
  entry: {
    // 把 React 相关模块放到一个单独的动态链接库
    react: ['react', 'react-dom'],
    // 把项目需要所有的 polyfill 放到一个单独的动态链接库
    polyfill: ['core-js/fn/object/assign', 'core-js/fn/promise', 'whatwg-fetch']
  },
  output: {
    // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
    // 也就是entry中配置的 react 和 polyfill
    filename: '[name].dll.js',
    // 存放的文件都存放到 dist 目录下
    path: path.resolve(__dirname, 'dist'),
    // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
    // 之所以在前面加上 _dll_是为了防止全局变量冲突
    library: '_dll_[name]'
  },
  plugins: [
    // 接入 DllPlugin
    new DllPlugin({
      // 动态链接库的全局变量名称,需要和 output.library 中保持一致
      // 该字段的值也就是输出的 manifest.json 文件中 name 字段的值
      // 例如 react.mainfest.json 中就有 "name": "_dll_react"
      name: '_dll_[name]',
      // 描述动态链接库的 mainfest.json 文件输出时的文件名称
      path: path.join(__dirname, 'dist', '[name].mainfest.json')
    })
  ]
}

使用动态链接库文件

构建出的动态链接库文件用于给其他地方使用,在这里也就是给执行使用。

用于输出 mian.js 的主 webpack 配置文件内容如下:

const path = require('path');
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');

module.exports = {
  entry: {
    // 定义入口 Chunk
    main: './main.js'
  },
  output: {
    // 输出文件的名称
    filename: '[name].js',
    // 输出文件都放到 dist 目录下
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        // 项目源码使用了 ES6 和 JSX 语法,需要使用 babel-loader 转换
        test: /\.js$/,
        use: ['babel-loader'],
        exclude: path.resolve(__dirname, 'node_modules'),
      },
    ]
  },
  plugins: [
    // 告诉 Webpack 使用了哪些动态链接库
    new DllReferencePlugin({
      // 描述 react 动态链接库的文件内容
      manifest: require('./dist/react.manifest.json'),
    }),
    new DllReferencePlugin({
      // 描述 polyfill 动态链接库的文件内容
      manifest: require('./dist/polyfill.manifest.json'),
    }),
  ],
  devtool: 'source-map'
};

注意: 在 webpack_dll.config.js 文件中,plugins DllPlugin 中的 name 参数必须和 output.library 中保持一致。原因在于 DllPlugin 中的 name 参数会影响输出的 mainfest.json 文件中的 name 字段的值,而在 webpack.config.js 文件中 DllReferencePlugin 会去 mainfest.json 文件读取 name 字段的值,把值的内容作为在从全局变量中获取动态链接库中内容时的全局变量名。

执行构建

在修改好以上两个 webpack 配置文件后,需要重新执行构建。重新执行构建时要注意的是需要先把动态链接库相关的文件编译出来,因为主 webpack 配置文件中定义的 DllReferencePlugin 依赖这些文件。

执行构建时流程如下:

  1. 如果动态链接库相关的文件还没有编译出来,就需要先把它们编译出来。方法是执行webpack --config webpack_dll.config.js 命令。

  2. 在确保动态链接库存在时,才能正常的编译出入口执行文件。方法是执行 webpack 命令。

使用 HappyPack

接入 HappyPack 的相关代码如下:

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HappyPack = require('happypack');

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
        use: ['happypack/loader?id=babel'],
        // 排除 node_modules 目录下的文件,node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        // 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          use: ['happypack/loader?id=css'],
        }),
      },
    ]
  },
  plugins: [
    new HappyPack({
      // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
      id: 'babel',
      // 如何处理 .js 文件,用法和 Loader 配置中一样
      loaders: ['babel-loader?cacheDirectory'],
      // ... 其它配置项
    }),
    new HappyPack({
      id: 'css',
      // 如何处理 .css 文件,用法和 Loader 配置中一样
      loaders: ['css-loader'],
    }),
    new ExtractTextPlugin({
      filename: `[name].css`,
    }),
  ],
};

以上代码有两点重要的修改:

  • 在 Loader 配置中,所有文件的处理都交给了 happypack/loader 去处理,使用紧跟其后的 querystring ?id=babel 去告诉 happypack/loader 去选择哪个 HappyPack 实例去处理文件。
  • 在 Plugin 配置中,新增了两个 HappyPack 实例分别用于告诉 happypack/loader 去如何处理 .js.css 文件。选项中的 id 属性的值和上面 querystring 中的 ?id=babel 相对应,选项中的 loaders 属性和 Loader 配置中一样。

在实例化 HappyPack 插件的时候,除了可以传入 idloaders 两个参数外, HappyPack 还支持如下参数:

  • threads 代表开启几个子进程去处理这一类型的文件,默认是 3 个,类型必须是整数。
  • verbose 是否允许 HappyPack 输出日志, 默认是 true
  • threadPool 代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多,相关代码如下:
const HappyPack = require('happypack');
// 构造出共享进程池,进程池中包含5个子进程
const happyThreadPool = HappyPack.ThreadPool({ size: 5 });

module.exports = {
  plugins: [
    new HappyPack({
      // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
      id: 'babel',
      // 如何处理 .js 文件,用法和 Loader 配置中一样
      loaders: ['babel-loader?cacheDirectory'],
      // 使用共享进程池中的子进程去处理任务
      threadPool: happyThreadPool,
    }),
    new HappyPack({
      id: 'css',
      // 如何处理 .css 文件,用法和 Loader 配置中一样
      loaders: ['css-loader'],
      // 使用共享进程池中的子进程去处理任务
      threadPool: happyThreadPool,
    }),
    new ExtractTextPlugin({
      filename: `[name].css`,
    }),
  ],
};

使用 ParallelUglifyPlugin

ParallelUglifyPlugin 则会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过 UglifyJS 去压缩代码,但是变成了并行执行

使用 ParallelUglifyPlugin 也非常简单,把原来 Webpack 配置文件中内置的 UglifyJsPlugin 去掉后,再替换成 ParallelUglifyPlugin,相关代码如下:

const path = require('path');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

module.exports = {
  plugins: [
    // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
    new ParallelUglifyPlugin({
      // 传递给 UglifyJS 的参数
      uglifyJS: {
        output: {
          // 最紧凑的输出
          beautify: false,
          // 删除所有的注释
          comments: false,
        },
        compress: {
          // 在UglifyJs删除没有用到的代码时不输出警告
          warnings: false,
          // 删除所有的 `console` 语句,可以兼容ie浏览器
          drop_console: true,
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        }
      },
    }),
  ],
};

优化总结

侧重优化开发体验的配置文件 webpack.config.js:

const path = require('path');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const {AutoWebPlugin} = require('web-webpack-plugin');
const HappyPack = require('happypack');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

// 自动寻找 pages 目录下的所有目录,把每一个目录看成一个单页应用
const autoWebPlugin = new AutoWebPlugin('./src/pages', {
  // HTML 模版文件所在的文件路径
  template: './template.html',
  // 提取出所有页面公共的代码
  commonsChunk: {
    // 提取出公共代码 Chunk 的名称
    name: 'common',
  },
  // 指定存放 CSS 文件的 CDN 目录 URL
  stylePublicPath: '//css.cdn.com/id/',
});

module.exports = {
  // AutoWebPlugin 会找为寻找到的所有单页应用,生成对应的入口配置,
  // autoWebPlugin.entry 方法可以获取到生成入口配置
  entry: autoWebPlugin.entry({
    // 这里可以加入你额外需要的 Chunk 入口
    base: './src/base.js',
  }),
  output: {
    // 给输出的文件名称加上 Hash 值
    filename: '[name]_[chunkhash:8].js',
    path: path.resolve(__dirname, './dist'),
    // 指定存放 JavaScript 文件的 CDN 目录 URL
    publicPath: '//js.cdn.com/id/',
  },
  resolve: {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    // 其中 __dirname 表示当前工作目录,也就是项目根目录
    modules: [path.resolve(__dirname, 'node_modules')],
    // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
    mainFields: ['jsnext:main', 'main'],
  },
  module: {
    rules: [
      {
        // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
        test: /\.js$/,
        // 使用 HappyPack 加速构建
        use: ['happypack/loader?id=babel'],
        // 只对项目根目录下的 src 目录中的文件采用 babel-loader
        include: path.resolve(__dirname, 'src'),
      },
      {
        test: /\.js$/,
        use: ['happypack/loader?id=ui-component'],
        include: path.resolve(__dirname, 'src'),
      },
      {
        // 增加对 CSS 文件的支持
        test: /\.css/,
        // 提取出 Chunk 中的 CSS 代码到单独的文件中
        use: ExtractTextPlugin.extract({
          use: ['happypack/loader?id=css'],
          // 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL
          publicPath: '//img.cdn.com/id/'
        }),
      },
    ]
  },
  plugins: [
    autoWebPlugin,
    // 4-14开启ScopeHoisting
    new ModuleConcatenationPlugin(),
    // 4-3使用HappyPack
    new HappyPack({
      // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
      id: 'babel',
      // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
      loaders: ['babel-loader?cacheDirectory'],
    }),
    new HappyPack({
      // UI 组件加载拆分
      id: 'ui-component',
      loaders: [{
        loader: 'ui-component-loader',
        options: {
          lib: 'antd',
          style: 'style/index.css',
          camel2: '-'
        }
      }],
    }),
    new HappyPack({
      id: 'css',
      // 如何处理 .css 文件,用法和 Loader 配置中一样
      // 通过 minimize 选项压缩 CSS 代码
      loaders: ['css-loader?minimize'],
    }),
    new ExtractTextPlugin({
      // 给输出的 CSS 文件名称加上 Hash 值
      filename: `[name]_[contenthash:8].css`,
    }),
    // 4-11提取公共代码
    new CommonsChunkPlugin({
      // 从 common 和 base 两个现成的 Chunk 中提取公共的部分
      chunks: ['common', 'base'],
      // 把公共的部分放到 base 中
      name: 'base'
    }),
    new DefinePlugin({
      // 定义 NODE_ENV 环境变量为 production 去除 react 代码中的开发时才需要的部分
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    }),
    // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
    new ParallelUglifyPlugin({
      // 传递给 UglifyJS 的参数
      uglifyJS: {
        output: {
          // 最紧凑的输出
          beautify: false,
          // 删除所有的注释
          comments: false,
        },
        compress: {
          // 在UglifyJs删除没有用到的代码时不输出警告
          warnings: false,
          // 删除所有的 `console` 语句,可以兼容ie浏览器
          drop_console: true,
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        }
      },
    }),
  ]
};

参考文档

  1. 全栈开发

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.