kedaya

kedaya 查看完整档案

北京编辑南开大学  |  软件工程 编辑快手  |  前端研发工程师 编辑填写个人主网站
编辑

要知其然 更要知其所以然

个人动态

kedaya 收藏了文章 · 2019-04-10

程序员练级攻略(2018):前端基础和底层原理

图片描述

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

这个是我订阅 陈皓老师在极客上的专栏《左耳听风》,我整理出来是为了自己方便学习,同时也分享给你们一起学习,当然如果有兴趣,可以去订阅,为了避免广告嫌疑,我这就不多说了!以下第一人称是指陈皓老师。

对于前端的学习和提高,我的基本思路是这样的。首先,前端的三个最基本的东西 HTML5、CSS3 和 JavaScript(ES6)是必须要学好的。这其中有很多很多的技术,比如,CSS3 引申出来的 Canvas(位图)、SVG(矢量图) 和 WebGL(3D 图),以及 CSS 的各种图形变换可以让你做出非常丰富的渲染效果和动画效果。

ES6 简直就是把 JavaScript 带到了一个新的台阶,JavaScript 语言的强大,大大释放了前端开发人员的生产力,让前端得以开发更为复杂的代码和程序,于是像 React 和 Vue 这样的框架开始成为前端编程的不二之选。

我一直认为学习任何知识都要从基础出发,所以我会有很大的篇幅在讲各种技术的基础知识和基本原理,尤其是如下的这些知识,都是前端程序员需要一块一块啃掉的硬骨头。

  • JavaScript 的核心原理。这里我会给出好些网上很不错的讲 JavaScript 的原理的文章或图书,你一定要学好语言的特性和其中的各种坑。
  • 浏览器的工作原理。这也是一块硬骨头,我觉得这是前端程序员需要了解和明白的东西,不然,你将无法深入下去。
  • 网络协议 HTTP。也是要着重了解的,尤其是 HTTP/2,还有 HTTP 的几种请求方式:短连接、长连接、Stream 连接、WebSocket 连接。
  • 前端性能调优。有了以上的这些基础后,你就可以进入前端性能调优的主题了,我相信你可以很容易上手各种性能调优技术的。
  • 框架学习。我只给了 React 和 Vue 两个框架。就这两个框架来说,Virtual DOM 技术是其底层技术,组件化是其思想,管理组件的状态是其重点。而对于 React 来说,函数式编程又是其编程思想,所以,这些基础技术都是你需要好好研究和学习的。
  • UI 设计。设计也是前端需要做的一个事,比如像 Google 的 Material UI,或是比较流行的 Atomic Design 等应该是前端工程师需要学习的。

而对于工具类的东西,这里我基本没怎么涉及,因为本文主要还是从原理和基础入手。那些工具我觉得都很简单,就像学习 Java 我没有让你去学习 Maven 一样,因为只要你去动手了,这种知识你自然就会获得,我们还是把精力重点放在更重要的地方。

下面我们从前端基础和底层原理开始讲起。先来讲讲 HTML5 相关的内容。

HTML5

  • HTML5 权威指南 ,本书面向初学者和中等水平 Web 开发人员,是牢固掌握 HTML5、CSS3 和 JavaScript 的必读之作。书看起来比较厚,是因为里面的代码很多。
  • HTML5 Canvas 核心技术 ,如果你要做 HTML5 游戏的话,这本书必读。

对于 SVG、Canvas 和 WebGL 这三个对应于矢量图、位图和 3D 图的渲染来说,给前端开发带来了重武器,很多 HTML5 小游戏也因此蓬勃发展。所以,你可以学习一下。

学习这三个技术,我个人觉得最好的地方是 MDN。

最后是几个资源列表。

CSS

在《程序员练级攻略(2018)》系列文章最开始,我们就推荐过 CSS 的在线学习文档,这里再推荐一下

MDN Web Doc - CSS 。我个人觉得只要你仔细读一下文档,CSS 并不难学。绝大多数觉得难的,一方面是文档没读透,另一方面是浏览器支持的标准不一致。所以,学好 CSS 最关键的还是要仔细地读文档。

之后,在写 CSS 的时候,你会发现,你的 CSS 中有很多看起来相似的东西。你的 DRY - Don’t Repeat Yourself 洁癖告诉你,这是不对的。所以,你需要学会使用 LESSSaSS
这两个 CSS 预处理工具,其可以帮你提高很多效率。

然后,你需要学习一下 CSS 的书写规范,前面的《程序员修养》一文中提到过一些,这里再补充几个。

如果你需要更有效率,那么你还需要使用一些 CSS Framework,其中最著名的就是 Twitter 公司的 Bootstrap,其有很多不错的 UI 组件,页面布局方案,可以让你非常方便也非常快速地开发页面。除此之外,还有,主打清新 UI 的 Semantic UI 、主打响应式界面的 Foundation 和基于 Flexbox 的 Bulma

当然,在使用 CSS 之前,你需要把你浏览器中的一些 HTML 标签给标准化掉。所以,推荐几个 Reset 或标准化的 CSS 库:NormalizeMiniRest.csssanitize.cssunstyle.css

关于更多的 CSS 框架,你可以参看 Awesome CSS Frameworks

接下来,是几个公司的 CSS 相关实践,供你参考。

CodePen’s CSS

Github 的 CSS

Medium’s CSS is actually pretty f*ing good

CSS at BBC Sport

Refining The Way We Structure Our CSS At Trello

最后是一个可以写出可扩展的 CSS 的阅读列表 A Scalable CSS Reading List

JavaScript

下面是学习 JavaScript 的一些图书和文章。

浏览器原理

你需要了解一下浏览器是怎么工作的,所以,你必需要看《How browsers work》。这篇文章受众之大,后来被人重新整理并发布为《How Browsers Work: Behind the scenes of modern web browsers》,其中还包括中文版。这篇文章非常非常长,所以,你要有耐心看完。如果你想看个精简版的,可以看我在 Coolshell 上发的《浏览器的渲染原理简介》或是看一下这个幻灯片

然后,是对 Virtual DOM 的学习。Virtual DOM 是 React 的一个非常核心的技术细节,它也是前端渲染和性能的关键技术。所以,你有必要要好好学习一下这个技术的实现原理和算法。当然,前提条件是你需要学习过前面我所推荐过的浏览器的工作原理。下面是一些不错的文章可以帮你学习这一技术。

网络协议

小结

总结一下今天的内容。我一直认为学习任何知识都要从基础出发,所以今天我主要讲述了 HTML5、CSS3 和 JavaScript(ES6)这三大基础核心,给出了大量的图书、文章以及其他一些相关的学习资源。之后,我建议你学习浏览器的工作原理和网络协议相关的内容。我认为,掌握这些原理也是学好前端知识的前提和基础。值得花时间,好好学习消化。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

你们的点赞是我持续分享好东西的动力,欢迎点赞!

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

查看原文

kedaya 评论了文章 · 2019-04-03

面试官问你基本类型时他想知道什么

前言: 面试的时候我们经常会被问答js的数据类型。大部分情况我们会这样回答包括
1、基本类型(值类型或者原始类型): Number、Boolean、String、NULL、Undefined以及ES6的Symbol
2、引用类型:Object、Array、Function、Date等
我曾经也是这样回答的,并且一直觉得没有什么问题。

1 、在内存中的位置不同

  • 基本类型: 占用空间固定,保存在栈中
  • 引用类型:占用空间不固定,保存在堆中
栈(stack)为自动分配的内存空间,它由系统自动释放;使用一级缓存,被调用时通常处于存储空间中,调用后被立即释放。
堆(heap)则是动态分配的内存,大小不定也不会自动释放。使用二级缓存,生命周期与虚拟机的GC算法有关

当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;栈中存储的是基础变量以及一些对象的引用变量,基础变量的值是存储在栈中,而引用变量存储在栈中的是指向堆中的数组或者对象的地址,这就是为何修改引用类型总会影响到其他指向这个地址的引用变量。

当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。

2、赋值、浅拷贝、深拷贝

  • 对于基本类型值,赋值、浅拷贝、深拷贝时都是复制基本类型的值给新的变量,之后二个变量之间操作不在相互影响。
  • 对于引用类型值,

    • 赋值后二个变量指向同一个地址,一个变量改变时,另一个也同样改变;
    • 浅拷贝后得到一个新的变量,这个与之前的已经不是指向同一个变量,改变时不会使原数据中的基本类型一同改变,但会改变会原数据中的引用类型数据
    • 深拷贝后得到的是一个新的变量,她的改变不会影响元数据
-和原数据是否指向同一对象第一层数据为基本数据类型原数据中包含子对象
赋值改变会使原数据一同改变改变会使原数据一同改变
浅拷贝改变不会使原数据一同改变改变会使原数据一同改变
深拷贝改变不会使原数据一同改变改变不会使原数据一同改变
    var obj1 = {
        'name' : 'zhangsan',
        'age' :  '18',
        'language' : [1,[2,3],[4,5]],
    };

    var obj2 = obj1;


    var obj3 = shallowCopy(obj1);
    function shallowCopy(src) {
        var dst = {};
        for (var prop in src) {
            if (src.hasOwnProperty(prop)) {
                dst[prop] = src[prop];
            }
        }
        return dst;
    }

    obj2.name = "lisi";
    obj3.age = "20";

    obj2.language[1] = ["二","三"];
    obj3.language[2] = ["四","五"];

    console.log(obj1);  
    //obj1 = {
    //    'name' : 'lisi',
    //    'age' :  '18',
    //    'language' : [1,["二","三"],["四","五"]],
    //};

    console.log(obj2);
    //obj2 = {
    //    'name' : 'lisi',
    //    'age' :  '18',
    //    'language' : [1,["二","三"],["四","五"]],
    //};

    console.log(obj3);
    //obj3 = {
    //    'name' : 'zhangsan',
    //    'age' :  '20',
    //    'language' : [1,["二","三"],["四","五"]],
    //};

2.1、浅拷贝

数组常用的浅拷贝方法slice,concat,Array.from() ,以及es6的析构

var arr1 = [1, 2,{a:1,b:2,c:3,d:4}];
var arr2 = arr1.slice();
var arr3 = arr1.concat();
var arr4 = Array.from(arr1);
var arr5 = [...arr1];
arr2[0]=2;
arr2[2].a=2;
arr3[0]=3;
arr3[2].b=3;
arr4[0]=4;
arr4[2].c=4;
arr5[0]=5;
arr5[2].d=5;
// arr1[1,2,{a:2,b:3,c:4,d:5}]
// arr2[2,2,{a:2,b:3,c:4,d:5}]
// arr3[3,2,{a:2,b:3,c:4,d:5}]
// arr4[4,2,{a:2,b:3,c:4,d:5}]
// arr5[5,2,{a:2,b:3,c:4,d:5}]

对象常用的浅拷贝方法Object.assign(),es6析构

var obj1 = {
    x: 1, 
    y: {
        m: 1
    }
};
var obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.x=2;
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 2}}
console.log(obj2) //{x: 2, y: {m: 2}}

我们自己实现一个浅拷贝

var obj = { a:1, arr: [2,3] };
var shallowObj = shallowCopy(obj);

var shallowCopy = function(obj) {
    // 只拷贝对象
    if (typeof obj !== 'object') return;
    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

2.2、深拷贝

比较简单粗暴的的做法是使用JSON.parse(JSON.stringify(obj))

var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse( JSON.stringify(arr) );
new_arr[4].old=4;
console.log(arr); //['old', 1, true, ['old1', 'old2'], {old: 1}]
console.log(new_arr); //['old', 1, true, ['old1', 'old2'], {old: 4}]

JSON.parse(JSON.stringify(obj)) 看起来很不错,不过MDN文档 的描述有句话写的很清楚:

undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。

但是在平时的开发中JSON.parse(JSON.stringify(obj))已经满足90%的使用场景了。
下面我们自己来实现一个

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    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;
}

3、参数的传递

所有的函数参数都是按值传递。也就是说把函数外面的值赋值给函数内部的参数,就和把一个值从一个变量赋值给另一个一样;

  • 基本类型
var a = 2;
function add(x) {
 return x = x + 2;
}
var result = add(a);
console.log(a, result); // 2 4

引用类型

function setName(obj) {
  obj.name = 'laowang';
  obj = new Object();
  obj.name = 'Tom';
}
var person = new Object();
setName(person);
console.log(person.name); //laowang

很多人错误地以为在局部作用域中修改的对象在全局作用域中反映出来就是说明参数是按引用传递的。
但是通过上面的例子可以看出如果person是按引用传递的最终的person.name应该是Tom。
实际上当函数内部重写obj时,这个变量引用的就是一个局部变量了。而这个变量会在函数执行结束后销毁。(这是是在js高级程序设计看到的,还不是很清楚)

4、判断方法

基本类型用typeof,引用类型用instanceof

特别注意typeof null"object", null instanceof Objectfalse;
console.log(typeof "Nicholas"); // "string"
console.log(typeof 10);         // "number"
console.log(typeof true);       // "boolean"
console.log(typeof undefined);  // "undefined"
console.log(typeof null);      // "object"


var items = [];
var obj = {};
function reflect(value){
  return value;
}

console.log(items instanceof Array); // true;
console.log(obj instanceof Object); // true;
console.log(reflect instanceof Function); // true;

Object.prototype.toString.call([]).slice(8, -1)有兴趣的同学可以看一下这个是干什么的

5、总结

图片描述

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star对作者也是一种鼓励。
查看原文

kedaya 评论了文章 · 2019-04-02

记2019前端面经

Motivation

2019寒冬来临,卷入动荡之中只能又开启了漫漫求职路。有辛酸,有坎坷,但也有点小幸运。

Experience

前前后后面试了6家公司,总结一下问题做一个backup。(仅记录问题,不区分面试轮次)

  • 脉脉

    • 框架

      • 简述vue的基本原理
      • vue的生命周期
      • vue与react的不同
      • vue父子通信的方式
      • vuex的原理及理解
      • vue v-model如何实现的,语法糖实际是什么
      • react 生命周期
      • react context 的理解
      • redux的原理
      • react-redux的原理
      • 如何避免render的触发
      • 说一下react vnode的diff算法
      • vnode的引入与直接操作原生dom相比,哪一个相率更高,为什么
    • 基础知识

      • 对缓存的理解
      • 对http2的理解
      • 对https的理解
      • 对原型链的理解,画一个经典的原型连接图
      • 对es6了解多少
      • 箭头函数与正常函数有哪些区别
      • class的实现 用原型写一个继承
      • ajax axios fetch的区别
      • 如何用promise封装一个ajax
      • 项目中的难点和亮点

二面送走,说会联系也没联系我 > <.(面试体验和福利还是挺好的)

  • 高德

    • 框架

      • React组件的生命周期
      • React父子组件如何通信的
      • React层级很深的组件如何通信传值(Context API)
      • React做了哪些性能优化 PureComponent的实现原理是什么
      • React setState后都会发生什么,是否了解
      • React 1000个列表节点渲染,给出一个优化方案
      • 是否了解React事件机制,如果让你实现如何来设计
      • Redux的原理及理解
      • react-redux是如何来实现的,connect是不是一个高阶函数,原理是什么
      • react与vue相比,有什么不同
    • 基础知识

      • 对缓存的理解,需要什么设置
      • 对代码构建上线流程是否了解,说一下如果让你实现的思路
      • 谈谈对webpack的理解,常用哪些plugin,对webpack配置是否了解,对项目打包是否做过什么优化
      • ES6常用到哪些,对class的理解,手写一个对继承的实现
      • Promise是否了解,如何实现一个promise
      • class继承中子类想使用父类的方法,应该用什么方式调用(super的意义)
      • 箭头函数与正常函数的区别
      • css实现border渐变
      • css实现下阴影(气泡类 带箭头的阴影)
      • css对flex的理解
      • 对浏览器渲染机制的理解(具体到细节,从渲染树到paint之间究竟发生了什么)
      • 纯css实现一个高宽比为1:3的盒子 列举几种方式
      • 浏览器的架构,bom,dom
  • 马蜂窝

    • 框架

      • vue的生命周期
      • 组件A下有子组件B、C,那么3个组件生命周期的调用顺序,同级组件mounted触发一定是先调用先call吗?同步还是异步?
      • vue的基本原理
      • vue eventbus的实现
      • vue父子组件的通信
      • vuex的使用
    • 基础知识

      • 对https的理解,对称、非对称加密在哪部使用
      • css布局 各种定位的方式
      • css实现水平垂直居中
      • css实现一个旋转的圆
      • cookie 跨域的处理方案
      • cookie 种在子域下能否携带发送到服务端(SSO登录)
      • 写一个函数,第一次调用返回0,之后每次调用返回比之前大1
      • 闭包、作用域的理解
      • 用原生xhr发送一个请求
      • 跨域请求可以携带cookie吗
      • axios与xhr的区别,如何用promise包装xhr
      • 讲讲项目中的难点

要吐槽一下。1面好看的小姐姐面完直接告诉我当备胎了,送我出去了= =。。。(不过马蜂窝的办公环境是真的挺美的,跟花园一样)

  • 猿辅导

    • 框架

      • vue的生命周期
      • vue双向绑定的原理
      • vue父子组件通信的方式
      • vue eventbus的原理
      • 对vuex的理解
      • 谈谈对vue和react对比,并从中能学到什么
      • vue中可以对对象进行数据监听,如果对于数组中的某个元素能否监听,是如何做到的
    • 基础知识

      • http的头部有什么字段,简要描述(缓存,content-type,cookie等等)
      • cookie跨域服务端需要如何适配(CORS头)
      • 一个请求跨域是否会抵达服务端
      • 对之前的项目做过什么优化,讲一讲
      • 对之前的项目遇到过什么难点,讲一讲
      • 对http2有哪些了解
      • 对canvas有哪些性能上的优化
      • 对settimeout和对setinterval的理解(涉及代码题倒计时函数,eventloop的考点)计时是否准确?如何实现较为准确的计时?
      • 对一个短时间并发高的场景需要如何处理(后端设计)(开始答题获取试卷的场景)
    • 写代码

      • 写一个倒计时函数
      • 写一个函数,给定一棵树,输出这棵树的深度
      • 写一个函数,给定一个url和最大深度maxdeep,输出抓取当前url及其子链接深度范围内的所有图片
      • 写一个函数,给定nodes=[],每一个节点拥有id,name,parentid,输出一个属性列表的展示(涉及dom操作)
  • 抖音

    • 框架

      • vue数据绑定的实现原理
      • vue computed具体在什么阶段进行的依赖收集,具体的过程详细描述
      • vuex和redux的差别
    • 基础知识

      • 跨域的解决办法
      • 原型链的理解,写一个原型继承
      • 实现一个sendRequest,有最大请求并发限制
      • EventLoop的理解
      • 浏览器渲染触发在EventLoop的哪个阶段,触发机制是怎么样的
      • https 建立连接的过程及通信 如何劫持,鉴别是否被劫持
      • ES module与cjs的区别
      • Tree shaking的实现原理
      • 给定一个sum 检验一棵树中,是否存在一条路径和为sum,输出该路径

二面送走,感觉到自己在一些问题的深度上还有待加深。

  • 快手

    • 框架

      • 对vuex源码上如何实现只能mutation更改,而不能直接更改
      • vuex中如何在层层都可以引用$store
      • vuex和redux的差别
    • 基础知识

      • 笔试题6页
      • css优先级关系
      • eventloop的先后顺序(node内)
      • node中的垃圾收集机制
      • BFC,IFC,FFC的区别
      • a11y是什么,如何理解
      • prototype的考察
      • TDZ的考察
      • 写一个数组方法,打乱整个数组顺序,并且每个数字落在各个位置的概率相同
      • one(add(two())) // 3 two(add(one())) // 3 写出 one() two() add()的实现
      • 实现一个catchPromise 发同一个请求缓存data 在实际网络层相同url只会发出一个请求
      • 给定 n 个 {x, y, w, h}的盒子 按需排列,左上聚拢(层叠后的max(h)*max(w)最小),求给一个{w,h},输出放置的位置
      • 从输入一个url到呈现网页,都有哪些步骤
      • http keep—alive都解决了哪些问题 keep-alive是从c - nginx建立的还是直接到服务建立的长连接,与websocket有什么区别与联系
      • 给定一个html,输出其中包含的html标签数量,可以用domapi 注意iframe
      • 实现一个NumberStack,实现pop,push,max(n)方法,max(n)返回第n大的数,max(n)需要 O(1)的时间复杂度
      • 实现一个bind函数
      • 跨域的解决办法,jsonp的实现原理
注:一些各个公司问的相同的问题就没有再重复写出来

Result

祸兮福所倚,福兮祸所伏。虽然经历了寒冬和求职的奔波,但也因为面试才对自己有了一个更加清楚地认识,知道自己未来的路更应该如何去走。这半年来因为种种原因对技术上有些许的松懈和怠慢,所幸还能拿到了自己很满意的offer,未来的路还要更加努力的走~

查看原文

kedaya 收藏了文章 · 2019-04-02

面试官:前端跨页面通信,你知道哪些方法?

引言

在浏览器中,我们可以同时打开多个Tab页,每个Tab页可以粗略理解为一个“独立”的运行环境,即使是全局对象也不会在多个Tab间共享。然而有些时候,我们希望能在这些“独立”的Tab页面之间同步页面的数据、信息或状态。

正如下面这个例子:我在列表页点击“收藏”后,对应的详情页按钮会自动更新为“已收藏”状态;类似的,在详情页点击“收藏”后,列表页中按钮也会更新。

跨页面通信实例

这就是我们所说的前端跨页面通信。

你知道哪些跨页面通信的方式呢?如果不清楚,下面我就带大家来看看七种跨页面通信的方式。


一、同源页面间的跨页面通信

以下各种方式的 在线 Demo 可以戳这里 >>

浏览器的同源策略在下述的一些跨页面通信方法中依然存在限制。因此,我们先来看看,在满足同源策略的情况下,都有哪些技术可以用来实现跨页面通信。

1. BroadCast Channel

BroadCast Channel 可以帮我们创建一个用于广播的通信频道。当所有页面都监听同一频道的消息时,其中某一个页面通过它发送的消息就会被其他所有页面收到。它的API和用法都非常简单。

下面的方式就可以创建一个标识为AlienZHOU的频道:

const bc = new BroadcastChannel('AlienZHOU');

各个页面可以通过onmessage来监听被广播的消息:

bc.onmessage = function (e) {
    const data = e.data;
    const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
    console.log('[BroadcastChannel] receive message:', text);
};

要发送消息时只需要调用实例上的postMessage方法即可:

bc.postMessage(mydata);
Broadcast Channel 的具体的使用方式可以看这篇《【3分钟速览】前端广播式通信:Broadcast Channel》

2. Service Worker

Service Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。

Service Worker 也是 PWA 中的核心技术之一,由于本文重点不在 PWA ,因此如果想进一步了解 Service Worker,可以阅读我之前的文章【PWA学习与实践】(3) 让你的WebApp离线可用

首先,需要在页面注册 Service Worker:

/* 页面逻辑 */
navigator.serviceWorker.register('../util.sw.js').then(function () {
    console.log('Service Worker 注册成功');
});

其中../util.sw.js是对应的 Service Worker 脚本。Service Worker 本身并不自动具备“广播通信”的功能,需要我们添加些代码,将其改造成消息中转站:

/* ../util.sw.js Service Worker 逻辑 */
self.addEventListener('message', function (e) {
    console.log('service worker receive message', e.data);
    e.waitUntil(
        self.clients.matchAll().then(function (clients) {
            if (!clients || clients.length === 0) {
                return;
            }
            clients.forEach(function (client) {
                client.postMessage(e.data);
            });
        })
    );
});

我们在 Service Worker 中监听了message事件,获取页面(从 Service Worker 的角度叫 client)发送的信息。然后通过self.clients.matchAll()获取当前注册了该 Service Worker 的所有页面,通过调用每个client(即页面)的postMessage方法,向页面发送消息。这样就把从一处(某个Tab页面)收到的消息通知给了其他页面。

处理完 Service Worker,我们需要在页面监听 Service Worker 发送来的消息:

/* 页面逻辑 */
navigator.serviceWorker.addEventListener('message', function (e) {
    const data = e.data;
    const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
    console.log('[Service Worker] receive message:', text);
});

最后,当需要同步消息时,可以调用 Service Worker 的postMessage方法:

/* 页面逻辑 */
navigator.serviceWorker.controller.postMessage(mydata);

3. LocalStorage

LocalStorage 作为前端最常用的本地存储,大家应该已经非常熟悉了;但StorageEvent这个与它相关的事件有些同学可能会比较陌生。

当 LocalStorage 变化时,会触发storage事件。利用这个特性,我们可以在发送消息时,把消息写入到某个 LocalStorage 中;然后在各个页面内,通过监听storage事件即可收到通知。

window.addEventListener('storage', function (e) {
    if (e.key === 'ctc-msg') {
        const data = JSON.parse(e.newValue);
        const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
        console.log('[Storage I] receive message:', text);
    }
});

在各个页面添加如上的代码,即可监听到 LocalStorage 的变化。当某个页面需要发送消息时,只需要使用我们熟悉的setItem方法即可:

mydata.st = +(new Date);
window.localStorage.setItem('ctc-msg', JSON.stringify(mydata));

注意,这里有一个细节:我们在mydata上添加了一个取当前毫秒时间戳的.st属性。这是因为,storage事件只有在值真正改变时才会触发。举个例子:

window.localStorage.setItem('test', '123');
window.localStorage.setItem('test', '123');

由于第二次的值'123'与第一次的值相同,所以以上的代码只会在第一次setItem时触发storage事件。因此我们通过设置st来保证每次调用时一定会触发storage事件。

小憩一下

上面我们看到了三种实现跨页面通信的方式,不论是建立广播频道的 Broadcast Channel,还是使用 Service Worker 的消息中转站,抑或是些 tricky 的storage事件,其都是“广播模式”:一个页面将消息通知给一个“中央站”,再由“中央站”通知给各个页面。

在上面的例子中,这个“中央站”可以是一个 BroadCast Channel 实例、一个 Service Worker 或是 LocalStorage。

下面我们会看到另外两种跨页面通信方式,我把它称为“共享存储+轮询模式”。


4. Shared Worker

Shared Worker 是 Worker 家族的另一个成员。普通的 Worker 之间是独立运行、数据互不相通;而多个 Tab 注册的 Shared Worker 则可以实现数据共享。

Shared Worker 在实现跨页面通信时的问题在于,它无法主动通知所有页面,因此,我们会使用轮询的方式,来拉取最新的数据。思路如下:

让 Shared Worker 支持两种消息。一种是 post,Shared Worker 收到后会将该数据保存下来;另一种是 get,Shared Worker 收到该消息后会将保存的数据通过postMessage传给注册它的页面。也就是让页面通过 get 来主动获取(同步)最新消息。具体实现如下:

首先,我们会在页面中启动一个 Shared Worker,启动方式非常简单:

// 构造函数的第二个参数是 Shared Worker 名称,也可以留空
const sharedWorker = new SharedWorker('../util.shared.js', 'ctc');

然后,在该 Shared Worker 中支持 get 与 post 形式的消息:

/* ../util.shared.js: Shared Worker 代码 */
let data = null;
self.addEventListener('connect', function (e) {
    const port = e.ports[0];
    port.addEventListener('message', function (event) {
        // get 指令则返回存储的消息数据
        if (event.data.get) {
            data && port.postMessage(data);
        }
        // 非 get 指令则存储该消息数据
        else {
            data = event.data;
        }
    });
    port.start();
});

之后,页面定时发送 get 指令的消息给 Shared Worker,轮询最新的消息数据,并在页面监听返回信息:

// 定时轮询,发送 get 指令的消息
setInterval(function () {
    sharedWorker.port.postMessage({get: true});
}, 1000);

// 监听 get 消息的返回数据
sharedWorker.port.addEventListener('message', (e) => {
    const data = e.data;
    const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
    console.log('[Shared Worker] receive message:', text);
}, false);
sharedWorker.port.start();

最后,当要跨页面通信时,只需给 Shared Worker postMessage即可:

sharedWorker.port.postMessage(mydata);
注意,如果使用addEventListener来添加 Shared Worker 的消息监听,需要显式调用MessagePort.start方法,即上文中的sharedWorker.port.start();如果使用onmessage绑定监听则不需要。

5. IndexedDB

除了可以利用 Shared Worker 来共享存储数据,还可以使用其他一些“全局性”(支持跨页面)的存储方案。例如 IndexedDB 或 cookie。

鉴于大家对 cookie 已经很熟悉,加之作为“互联网最早期的存储方案之一”,cookie 已经在实际应用中承受了远多于其设计之初的责任,我们下面会使用 IndexedDB 来实现。

其思路很简单:与 Shared Worker 方案类似,消息发送方将消息存至 IndexedDB 中;接收方(例如所有页面)则通过轮询去获取最新的信息。在这之前,我们先简单封装几个 IndexedDB 的工具方法。

  • 打开数据库连接:
function openStore() {
    const storeName = 'ctc_aleinzhou';
    return new Promise(function (resolve, reject) {
        if (!('indexedDB' in window)) {
            return reject('don\'t support indexedDB');
        }
        const request = indexedDB.open('CTC_DB', 1);
        request.onerror = reject;
        request.onsuccess =  e => resolve(e.target.result);
        request.onupgradeneeded = function (e) {
            const db = e.srcElement.result;
            if (e.oldVersion === 0 && !db.objectStoreNames.contains(storeName)) {
                const store = db.createObjectStore(storeName, {keyPath: 'tag'});
                store.createIndex(storeName + 'Index', 'tag', {unique: false});
            }
        }
    });
}
  • 存储数据
function saveData(db, data) {
    return new Promise(function (resolve, reject) {
        const STORE_NAME = 'ctc_aleinzhou';
        const tx = db.transaction(STORE_NAME, 'readwrite');
        const store = tx.objectStore(STORE_NAME);
        const request = store.put({tag: 'ctc_data', data});
        request.onsuccess = () => resolve(db);
        request.onerror = reject;
    });
}
  • 查询/读取数据
function query(db) {
    const STORE_NAME = 'ctc_aleinzhou';
    return new Promise(function (resolve, reject) {
        try {
            const tx = db.transaction(STORE_NAME, 'readonly');
            const store = tx.objectStore(STORE_NAME);
            const dbRequest = store.get('ctc_data');
            dbRequest.onsuccess = e => resolve(e.target.result);
            dbRequest.onerror = reject;
        }
        catch (err) {
            reject(err);
        }
    });
}

剩下的工作就非常简单了。首先打开数据连接,并初始化数据:

openStore().then(db => saveData(db, null))

对于消息读取,可以在连接与初始化后轮询:

openStore().then(db => saveData(db, null)).then(function (db) {
    setInterval(function () {
        query(db).then(function (res) {
            if (!res || !res.data) {
                return;
            }
            const data = res.data;
            const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
            console.log('[Storage I] receive message:', text);
        });
    }, 1000);
});

最后,要发送消息时,只需向 IndexedDB 存储数据即可:

openStore().then(db => saveData(db, null)).then(function (db) {
    // …… 省略上面的轮询代码
    // 触发 saveData 的方法可以放在用户操作的事件监听内
    saveData(db, mydata);
});

小憩一下

在“广播模式”外,我们又了解了“共享存储+长轮询”这种模式。也许你会认为长轮询没有监听模式优雅,但实际上,有些时候使用“共享存储”的形式时,不一定要搭配长轮询。

例如,在多 Tab 场景下,我们可能会离开 Tab A 到另一个 Tab B 中操作;过了一会我们从 Tab B 切换回 Tab A 时,希望将之前在 Tab B 中的操作的信息同步回来。这时候,其实只用在 Tab A 中监听visibilitychange这样的事件,来做一次信息同步即可。

下面,我会再介绍一种通信方式,我把它称为“口口相传”模式。


6. window.open + window.opener

当我们使用window.open打开页面时,方法会返回一个被打开页面window的引用。而在未显示指定noopener时,被打开的页面可以通过window.opener获取到打开它的页面的引用 —— 通过这种方式我们就将这些页面建立起了联系(一种树形结构)。

首先,我们把window.open打开的页面的window对象收集起来:

let childWins = [];
document.getElementById('btn').addEventListener('click', function () {
    const win = window.open('./some/sample');
    childWins.push(win);
});

然后,当我们需要发送消息的时候,作为消息的发起方,一个页面需要同时通知它打开的页面与打开它的页面:

// 过滤掉已经关闭的窗口
childWins = childWins.filter(w => !w.closed);
if (childWins.length > 0) {
    mydata.fromOpenner = false;
    childWins.forEach(w => w.postMessage(mydata));
}
if (window.opener && !window.opener.closed) {
    mydata.fromOpenner = true;
    window.opener.postMessage(mydata);
}

注意,我这里先用.closed属性过滤掉已经被关闭的 Tab 窗口。这样,作为消息发送方的任务就完成了。下面看看,作为消息接收方,它需要做什么。

此时,一个收到消息的页面就不能那么自私了,除了展示收到的消息,它还需要将消息再传递给它所“知道的人”(打开与被它打开的页面):

需要注意的是,我这里通过判断消息来源,避免将消息回传给发送方,防止消息在两者间死循环的传递。(该方案会有些其他小问题,实际中可以进一步优化)
window.addEventListener('message', function (e) {
    const data = e.data;
    const text = '[receive] ' + data.msg + ' —— tab ' + data.from;
    console.log('[Cross-document Messaging] receive message:', text);
    // 避免消息回传
    if (window.opener && !window.opener.closed && data.fromOpenner) {
        window.opener.postMessage(data);
    }
    // 过滤掉已经关闭的窗口
    childWins = childWins.filter(w => !w.closed);
    // 避免消息回传
    if (childWins && !data.fromOpenner) {
        childWins.forEach(w => w.postMessage(data));
    }
});

这样,每个节点(页面)都肩负起了传递消息的责任,也就是我说的“口口相传”,而消息就在这个树状结构中流转了起来。

小憩一下

显然,“口口相传”的模式存在一个问题:如果页面不是通过在另一个页面内的window.open打开的(例如直接在地址栏输入,或从其他网站链接过来),这个联系就被打破了。

除了上面这六个常见方法,其实还有一种(第七种)做法是通过 WebSocket 这类的“服务器推”技术来进行同步。这好比将我们的“中央站”从前端移到了后端。

关于 WebSocket 与其他“服务器推”技术,不了解的同学可以阅读这篇《各类“服务器推”技术原理与实例(Polling/COMET/SSE/WebSocket)》

此外,我还针对以上各种方式写了一个 在线演示的 Demo >>

Demo页面

二、非同源页面之间的通信

上面我们介绍了七种前端跨页面通信的方法,但它们大都受到同源策略的限制。然而有时候,我们有两个不同域名的产品线,也希望它们下面的所有页面之间能无障碍地通信。那该怎么办呢?

要实现该功能,可以使用一个用户不可见的 iframe 作为“桥”。由于 iframe 与父页面间可以通过指定origin来忽略同源限制,因此可以在每个页面中嵌入一个 iframe (例如:http://sample.com/bridge.html),而这些 iframe 由于使用的是一个 url,因此属于同源页面,其通信方式可以复用上面第一部分提到的各种方式。

页面与 iframe 通信非常简单,首先需要在页面中监听 iframe 发来的消息,做相应的业务处理:

/* 业务页面代码 */
window.addEventListener('message', function (e) {
    // …… do something
});

然后,当页面要与其他的同源或非同源页面通信时,会先给 iframe 发送消息:

/* 业务页面代码 */
window.frames[0].window.postMessage(mydata, '*');

其中为了简便此处将postMessage的第二个参数设为了'*',你也可以设为 iframe 的 URL。iframe 收到消息后,会使用某种跨页面消息通信技术在所有 iframe 间同步消息,例如下面使用的 Broadcast Channel:

/* iframe 内代码 */
const bc = new BroadcastChannel('AlienZHOU');
// 收到来自页面的消息后,在 iframe 间进行广播
window.addEventListener('message', function (e) {
    bc.postMessage(e.data);
});    

其他 iframe 收到通知后,则会将该消息同步给所属的页面:

/* iframe 内代码 */
// 对于收到的(iframe)广播消息,通知给所属的业务页面
bc.onmessage = function (e) {
    window.parent.postMessage(e.data, '*');
};

下图就是使用 iframe 作为“桥”的非同源页面间通信模式图。

其中“同源跨域通信方案”可以使用文章第一部分提到的某种技术。


总结

今天和大家分享了一下跨页面通信的各种方式。

对于同源页面,常见的方式包括:

  • 广播模式:Broadcast Channe / Service Worker / LocalStorage + StorageEvent
  • 共享存储模式:Shared Worker / IndexedDB / cookie
  • 口口相传模式:window.open + window.opener
  • 基于服务端:Websocket / Comet / SSE 等

而对于非同源页面,则可以通过嵌入同源 iframe 作为“桥”,将非同源页面通信转换为同源页面通信。

本文在分享的同时,也是为了抛转引玉。如果你有什么其他想法,欢迎一起讨论,提出你的见解和想法~

对文章感兴趣的同学欢迎关注 我的博客 >> https://github.com/alienzhou/blog
查看原文

kedaya 评论了文章 · 2019-04-02

记2019前端面经

Motivation

2019寒冬来临,卷入动荡之中只能又开启了漫漫求职路。有辛酸,有坎坷,但也有点小幸运。

Experience

前前后后面试了6家公司,总结一下问题做一个backup。(仅记录问题,不区分面试轮次)

  • 脉脉

    • 框架

      • 简述vue的基本原理
      • vue的生命周期
      • vue与react的不同
      • vue父子通信的方式
      • vuex的原理及理解
      • vue v-model如何实现的,语法糖实际是什么
      • react 生命周期
      • react context 的理解
      • redux的原理
      • react-redux的原理
      • 如何避免render的触发
      • 说一下react vnode的diff算法
      • vnode的引入与直接操作原生dom相比,哪一个相率更高,为什么
    • 基础知识

      • 对缓存的理解
      • 对http2的理解
      • 对https的理解
      • 对原型链的理解,画一个经典的原型连接图
      • 对es6了解多少
      • 箭头函数与正常函数有哪些区别
      • class的实现 用原型写一个继承
      • ajax axios fetch的区别
      • 如何用promise封装一个ajax
      • 项目中的难点和亮点

二面送走,说会联系也没联系我 > <.(面试体验和福利还是挺好的)

  • 高德

    • 框架

      • React组件的生命周期
      • React父子组件如何通信的
      • React层级很深的组件如何通信传值(Context API)
      • React做了哪些性能优化 PureComponent的实现原理是什么
      • React setState后都会发生什么,是否了解
      • React 1000个列表节点渲染,给出一个优化方案
      • 是否了解React事件机制,如果让你实现如何来设计
      • Redux的原理及理解
      • react-redux是如何来实现的,connect是不是一个高阶函数,原理是什么
      • react与vue相比,有什么不同
    • 基础知识

      • 对缓存的理解,需要什么设置
      • 对代码构建上线流程是否了解,说一下如果让你实现的思路
      • 谈谈对webpack的理解,常用哪些plugin,对webpack配置是否了解,对项目打包是否做过什么优化
      • ES6常用到哪些,对class的理解,手写一个对继承的实现
      • Promise是否了解,如何实现一个promise
      • class继承中子类想使用父类的方法,应该用什么方式调用(super的意义)
      • 箭头函数与正常函数的区别
      • css实现border渐变
      • css实现下阴影(气泡类 带箭头的阴影)
      • css对flex的理解
      • 对浏览器渲染机制的理解(具体到细节,从渲染树到paint之间究竟发生了什么)
      • 纯css实现一个高宽比为1:3的盒子 列举几种方式
      • 浏览器的架构,bom,dom
  • 马蜂窝

    • 框架

      • vue的生命周期
      • 组件A下有子组件B、C,那么3个组件生命周期的调用顺序,同级组件mounted触发一定是先调用先call吗?同步还是异步?
      • vue的基本原理
      • vue eventbus的实现
      • vue父子组件的通信
      • vuex的使用
    • 基础知识

      • 对https的理解,对称、非对称加密在哪部使用
      • css布局 各种定位的方式
      • css实现水平垂直居中
      • css实现一个旋转的圆
      • cookie 跨域的处理方案
      • cookie 种在子域下能否携带发送到服务端(SSO登录)
      • 写一个函数,第一次调用返回0,之后每次调用返回比之前大1
      • 闭包、作用域的理解
      • 用原生xhr发送一个请求
      • 跨域请求可以携带cookie吗
      • axios与xhr的区别,如何用promise包装xhr
      • 讲讲项目中的难点

要吐槽一下。1面好看的小姐姐面完直接告诉我当备胎了,送我出去了= =。。。(不过马蜂窝的办公环境是真的挺美的,跟花园一样)

  • 猿辅导

    • 框架

      • vue的生命周期
      • vue双向绑定的原理
      • vue父子组件通信的方式
      • vue eventbus的原理
      • 对vuex的理解
      • 谈谈对vue和react对比,并从中能学到什么
      • vue中可以对对象进行数据监听,如果对于数组中的某个元素能否监听,是如何做到的
    • 基础知识

      • http的头部有什么字段,简要描述(缓存,content-type,cookie等等)
      • cookie跨域服务端需要如何适配(CORS头)
      • 一个请求跨域是否会抵达服务端
      • 对之前的项目做过什么优化,讲一讲
      • 对之前的项目遇到过什么难点,讲一讲
      • 对http2有哪些了解
      • 对canvas有哪些性能上的优化
      • 对settimeout和对setinterval的理解(涉及代码题倒计时函数,eventloop的考点)计时是否准确?如何实现较为准确的计时?
      • 对一个短时间并发高的场景需要如何处理(后端设计)(开始答题获取试卷的场景)
    • 写代码

      • 写一个倒计时函数
      • 写一个函数,给定一棵树,输出这棵树的深度
      • 写一个函数,给定一个url和最大深度maxdeep,输出抓取当前url及其子链接深度范围内的所有图片
      • 写一个函数,给定nodes=[],每一个节点拥有id,name,parentid,输出一个属性列表的展示(涉及dom操作)
  • 抖音

    • 框架

      • vue数据绑定的实现原理
      • vue computed具体在什么阶段进行的依赖收集,具体的过程详细描述
      • vuex和redux的差别
    • 基础知识

      • 跨域的解决办法
      • 原型链的理解,写一个原型继承
      • 实现一个sendRequest,有最大请求并发限制
      • EventLoop的理解
      • 浏览器渲染触发在EventLoop的哪个阶段,触发机制是怎么样的
      • https 建立连接的过程及通信 如何劫持,鉴别是否被劫持
      • ES module与cjs的区别
      • Tree shaking的实现原理
      • 给定一个sum 检验一棵树中,是否存在一条路径和为sum,输出该路径

二面送走,感觉到自己在一些问题的深度上还有待加深。

  • 快手

    • 框架

      • 对vuex源码上如何实现只能mutation更改,而不能直接更改
      • vuex中如何在层层都可以引用$store
      • vuex和redux的差别
    • 基础知识

      • 笔试题6页
      • css优先级关系
      • eventloop的先后顺序(node内)
      • node中的垃圾收集机制
      • BFC,IFC,FFC的区别
      • a11y是什么,如何理解
      • prototype的考察
      • TDZ的考察
      • 写一个数组方法,打乱整个数组顺序,并且每个数字落在各个位置的概率相同
      • one(add(two())) // 3 two(add(one())) // 3 写出 one() two() add()的实现
      • 实现一个catchPromise 发同一个请求缓存data 在实际网络层相同url只会发出一个请求
      • 给定 n 个 {x, y, w, h}的盒子 按需排列,左上聚拢(层叠后的max(h)*max(w)最小),求给一个{w,h},输出放置的位置
      • 从输入一个url到呈现网页,都有哪些步骤
      • http keep—alive都解决了哪些问题 keep-alive是从c - nginx建立的还是直接到服务建立的长连接,与websocket有什么区别与联系
      • 给定一个html,输出其中包含的html标签数量,可以用domapi 注意iframe
      • 实现一个NumberStack,实现pop,push,max(n)方法,max(n)返回第n大的数,max(n)需要 O(1)的时间复杂度
      • 实现一个bind函数
      • 跨域的解决办法,jsonp的实现原理
注:一些各个公司问的相同的问题就没有再重复写出来

Result

祸兮福所倚,福兮祸所伏。虽然经历了寒冬和求职的奔波,但也因为面试才对自己有了一个更加清楚地认识,知道自己未来的路更应该如何去走。这半年来因为种种原因对技术上有些许的松懈和怠慢,所幸还能拿到了自己很满意的offer,未来的路还要更加努力的走~

查看原文

kedaya 关注了用户 · 2019-04-02

pandaGao @pandagao

念念不忘 必有回响 凭一口气 点一盏灯

关注 3

kedaya 发布了文章 · 2019-04-02

记2019前端面经

Motivation

2019寒冬来临,卷入动荡之中只能又开启了漫漫求职路。有辛酸,有坎坷,但也有点小幸运。

Experience

前前后后面试了6家公司,总结一下问题做一个backup。(仅记录问题,不区分面试轮次)

  • 脉脉

    • 框架

      • 简述vue的基本原理
      • vue的生命周期
      • vue与react的不同
      • vue父子通信的方式
      • vuex的原理及理解
      • vue v-model如何实现的,语法糖实际是什么
      • react 生命周期
      • react context 的理解
      • redux的原理
      • react-redux的原理
      • 如何避免render的触发
      • 说一下react vnode的diff算法
      • vnode的引入与直接操作原生dom相比,哪一个相率更高,为什么
    • 基础知识

      • 对缓存的理解
      • 对http2的理解
      • 对https的理解
      • 对原型链的理解,画一个经典的原型连接图
      • 对es6了解多少
      • 箭头函数与正常函数有哪些区别
      • class的实现 用原型写一个继承
      • ajax axios fetch的区别
      • 如何用promise封装一个ajax
      • 项目中的难点和亮点

二面送走,说会联系也没联系我 > <.(面试体验和福利还是挺好的)

  • 高德

    • 框架

      • React组件的生命周期
      • React父子组件如何通信的
      • React层级很深的组件如何通信传值(Context API)
      • React做了哪些性能优化 PureComponent的实现原理是什么
      • React setState后都会发生什么,是否了解
      • React 1000个列表节点渲染,给出一个优化方案
      • 是否了解React事件机制,如果让你实现如何来设计
      • Redux的原理及理解
      • react-redux是如何来实现的,connect是不是一个高阶函数,原理是什么
      • react与vue相比,有什么不同
    • 基础知识

      • 对缓存的理解,需要什么设置
      • 对代码构建上线流程是否了解,说一下如果让你实现的思路
      • 谈谈对webpack的理解,常用哪些plugin,对webpack配置是否了解,对项目打包是否做过什么优化
      • ES6常用到哪些,对class的理解,手写一个对继承的实现
      • Promise是否了解,如何实现一个promise
      • class继承中子类想使用父类的方法,应该用什么方式调用(super的意义)
      • 箭头函数与正常函数的区别
      • css实现border渐变
      • css实现下阴影(气泡类 带箭头的阴影)
      • css对flex的理解
      • 对浏览器渲染机制的理解(具体到细节,从渲染树到paint之间究竟发生了什么)
      • 纯css实现一个高宽比为1:3的盒子 列举几种方式
      • 浏览器的架构,bom,dom
  • 马蜂窝

    • 框架

      • vue的生命周期
      • 组件A下有子组件B、C,那么3个组件生命周期的调用顺序,同级组件mounted触发一定是先调用先call吗?同步还是异步?
      • vue的基本原理
      • vue eventbus的实现
      • vue父子组件的通信
      • vuex的使用
    • 基础知识

      • 对https的理解,对称、非对称加密在哪部使用
      • css布局 各种定位的方式
      • css实现水平垂直居中
      • css实现一个旋转的圆
      • cookie 跨域的处理方案
      • cookie 种在子域下能否携带发送到服务端(SSO登录)
      • 写一个函数,第一次调用返回0,之后每次调用返回比之前大1
      • 闭包、作用域的理解
      • 用原生xhr发送一个请求
      • 跨域请求可以携带cookie吗
      • axios与xhr的区别,如何用promise包装xhr
      • 讲讲项目中的难点

要吐槽一下。1面好看的小姐姐面完直接告诉我当备胎了,送我出去了= =。。。(不过马蜂窝的办公环境是真的挺美的,跟花园一样)

  • 猿辅导

    • 框架

      • vue的生命周期
      • vue双向绑定的原理
      • vue父子组件通信的方式
      • vue eventbus的原理
      • 对vuex的理解
      • 谈谈对vue和react对比,并从中能学到什么
      • vue中可以对对象进行数据监听,如果对于数组中的某个元素能否监听,是如何做到的
    • 基础知识

      • http的头部有什么字段,简要描述(缓存,content-type,cookie等等)
      • cookie跨域服务端需要如何适配(CORS头)
      • 一个请求跨域是否会抵达服务端
      • 对之前的项目做过什么优化,讲一讲
      • 对之前的项目遇到过什么难点,讲一讲
      • 对http2有哪些了解
      • 对canvas有哪些性能上的优化
      • 对settimeout和对setinterval的理解(涉及代码题倒计时函数,eventloop的考点)计时是否准确?如何实现较为准确的计时?
      • 对一个短时间并发高的场景需要如何处理(后端设计)(开始答题获取试卷的场景)
    • 写代码

      • 写一个倒计时函数
      • 写一个函数,给定一棵树,输出这棵树的深度
      • 写一个函数,给定一个url和最大深度maxdeep,输出抓取当前url及其子链接深度范围内的所有图片
      • 写一个函数,给定nodes=[],每一个节点拥有id,name,parentid,输出一个属性列表的展示(涉及dom操作)
  • 抖音

    • 框架

      • vue数据绑定的实现原理
      • vue computed具体在什么阶段进行的依赖收集,具体的过程详细描述
      • vuex和redux的差别
    • 基础知识

      • 跨域的解决办法
      • 原型链的理解,写一个原型继承
      • 实现一个sendRequest,有最大请求并发限制
      • EventLoop的理解
      • 浏览器渲染触发在EventLoop的哪个阶段,触发机制是怎么样的
      • https 建立连接的过程及通信 如何劫持,鉴别是否被劫持
      • ES module与cjs的区别
      • Tree shaking的实现原理
      • 给定一个sum 检验一棵树中,是否存在一条路径和为sum,输出该路径

二面送走,感觉到自己在一些问题的深度上还有待加深。

  • 快手

    • 框架

      • 对vuex源码上如何实现只能mutation更改,而不能直接更改
      • vuex中如何在层层都可以引用$store
      • vuex和redux的差别
    • 基础知识

      • 笔试题6页
      • css优先级关系
      • eventloop的先后顺序(node内)
      • node中的垃圾收集机制
      • BFC,IFC,FFC的区别
      • a11y是什么,如何理解
      • prototype的考察
      • TDZ的考察
      • 写一个数组方法,打乱整个数组顺序,并且每个数字落在各个位置的概率相同
      • one(add(two())) // 3 two(add(one())) // 3 写出 one() two() add()的实现
      • 实现一个catchPromise 发同一个请求缓存data 在实际网络层相同url只会发出一个请求
      • 给定 n 个 {x, y, w, h}的盒子 按需排列,左上聚拢(层叠后的max(h)*max(w)最小),求给一个{w,h},输出放置的位置
      • 从输入一个url到呈现网页,都有哪些步骤
      • http keep—alive都解决了哪些问题 keep-alive是从c - nginx建立的还是直接到服务建立的长连接,与websocket有什么区别与联系
      • 给定一个html,输出其中包含的html标签数量,可以用domapi 注意iframe
      • 实现一个NumberStack,实现pop,push,max(n)方法,max(n)返回第n大的数,max(n)需要 O(1)的时间复杂度
      • 实现一个bind函数
      • 跨域的解决办法,jsonp的实现原理
注:一些各个公司问的相同的问题就没有再重复写出来

Result

祸兮福所倚,福兮祸所伏。虽然经历了寒冬和求职的奔波,但也因为面试才对自己有了一个更加清楚地认识,知道自己未来的路更应该如何去走。这半年来因为种种原因对技术上有些许的松懈和怠慢,所幸还能拿到了自己很满意的offer,未来的路还要更加努力的走~

查看原文

赞 116 收藏 87 评论 12

kedaya 评论了文章 · 2018-10-14

rem, vw, 还是...? 各凭本事的移动端适配方案

前言

2018年最后的法定假期都已经结束了,我相信大部分正在进行或曾经进行过移动端页面开发的同学都或多或少的了解过使用rem进行移动端页面适配的方案以及使用vw的方案,(没了解过的同学可以参见大漠老师的这两篇文章 使用Flexible实现手淘H5页面的终端适配再聊移动端页面的适配)也面临过在不同适配方案间进行抉择的思考,我个人最近对于移动端适配方案也进行了一轮重新的研究,期间,对各种适配方案也有一些自己的见解,正好记录下来与大家一起分享。

vw与rem方案中的一些思考

所以,移动端适配 = vw or rem ?

当然不是。并不是所有场景下的移动端页面都适合采用vw或rem的方案,这类方案的本质是布局等比例的缩放,让页面在不同尺寸的屏幕下有类似于矢量图片缩放的效果,即保证页面各元素之间位置尺寸的比例关系,并让元素可以清晰地展现。
这样的方案更适合于视觉组件种类较多,视觉设计对元素位置的相对关系依赖较强的移动端页面。其实大部分页面都可以使用rem或vw的方案进行适配,但对于文本内容较多,或者说希望引导用户沉浸浏览的页面,我个人并不推荐使用等比缩放的方案,至少并不推荐完全使用等比缩放的方案,对于文本内容还是应该直接使用px这种绝对长度单位,毕竟在大屏手机上用户希望看到的是更多的内容而不是更大的内容。实际上很多这类的网站也确实是直接使用px结合flex等布局方式进行移动端适配的,这个在后面讨论具体技术方案的时候我会举几个例子。

rem方案到底在做什么?

在各种rem适配方案的实现中,有两个核心的点

  • 设置<meta name="viewport" content="xxx">(可以根据dpr缩放viewport,也可以直接使用1倍的视口大小)
  • 根据当前布局的宽度(通常是viewport宽度, 也可以是被限制的最大/最小布局宽度)来设置html元素的font-size

之前我已经提到过,vw和rem的方案的本质都是“等比例缩放”,因为vw和rem都是相对长度单位(relative length unit),可以很好的满足这个需求。区别是vw是viewport width的1%,而rem是html元素的font-size。当我们让html元素的font-size与viewport width产生了关联后,rem就可以模拟出使用vw进行布局的效果了。所以在rem方案中,我们只是在把rem当做是vw的影子。写作rem,读作vw...(剧情似乎狗血起来了... rem: 当然是选择原谅你们啊)
我rem有话说

那直接用vw不就完事儿了吗?

且慢

且慢,当初之所以使用rem的方案流行开来正是因为在那时viewport units的浏览器支持程度不甚理想(IOS 8+, Android 4.4+ 参见viewport units的caniuse)。而相比较之下rem就好多了(IOS 4.1+, Android 2.1+ 参见caniuse),所以对于vw,在当时的大环境下前端想说爱你不容易。

我想这时候有人要说了:“醒醒兄弟 已经8102年了!”
是的,8102年都快要过去了,对于兼容性要求不是特别高的情况下vw也算是可以见天日了,并且也有一些针对vw的补丁方案,但还有一个问题我们要稍微讨论一下...

vw可以完全替代rem吗?

回答依旧是否定的。单纯使用vw进行布局不能限制布局的宽度,对于有这个需求的场景至少还是需要将vw和rem混用来处理边界情况。下文也会更详细地提到这种方案,这里先按下不表。

现有生产环境中移动端适配方案的一点总结

当我们在苦苦地寻找适合自己的道路时,不妨先抬头看看别人是怎么做的。那么现实世界里各家互联网公司的移动端页面都采用了什么样的适配方案呢?有没有一些比较有特色的绝活儿呢?以下我按照页面实际使用的css长度单位作为划分标准,为大家举一些栗子。

px方案

就像开篇提到的,并不是说移动端就一定要使用相对长度单位,传统的响应式布局依然是很好的选择,尤其在新闻,社区等可阅读内容较多的场景直接使用px单位可以营造更好地体验。px方案可以让大屏幕手机展示出更多的内容,更符合人们的阅读习惯。采用这种方案的网站有:

  • 腾讯

    移动端首页主要是新闻内容,需要更好的阅读体验,适合直接使用px进行布局。

  • 知乎

    知乎也是比较典型的追求阅读体验的场景,不出意外的也是直接使用px。

  • 点评

    视觉元素较丰富,依旧采用了px方案,页面基本是flex布局,适配效果很好

  • 头条

    头条的这个方案有点特色,依然会设置html元素的font-size也会加上data-dpr属性并且会对viewport进行scale, 但是最终css的输出还是px,并没有使用rem,并且会对不同dpr下的样式单独定义,如下图所示:

头条的适配方案
这样可以解决1px border的问题,文字大小也不会随屏幕尺寸变动(毕竟文本内容较多),虽然我暂时没找到使用到rem的地方,但确实可以在需要的时候对特殊元素做rem方案的布局,不过这种方案应该会造成css文件大小倍增,而且输出这样的css肯定也少不了构建流程插件的支持,算是一种特定的解决方案吧。

看到这里你以为最终输出px就不能做类似于rem/vw的弹性布局了吗,下面就给大家看一手绝活儿...

  • 淘宝

    什么?给我们看了半天文章结果用的是px?
    其实聪明的你一定很快就会发现在效果上淘宝移动端的适配方案和rem/vw的方案其实是差不多的,元素的样式都是通过js生成的,虽然单位确实是px,但是方案依旧是以375px宽度的尺寸为基准进行缩放的。原理上应该是一种css in js的方案,只不过把rem方案中设置html元素font-size的过程内化到使用js计算元素style的过程中去了。这样的方案涉及到整体的开发框架上的统一与支持,并不算是一个特别通用的方案。好处可能是直接使用px单位结果更为精确。可以说是一手绝活儿了。当然淘宝旗下还是有非常多的产品线的,也未必是同样的适配方案(比如大漠老师文中的例子),这里只针对这个移动端首页来说。

rem方案

rem方案可以说是比较成熟了,出镜率也较高,也就不再赘述了,总的来说rem方案主要分为两种,一种是缩放viewport的方案,如当年的lib-flexible,可以对1px border等细节问题较方便的处理,但会影响到media query。另一种就是不缩放viewport,对1px boder等问题单独引入处理方案。然后对于基准尺寸下的html元素fong-size也有很多不同的定义方式,这个说起来没什么标准可言,我就随便举几个例子说明吧:

不缩放viewport

(以下说明的rem与px的对应关系都是在屏幕宽度为375px的情况下)

缩放viewport

(以下说明的rem与px的对应关系都是在屏幕宽度为375px, viewport scale 0.5的情况下)

vw方案

来了,终于来了!前面说了这么多关于vw的问题,到底有没有现有的产品在大规模的使用vw的方案呢?兼容方案又是怎么做的呢?

  • 京东

    京东的移动端首页采用了vw+rem的布局方式,元素布局上依然使用rem单位,没有缩放viewport, html元素的font-size则使用vw + px fallback的形式,并且使用media query来限制布局宽度,如下图所示

    正常情况下:

京东适配方案 正常情况下

边界情况下

京东适配方案 边界情况下

  • 网易

    网易的方案和京东基本相同,没有缩放viewport,使用media query,只处理html元素的font-size,并限制布局宽度。

  • 饿了么

    饿了么也是采用的vw+rem的方案,不过对viewport进行了缩放,也没有限制布局宽度,html元素的font-size依然由px指定,但是具体元素的布局上使用vw + px fallbak的形式,如下图所示:

饿了么适配方案

可以看到,使用上述两种vw+rem的方案对现有的rem方案的改动都不会很大,都采用了vw + fallback的方式,兼容性问题得到了保证,只是饿了么的方案可能更需要构建过程中的插件支持(关于这个,后面我给你们解释解释什么叫惊喜)。这样来看,对于大漠老师提出的vw方案中使用viewport-units-buggyfill库进行兼容的做法,我个人就并不是很推荐了,因为该库使用了css content属性进行兼容处理,官方文档中就指出了对部分浏览器的img标签有影响 ,需要全局引入一条css规则。且对于需要正常使用content的情况(如:图标字体)也会引起不可避免的冲突,另外也不支持伪元素的兼容。所以从我个人的角度来说,如果你一定要问我使用怎样的vw适配方案,我会推荐给你上述两种vw + rem的方案。

这就是全部了吗?

当然不是,我只是列举了几个比较典型的移动端适配方案,其实在具体实现的细节上可以自行把握的点还是很多的,适合的终归才是最好的,那颗银弹或许不会出现,但我们的手中也从未缺少过利器。

彩(an)蛋(li)部分

相信大多数同学也是有想法在实际开发中把vw融入到现有的移动端适配方案中的。如我上述提到的两种vw+rem方案,只修改html元素font-size的方案对于已经在使用rem方案的同学来说改动的成本并不大,只需要在原本的media query 里(或js生成style时)在font-size: *px后面加上font-size: *vw就可以了,如需限制布局宽度则需多加一点判断。

而对于饿了么那种在使用到长度单位时同时输出rem+vw的方式,肯定是要通过一点额外的插件来做了。如果你和我一样刚好在使用Stylus作为css预处理器,那我专门写了一个Stylus的插件用来帮你处理这个问题。
这个插件可以让你在开发流程使用px书写css, 和现有的部分插件不同的是,它同时支持多种适配方案的输出,目前支持rem,纯vw方案以及刚才提到的vw+rem方案的输出。并且对不希望转换px的场景做了很方便的处理。也就是说,如果你现在使用rem方案,可以直接替换使用该插件,当你需要切换到vw或vw+rem方案时基本可以做到无缝切换。

具体的使用方式和示例请参见pandaGao/stylus-px-to-relative-unit

懂我的意思吧

查看原文

kedaya 赞了文章 · 2018-10-14

rem, vw, 还是...? 各凭本事的移动端适配方案

前言

2018年最后的法定假期都已经结束了,我相信大部分正在进行或曾经进行过移动端页面开发的同学都或多或少的了解过使用rem进行移动端页面适配的方案以及使用vw的方案,(没了解过的同学可以参见大漠老师的这两篇文章 使用Flexible实现手淘H5页面的终端适配再聊移动端页面的适配)也面临过在不同适配方案间进行抉择的思考,我个人最近对于移动端适配方案也进行了一轮重新的研究,期间,对各种适配方案也有一些自己的见解,正好记录下来与大家一起分享。

vw与rem方案中的一些思考

所以,移动端适配 = vw or rem ?

当然不是。并不是所有场景下的移动端页面都适合采用vw或rem的方案,这类方案的本质是布局等比例的缩放,让页面在不同尺寸的屏幕下有类似于矢量图片缩放的效果,即保证页面各元素之间位置尺寸的比例关系,并让元素可以清晰地展现。
这样的方案更适合于视觉组件种类较多,视觉设计对元素位置的相对关系依赖较强的移动端页面。其实大部分页面都可以使用rem或vw的方案进行适配,但对于文本内容较多,或者说希望引导用户沉浸浏览的页面,我个人并不推荐使用等比缩放的方案,至少并不推荐完全使用等比缩放的方案,对于文本内容还是应该直接使用px这种绝对长度单位,毕竟在大屏手机上用户希望看到的是更多的内容而不是更大的内容。实际上很多这类的网站也确实是直接使用px结合flex等布局方式进行移动端适配的,这个在后面讨论具体技术方案的时候我会举几个例子。

rem方案到底在做什么?

在各种rem适配方案的实现中,有两个核心的点

  • 设置<meta name="viewport" content="xxx">(可以根据dpr缩放viewport,也可以直接使用1倍的视口大小)
  • 根据当前布局的宽度(通常是viewport宽度, 也可以是被限制的最大/最小布局宽度)来设置html元素的font-size

之前我已经提到过,vw和rem的方案的本质都是“等比例缩放”,因为vw和rem都是相对长度单位(relative length unit),可以很好的满足这个需求。区别是vw是viewport width的1%,而rem是html元素的font-size。当我们让html元素的font-size与viewport width产生了关联后,rem就可以模拟出使用vw进行布局的效果了。所以在rem方案中,我们只是在把rem当做是vw的影子。写作rem,读作vw...(剧情似乎狗血起来了... rem: 当然是选择原谅你们啊)
我rem有话说

那直接用vw不就完事儿了吗?

且慢

且慢,当初之所以使用rem的方案流行开来正是因为在那时viewport units的浏览器支持程度不甚理想(IOS 8+, Android 4.4+ 参见viewport units的caniuse)。而相比较之下rem就好多了(IOS 4.1+, Android 2.1+ 参见caniuse),所以对于vw,在当时的大环境下前端想说爱你不容易。

我想这时候有人要说了:“醒醒兄弟 已经8102年了!”
是的,8102年都快要过去了,对于兼容性要求不是特别高的情况下vw也算是可以见天日了,并且也有一些针对vw的补丁方案,但还有一个问题我们要稍微讨论一下...

vw可以完全替代rem吗?

回答依旧是否定的。单纯使用vw进行布局不能限制布局的宽度,对于有这个需求的场景至少还是需要将vw和rem混用来处理边界情况。下文也会更详细地提到这种方案,这里先按下不表。

现有生产环境中移动端适配方案的一点总结

当我们在苦苦地寻找适合自己的道路时,不妨先抬头看看别人是怎么做的。那么现实世界里各家互联网公司的移动端页面都采用了什么样的适配方案呢?有没有一些比较有特色的绝活儿呢?以下我按照页面实际使用的css长度单位作为划分标准,为大家举一些栗子。

px方案

就像开篇提到的,并不是说移动端就一定要使用相对长度单位,传统的响应式布局依然是很好的选择,尤其在新闻,社区等可阅读内容较多的场景直接使用px单位可以营造更好地体验。px方案可以让大屏幕手机展示出更多的内容,更符合人们的阅读习惯。采用这种方案的网站有:

  • 腾讯

    移动端首页主要是新闻内容,需要更好的阅读体验,适合直接使用px进行布局。

  • 知乎

    知乎也是比较典型的追求阅读体验的场景,不出意外的也是直接使用px。

  • 点评

    视觉元素较丰富,依旧采用了px方案,页面基本是flex布局,适配效果很好

  • 头条

    头条的这个方案有点特色,依然会设置html元素的font-size也会加上data-dpr属性并且会对viewport进行scale, 但是最终css的输出还是px,并没有使用rem,并且会对不同dpr下的样式单独定义,如下图所示:

头条的适配方案
这样可以解决1px border的问题,文字大小也不会随屏幕尺寸变动(毕竟文本内容较多),虽然我暂时没找到使用到rem的地方,但确实可以在需要的时候对特殊元素做rem方案的布局,不过这种方案应该会造成css文件大小倍增,而且输出这样的css肯定也少不了构建流程插件的支持,算是一种特定的解决方案吧。

看到这里你以为最终输出px就不能做类似于rem/vw的弹性布局了吗,下面就给大家看一手绝活儿...

  • 淘宝

    什么?给我们看了半天文章结果用的是px?
    其实聪明的你一定很快就会发现在效果上淘宝移动端的适配方案和rem/vw的方案其实是差不多的,元素的样式都是通过js生成的,虽然单位确实是px,但是方案依旧是以375px宽度的尺寸为基准进行缩放的。原理上应该是一种css in js的方案,只不过把rem方案中设置html元素font-size的过程内化到使用js计算元素style的过程中去了。这样的方案涉及到整体的开发框架上的统一与支持,并不算是一个特别通用的方案。好处可能是直接使用px单位结果更为精确。可以说是一手绝活儿了。当然淘宝旗下还是有非常多的产品线的,也未必是同样的适配方案(比如大漠老师文中的例子),这里只针对这个移动端首页来说。

rem方案

rem方案可以说是比较成熟了,出镜率也较高,也就不再赘述了,总的来说rem方案主要分为两种,一种是缩放viewport的方案,如当年的lib-flexible,可以对1px border等细节问题较方便的处理,但会影响到media query。另一种就是不缩放viewport,对1px boder等问题单独引入处理方案。然后对于基准尺寸下的html元素fong-size也有很多不同的定义方式,这个说起来没什么标准可言,我就随便举几个例子说明吧:

不缩放viewport

(以下说明的rem与px的对应关系都是在屏幕宽度为375px的情况下)

缩放viewport

(以下说明的rem与px的对应关系都是在屏幕宽度为375px, viewport scale 0.5的情况下)

vw方案

来了,终于来了!前面说了这么多关于vw的问题,到底有没有现有的产品在大规模的使用vw的方案呢?兼容方案又是怎么做的呢?

  • 京东

    京东的移动端首页采用了vw+rem的布局方式,元素布局上依然使用rem单位,没有缩放viewport, html元素的font-size则使用vw + px fallback的形式,并且使用media query来限制布局宽度,如下图所示

    正常情况下:

京东适配方案 正常情况下

边界情况下

京东适配方案 边界情况下

  • 网易

    网易的方案和京东基本相同,没有缩放viewport,使用media query,只处理html元素的font-size,并限制布局宽度。

  • 饿了么

    饿了么也是采用的vw+rem的方案,不过对viewport进行了缩放,也没有限制布局宽度,html元素的font-size依然由px指定,但是具体元素的布局上使用vw + px fallbak的形式,如下图所示:

饿了么适配方案

可以看到,使用上述两种vw+rem的方案对现有的rem方案的改动都不会很大,都采用了vw + fallback的方式,兼容性问题得到了保证,只是饿了么的方案可能更需要构建过程中的插件支持(关于这个,后面我给你们解释解释什么叫惊喜)。这样来看,对于大漠老师提出的vw方案中使用viewport-units-buggyfill库进行兼容的做法,我个人就并不是很推荐了,因为该库使用了css content属性进行兼容处理,官方文档中就指出了对部分浏览器的img标签有影响 ,需要全局引入一条css规则。且对于需要正常使用content的情况(如:图标字体)也会引起不可避免的冲突,另外也不支持伪元素的兼容。所以从我个人的角度来说,如果你一定要问我使用怎样的vw适配方案,我会推荐给你上述两种vw + rem的方案。

这就是全部了吗?

当然不是,我只是列举了几个比较典型的移动端适配方案,其实在具体实现的细节上可以自行把握的点还是很多的,适合的终归才是最好的,那颗银弹或许不会出现,但我们的手中也从未缺少过利器。

彩(an)蛋(li)部分

相信大多数同学也是有想法在实际开发中把vw融入到现有的移动端适配方案中的。如我上述提到的两种vw+rem方案,只修改html元素font-size的方案对于已经在使用rem方案的同学来说改动的成本并不大,只需要在原本的media query 里(或js生成style时)在font-size: *px后面加上font-size: *vw就可以了,如需限制布局宽度则需多加一点判断。

而对于饿了么那种在使用到长度单位时同时输出rem+vw的方式,肯定是要通过一点额外的插件来做了。如果你和我一样刚好在使用Stylus作为css预处理器,那我专门写了一个Stylus的插件用来帮你处理这个问题。
这个插件可以让你在开发流程使用px书写css, 和现有的部分插件不同的是,它同时支持多种适配方案的输出,目前支持rem,纯vw方案以及刚才提到的vw+rem方案的输出。并且对不希望转换px的场景做了很方便的处理。也就是说,如果你现在使用rem方案,可以直接替换使用该插件,当你需要切换到vw或vw+rem方案时基本可以做到无缝切换。

具体的使用方式和示例请参见pandaGao/stylus-px-to-relative-unit

懂我的意思吧

查看原文

赞 40 收藏 25 评论 4

kedaya 评论了文章 · 2018-07-15

浅谈vue中style的scoped属性(修改特定Element组件样式的方法)

在单页.vue文件中,为了保证各组件间的css样式不冲突,很可能会使用到局部css,也就是给<style>标签加上一个scoped属性(当然也可以用各种命名规则来规避这个问题)。

一开始用的时候感觉很神奇,于是看代码查资料了解了一下原理。
所谓的局部css,就是通过vue-loader这个插件,在编译打包的时候将带有scoped属性的css打上一个tag,同时将template内的所有html都打上一个相同的tag,最后通过css的属性选择器定位,造就了所谓的局部css。

原理都懂了,再有兴趣深究可以看一下vue-loader的源码。

下面说一个自己在开发中遇到的问题

最近项目中也在使用element-ui进行开发,有时候需要对引入的组件进行一些样式的重写调整,但仅仅是针对某一个页面而不是整体修改,在当前组件中用局部属性导致了不能重写element-ui的样式的问题。

下面是简单的例子,想修改.el-table th的样式

<style lang="css" scoped>
    .el-table th {
        background-color: #eee;
    }
</style>

但是发现并不能正常的修改样式,于是通过chrome查看元素一探究竟
发现html中只有子组件的根元素被打了tag标签而编译后的css中是最后一个属性被加了tag标签,所以自然就定位不到元素修改他的样式了

<div data-v-15ed03ea class="el-table el-table--fit el-table--enable-row-hover el-table--enable-row-transition" style="width: 100%;">
    <th>...<th>
</div>

<style type="text/css">
.el-table th[data-v-15ed03ea] {
    background-color: #eee;
}
</style>

既然问题的原理已经清楚了,那么接下来开始寻找解决方案,我们的目的只需要将tag打给我们要修改子组件样式的根元素就可以了

https://vue-loader.vuejs.org/en/features/scoped-css.html3 vue-loader的官方文档中也说了这个问题 所以使用 >>> 符号可以做到,但是注意vue-loader的版本要高于12.2.0 这个功能是这个版本后才具有的
https://github.com/vuejs/vue-loader/releases/tag/v12.2.0

最后上一下测试的demo

<template>
  <div>
      <el-table
      :data="tableData"
      style="width: 100%">
      <el-table-column
          prop="date"
          label="日期"
          width="180">
      </el-table-column>
      <el-table-column
          prop="name"
          label="姓名"
          width="180">
      </el-table-column>
      <el-table-column
          prop="address"
          label="地址">
      </el-table-column>
      </el-table>
      <div>
          <span>123</span>
          <p>321</p>
          <p>321</p>
      </div>

      </div>

    </template>
  <script>
  export default {
      data() {
      return {
          tableData: [{
          date: '2016-05-02',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1518 弄'
          }, {
          date: '2016-05-04',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1517 弄'
          }, {
          date: '2016-05-01',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1519 弄'
          }, {
          date: '2016-05-03',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1516 弄'
          }]
      }
      }
  }
  </script>
  <style lang="css" scoped>
      .el-table >>> th {
          background-color: #eee;
      }
  </style>
查看原文

认证与成就

  • 获得 233 次点赞
  • 获得 10 枚徽章 获得 1 枚金徽章, 获得 2 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-11-09
个人主页被 1.3k 人浏览