前端知识整理2

1.ES6 语法

1.ES6 模块化如何使用,开发环境如何打包?
语法: import export (注意有无 default 的区别)
环境: babel 编译 ES6 语法,模块化可用 webpack 和 rollup
扩展: 说一下自己对模块化标准统一的期待
rollup 功能单一(只做模块化的打包编译),webpack 功能强大

2.Class 和普通构造函数有何区别?
(1)Class 在语法上更加贴合面向对象的写法
(2)Class 实现继承更加易读、易理解
(3)更易于写 Java 等后端语言的使用
(4)本质还是语法糖,使用 prototype

class MathHandle {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    add() {
        return this.x + this.y;
    }
}
const m = new MathHandle(1, 2);
console.log(m.add()); // 3
console.log(typeof MathHandle); // 'function'
console.log(MathHandle.prototype.constructor === MathHandle); // true
console.log(m.__proto__ === MathHandle.prototype); // true

2.原型的实际应用、原型扩展

1.说一个原型的实际应用?
(1)描述一下 jquery 如何使用原型
(2)描述一下 zepto 如何使用原型
(3)再结合自己的项目经验,说一个自己开发的例子

2.原型如何体现它的扩展性?
(1)说一下 jquery 和 zepto 的插件机制
(2)结合自己的开发经验,做过的基于原型的插件

my-jquery.js

(function(window) {
    let jQuery = function (selector) {
        return new jQuery.fn.init(selector);
    };

    jQuery.fn = {
        css: function (key, value) {
            console.log('css'); // 这里只是简单打印
        },
        html : function (value) {
            return 'html';
        }
    };

    let init = jQuery.fn.init = function (selector) {
        let slice = Array.prototype.slice;
        let dom = slice.call(document.querySelectorAll(selector));
        let len = dom ? dom.length : 0;
        for (let i = 0; i < len; i++) {
            this[i] = dom[i];
        }
        this.length = len;
        this.selector = selector;
    };

    init.prototype = jQuery.fn;
    window.$ = jQuery;
})(window);

使用示例:



3.异步

1.什么是单线程,和异步有什么关系?
(1)单线程 - 只有一个线程,同一时间只能做一件事情,两段JS不能同时执行
(2)原因 - 避免DOM渲染的冲突:
浏览器需要渲染DOM
JS可以修改DOM结构
JS执行的时候,浏览器DOM渲染会暂停
两段JS也不能同时执行(都修改DOM就冲突了)
webworker 支持多线程,但是不能访问DOM
(3)解决方案 - 异步
问题1:没按照书写方式执行,可读性差
问题2:callback中不容易模块化

2.什么是event-loop?
(1)事件轮询,JS实现异步的具体解决方案
(2)同步代码,直接执行
(3)异步函数先放在 异步队列 中
setTimeout 没有延时,立刻放入异步队列
setTimeout 有延时,延时过后放入异步队列
ajax,待ajax加载完成后放入异步队列
(4)待同步函数执行完毕,轮询执行 异步队列 的函数

// ajax请求请求成功之后被放入异步队列
$.ajax({
    url: './data.json',
    success: function (res) {
        console.log(res);
        console.log('a');
    }
});
// 1000ms 之后被放入异步队列
setTimeout(function () {
    console.log('b');
}, 1000);
// 立刻被放入异步队列
setTimeout(function () {
    console.log('c');
});
// 主进程
console.log('d');

3.是否用过jquery的Deferred?
(1)jQuery 1.5 的变化
无法改变JS异步和单线程的本质
只能从写法上杜绝 callback 这种形式
它是一种语法糖形式,但是解耦了代码
很好的体现:开放封闭原则

// jquery 1.5 版本之前的写法:
let ajax = $.ajax({
    url: './data.json',
    success: function (res) {
        console.log('success 1');
        console.log('success 2');
        console.log('success 3');
    },
    error: function () {
        console.log('error 1');
    }
});
// jquery 1.5 版本之后的写法:
let ajax = $.ajax('./data.json');
ajax.then(function () {
    console.log('success a');
}, function () {
    console.log('error 1');
}).then(function () {
    console.log('success b');
}, function () {
    console.log('error 2');
});

(2)jQuery Deferred,如何简单的封装、使用?

// 对 Deferred 的简单封装
function waitHandle() {
    // 定义
    let dtd = $.Deferred();
    let wait = function (dtd) {
        let task = function () {
            console.log('执行完成');
            dtd.resolve(); // 成功
            // dtd.reject(); // 失败
        };
        setTimeout(task, 2000);

        // wait 返回
        // 注意,这里返回的是 promise 对象,而不是直接返回 deferred 对象
        return dtd.promise();
    };
    // 最终返回
    return wait(dtd);
}
let w = waitHandle(); // promise 对象
// w.reject(); // 报错,w.reject is not a function
$.when(w).then(function () {
    console.log('success 1');
}, function () {
    console.log('error 1');
});
$.when(w).then(function () {
    console.log('success 2');
}, function () {
    console.log('error 2');
});

// 1.总结,dtd的API可分为两类,用意不同
// 2.第一类:dtd.resolve dtd.reject,这是主动触发的函数
// 3.第二类:dtd.then dtd.done dtd.fail,这是被动监听的函数
// 4.这两类应该分开,用意不同,否则后果很严重
// 5.可以在上面代码创建实例之后执行 w.reject() 试一下,会报错

(3)初步引入 Promise 概念,promise 和 Deferred 的区别?
最主要的区别就是,Deferred 对象有主动修改和被动监听的函数,它们混在了一起,容易被外部篡改。
promise 对象只能被动监听,不能主动修改。

4.Promise 的基本使用和原理?
(1)基本语法
(2)如何捕获异常
(3)多个串联 - 链式执行的好处
(4)Promise.all 和 Promise.race
(5)Promise 标准 - 状态变化,then函数

5.介绍一下 async/await (和Promise的区别、联系)
(1)基本语法
(2)使用了 Promise,并没有和 Promise 冲突
(3)完全是同步的写法,再也没有回调函数
(4)但是:改变不了 JS 单线程、异步的本质

6.总结一下当前JS解决异步的方案
(1)jQuery Deferred
(2)Promise
(3)async/await
(4)还有一个,Generator,可以解决异步,但是为什么不用它:
原理比较复杂
不是异步的直接替代方式
有更好更简洁的解决方案 async/await
koa框架 也由 Generator 转向 async/await

4.虚拟 DOM

1.vdom 是什么?为何会存在vdom?
(1)virtual dom,虚拟DOM
(2)用JS模拟DOM结构
(3)DOM变化的对比,放在JS层来做(图灵完备语言才能做),提高效率
(4)提高重绘性能
为何会存在vdom?
(1)DOM操作是“昂贵”的,JS运行效率高
(2)尽量减少DOM操作,而不是“推到重来”
(3)项目越复杂,影响越严重
(4)vdom即可解决这个问题


  • Item 1
  • Item 2
// 对应的 vnode 基本结构 let vnode = { tag: 'ul', attrs: { id: 'list' }, children: [ { tag: 'li', attrs: { className: 'item' }, children: ['Item 1'] }, { tag: 'li', attrs: { className: 'item' }, children: ['Item 2'] }, ] };

2.vdom 如何应用,核心API是什么?
如何使用?使用 snabbdom 的用法来举例

核心API:h函数,patch函数
h('<标签名>', {...属性...}, [...子元素...]);
h('<标签名>', {...属性...});
patch(container, vnode);
patch(vnode, newVnode);

3.介绍一下diff算法
(1)什么是 diff 算法
是linux的基础命令,是git的基本工具
(2)去繁就简
diff算法非常复杂,实现难度很大,源码量很大
去繁就简,讲明白核心流程,不关心细节
大部分人都不清楚细节,但是要关心核心流程
去繁就简之后,依然具有很大挑战性,并不简单
(3)vdom为何要用diff算法
DOM操作是“昂贵”的,因此尽量减少DOM操作
找出本次DOM必须更新的节点来更新,其他的不更新
这个“找出”的过程,就需要diff算法
(4)diff算法的实现流程
patch(container, vnode) 和 patch(vnode, newVnode)
createElement() 和 updateChildren()

function createElement (vnode) {
    let tag = vnode.tag;
    let attrs = vnode.attrs || {};
    let children = vnode.children || [];
    if (tag == null) return null;

    // 创建真实 DOM 元素
    let elem = document.createElement(tag);
    // 给 elem 添加属性
    for (let attrName in attrs) {
        if (attrs.hasOwnProperty(attrName)) {
            elem.setAttribute(attrName, attrs[attrName]);
        }
    }
    // 给 elem 添加子元素
    children.forEach(function (childVnode){
        elem.appendChild(createElement(childVnode)); // 递归
    });

    return elem;
}
function updateChildren(vnode, newVnode) {
    let children = vnode.children || [];
    let newChildren = newVnode.children || [];

    children.forEach(function (childVnode, index) {
        let newChildVnode = newChildren[index];
        if (childVnode.tag === newChildVnode.tag) {
            // 深层次对比,递归
            updateChildren(childVnode, newChildVnode);
        } else {
            // 替换
            replaceNode(childVnode, newChildVnode);
        }
    });
}

function replaceNode(vnode, newVnode) {
    let elem = vnode.elem; // 真实的dom节点
    let newElem = createElement(newVnode);
    // 替换
    
}

5.MVVM 和 vue

1.说一下使用jQuery和使用vue的区别
(1)数据和视图的分离,解耦(开放封闭原则)
(2)以数据驱动视图,只关心数据变化,DOM操作被封装

使用jQuery写一个 todo-list demo

    使用vue写一个 todo-list demo

    • {{item}}

    2.说一下对MVVM的理解
    (1)MVVM - Model View ViewModel
    (2)三者的联系,以及如何对应到各段代码(todo-list例子)
    (3)ViewModel 的理解,联系 View 和 Model

    Vue三要素
    (1)响应式:vue 如何监听到data的每个属性变化?
    (2)模板引擎:vue 的模板如何被解析,指令如何处理?
    (3)渲染:vue 的模板如何被渲染成 html?以及渲染过程

    3.vue中如何实现响应式
    (1)什么是响应式?
    修改 data 属性之后,vue立刻监听到
    data 属性被代理到 vm 上
    (2)Object.defineProperty

    var vm = {};
    var data = {
        name: 'lxcan',
        age: 18
    };
    var key;
    for (key in data) {
        (function(key) {
            Object.defineProperty(vm, key, {
                get: function () {
                    console.log('get', data[key]);
                    return data[key];
                },
                set: function (newVal) {
                    console.log('set', newVal);
                    data[key] = newVal;
                }
            });
        })(key);
    }

    4.vue中如何解析模板
    (1)模板是什么?
    本质:字符串
    有逻辑,如 v-if v-for 等,嵌入了JS变量
    与 html 格式很像,但有很大区别
    最终还要转换为 html 来显示

    模板最终必须转换成JS代码,因为:
    有逻辑(v-if v-for),必须用 JS 才能实现
    转换为 html 渲染页面,必须用 JS 才能实现
    因此,模板最终要转换成一个 JS 函数(render 函数)

    render函数小demo

    {{price}}

    (2)render 函数
    根据 todo-list demo 的 render 函数:
    v-model 是怎么实现的?(双向数据绑定)
    v-on:click 是怎么实现的?(绑定click事件)
    v-for 是怎么实现的?(执行 _l() 函数,返回数组)

    function render() {
        with (this) { // this 就是 vm
            return _c(
                'div',
                {
                    attrs: {"id": "app"}
                },
                [
                    _c(
                        'div',
                        [
                            _c(
                                'input',
                                {
                                    directives: [
                                        {
                                            name: "model",
                                            rawName: "v-model",
                                            value: (title),
                                            expression: "title"
                                        }
                                    ],
                                    attrs: {"type": "text"},
                                    domProps: {
                                        "value": (title)
                                    },
                                    on: {
                                        "input": function ($event) {
                                            if ($event.target.composing) return;
                                            title = $event.target.value
                                        }
                                    }
                                }
                            ),
                            _v(" "),
                            _c(
                                'button',
                                {
                                    on: {
                                        "click": add
                                    }
                                },
                                [_v("submit")]
                            )
                        ]
                    ),
                    _v(" "),
                    _c(
                        'div',
                        [
                            _c(
                                'ul',
                                _l((list), function (item) {
                                    return _c('li', [_v(_s(item))])
                                })
                            )
                        ]
                    )
                ]
            )
        }
    }

    (3)render 函数与 vdom
    vm._c 是什么?render 函数返回了什么?
    vm._c 其实就相当于 snabbdom 中的 h 函数
    render 函数执行之后,返回的是 vnode

    vm._update = function(vnode) {
        const prevVnode = vm._vnode;
        vm._vnode = vnode;
        if (!prevVnode) {
            vm.$el = vm.__patch__(vm.$el, vnode);
        } else {
            vm.$el = vm.__patch__(prevVnode, vnode);
        }
    };
    function updateComponent() {
        vm._update(vm._render());  // vm._render 即 render 函数,返回了 vnode
    }

    updateComponent 中实现了 vdom 的 patch
    页面首次渲染执行 updateComponent
    data 中每次修改属性,执行 updateComponent

    总结:
    (1)模板:字符串,有逻辑,嵌入了JS变量
    (2)模板必须转换为 JS 代码(因为 有逻辑、要渲染html、有JS变量)
    (3)render 函数是什么样子的(price、todo-list)
    (4)render 函数执行返回 vnode
    (5)updateComponent

    5.vue的整个实现流程
    (1)第一步:解析模板成 render 函数
    with 的用法
    模板中的所有信息都被 render 函数包含
    模板中用到的 data 中的属性,都变成了JS变量
    模板中的 v-model v-for v-on 都变成了JS逻辑
    render 函数返回 vnode

    (2)第二步:响应式开始监听
    Object.defineProperty 将 data 的属性代理到 vm 上

    (3)第三步:首次渲染,显示页面,且绑定依赖
    初次渲染,执行 updateComponent ,执行 vm.render()
    执行 render 函数,会访问到 vm.list 和 vm.title
    会被响应式的 get 方法监听到

    为何要监听get,直接监听set不行吗?
    data 中有很多属性,有些被用到,有些可能不被用到
    被用到的会走到get,不被用到的不会走到get
    未走到get中的属性,set的时候我们也无需关心
    可以避免不必要的重复渲染

    执行 updateComponent,会走到 vdom 的 patch 方法
    patch 将 vnode 渲染成 DOM,初次渲染完成

    (4)第四步:data 属性变化,触发 rerender
    修改属性,被响应式的set监听到
    set中执行 updateComponent(异步执行)
    updateComponent 重新执行 vm._render()
    生成的 vnode 和 preVnode,通过 patch 进行对比
    渲染到 html 中

    6.组件化和 React

    1.说一下对组件化的理解?
    (1)组件的封装:封装视图、数据、变化逻辑(数据驱动视图变化)
    (2)组件的复用:props传递、复用

    2.JSX语法的本质
    (1)html的形式
    (2)引入JS变量和表达式
    (3)if...else...
    (4)循环
    (5)style和className
    (6)事件

    JSX解析

    // JSX代码
    var profile = 

    {[user.firstName, user.lastName].join(' ')}

    ; // 解析结果 var profile = React.createElement('div', null, React.createElement('img', {src: 'avator.png', className: 'profile'}), React.createElement('h3', null, [user.firstName, user.lastName].join(' ')), );

    JSX 其实是语法糖
    开发环境会将JSX编译成JS代码
    JSX的写法大大降低了学习成本和编码工作量
    同时,JSX也会增加debug成本

    JSX => 独立的标准
    JSX是React引入的,但不是React独有的
    React已经将它作为一个独立标准开放,其他项目也可用
    React.createElement 是可以自定义修改的
    标准说明:本身功能已经完备;和其他标准兼容和扩展没问题

    3.JSX和vdom的关系
    (1)分析:为何需要vdom?
    vdom 是 React 初次推广开来的,结合 JSX
    JSX 就是模板,最终要渲染成html,数据驱动视图
    初次渲染 + 修改 state 后的 re-render
    正好符合 vdom 的应用场景

    (2)React.createElement 和 h 函数,最终都返回 vnode

    (3)何时 patch
    初次渲染 - ReactDOM.render(, container)
    会触发 patch(container, vnode)
    re-render - setState
    会触发 patch(vnode, newVnode)

    (4)自定义组件的解析
    初始化实例,然后执行 render

    // JSX代码
    // return (
    //     
    // // //
    // ) // 解析之后的代码 // return React.createElement('div', null, // React.createElement(Input, {addTitle: this.addTitle.bind(this)}), // React.createElement(List, {data: this.state.list}), // )

    'div' - 直接渲染

    即可,vdom可以做到
    Input 和 List ,是自定义组件(class),vdom 默认不认识
    因此 Input 和 List 定义的时候必须声明 render 函数
    根据 props 初始化实例,然后执行实例的 render 函数
    render 函数返回的还是 vnode 对象

    4.说一下 React setState 的过程
    (1)setState 的异步(效果、原因)
    setState 为何需要异步?
    可能会一次执行多次 setState
    你无法规定、限制用户如何使用 setState
    没必要每次 setState 都重新渲染,考虑性能
    即便是每次重新渲染,用户也看不到中间的效果(JS执行的时候DOM渲染是阻塞的,单线程)
    只看到最后的结果即可

    (2)vue 修改属性也是异步(效果、原因)
    效果、原因和 setState 一样
    对比记忆,印象深刻

    (3)setState 的过程
    每个组件实例,都有 renderComponent 方法(继承自 Component 类)
    执行 renderComponent 会重新执行实例的render
    render 函数返回 newVnode,然后拿到 preVnode
    最终执行 patch(preVnode, newVnode)

    5.React vs Vue,怎么做技术选型

    前言:
    技术选型没有绝对的对错,技术选型要考虑的因素非常多,你要有自己的主见就行,只要能说出合理的理由。

    (1)两者的本质区别
    vue - 本质是 MVVM 框架,由 MVC 发展而来
    React - 本质是前端组件化框架,由后端组件化发展而来
    但这并不妨碍他们两者都能实现相同的功能

    (2)看模板和组件化的区别
    vue - 使用模板(最初由 angular 提出)
    React - 使用 JSX
    模板语法上,我更加倾向于 JSX
    模板分离上,我更加倾向于 vue

    模板的区别:
    模板应该和 JS 逻辑分离(开放封闭原则)

    组件化的区别:
    React 本身就是组件化,没有组件化就不是 React
    vue 也支持组件化,不过是在 MVVM上的扩展
    查阅 vue 组件化的文档,洋洋洒洒很多还挺复杂(侧面反映)
    对于组件化,我更加倾向于 React,做的彻底而清晰

    (3)两者共同点
    都支持组件化
    都是数据驱动视图

    (4)总结问题答案
    国内使用,首推 vue。文档更易读、易学、社区够大。(团队的技术不太扎实,没有框架经验,首推 vue)
    如果团队水平较高经验较丰富,推荐使用 React。组件化和JSX,组件化做得彻底而清晰,JSX也趋向于标准化

    7.hybrid

    前言
    移动端占大部分流量,已经远远超过PC
    一线互联网公司都有自己的APP
    这些APP中有很大比例的前端代码
    那微信举例,你每天浏览微信的内容,多少是前端?

    1.hybrid 是什么,为何用 hybrid?
    (1)hybrid 文字解释
    hybrid 即“混合”,即前端和客户端的混合开发
    需前端开发人员和客户端开发人员配合完成
    某些环节也可能涉及到 server 端

    (2)存在价值,为何会用 hybrid
    可以快速迭代更新【关键】(无需app审核,思考为何?)
    体验流畅(和NA的体验基本类似)
    减少开发和沟通成本,双端公用一套代码

    (3)webview
    是app中的一个组件(app可以有webview,也可以没有)
    用于加载 h5 页面,即一个小型的浏览器内核

    (4)file:// 协议
    其实在一开始接触 html 开发,就已经使用了 file 协议
    只不过你当时没有 “协议” “标准” 等这些概念
    再次强调 “协议” “标准” 的重要性!!!

    file 协议和 http 协议区别
    file 协议:本地文件,快
    http(s) 协议:网络加载,慢

    (5)hybrid 实现流程
    不是所有场景都适合使用 hybrid
    使用NA:提要要求极致,变化不频繁(如头条的首页)
    使用hybrid:体验要求高,变化频繁(如头条的新闻详情页)
    使用h5:体验无要求,不常用(如举报、反馈等页面)

    前端做好静态页面(html js css),将文件交给客户端
    客户端拿到前端静态页面,以文件形式存储在app中
    客户端在一个 webview 中,使用file协议加载静态页面

    解答:
    hybrid 是客户端和前端的混合开发
    hybrid 存在的核心意义在于快速迭代,无需审核
    hybrid 实现流程(图),以及 webview 和 file 协议

    2.介绍一下 hybrid 更新和上线的流程?
    前提:
    要替换每个客户端的静态文件
    只能客户端来做(客户端是我们开发的)
    客户端去 server 下载最新的静态文件
    我们维护 server 的静态文件

    完整流程:
    分版本,有版本号,如 201910030916
    将静态文件压缩成 zip 包,上传到服务端
    客户端每次启动,都去服务端检查版本号
    如果服务端版本号大于客户端版本号,就去下载最新的 zip 包
    下载完之后解压包,然后将现有文件覆盖

    掌握流程图
    要点1:服务端的版本和 zip 包维护
    要点2:更新 zip 包之前,先对比版本号
    要点3:zip 下载解压和覆盖

    3.hybrid 和 h5 的主要区别?
    (1)优点:
    体验更好,跟NA体验基本一致
    可快速迭代,无需app审核(关键)
    (2)缺点:
    开发成本高。联调、测试、查bug都比较麻烦
    运维成本高。参考更新上线的流程
    (3)适用的场景:
    hybrid:产品的稳定功能,体验要求高,迭代频繁
    h5:单次的运营活动(如xx红包)或不常用功能

    优点:体验好,可快速迭代
    缺点:开发成本高,运维成本高
    适用的场景:hybrid 适合产品型,h5 适用运营型

    4.前端 JS 和客户端如何通讯?
    (1)回顾遗留问题
    新闻详情页适用 hybrid ,前端如何获取新闻内容?
    不能用 ajax 获取。第一跨域(还能解决),第二速度慢
    客户端获取新闻内容,然后JS通讯拿到内容,再渲染

    (2)JS 和客户端通讯的基本形式
    JS 访问客户端能力,传递参数和回调函数
    客户端通过回调函数返回内容

    (3)schema 协议简介和使用
    schema 协议——前端和客户端通讯的约定

    
    

    (4)schema 使用的封装

    invoke.js

    (function (window) {
        // 调用 schema 的封装
        function _invoke (action, data, callback) {
            // 拼接 schema 协议
            var schema = 'myapp://utils/' + action;
    
            // 拼接参数
            schema += '?a=a';
            var key;
            for(key in data) {
                if (data.hasOwnProperty(key)) {
                    schema += '&' + key + '=' + data[key];
                }
            }
    
            // 处理 callback
            var callbackName = '';
            if (typeof callback === 'string') {
                callbackName = callback;
            } else {
                callbackName = action + Date.now();
                window[callbackName] = callback;
            }
    
            schema += '&callback=' + callbackName;
    
            // 触发
            var iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            iframe.src = schema;  // 重要
            var body = document.body;
            body.appendChild(iframe);
            setTimeout(function () {
                body.removeChild(iframe);
                iframe = null;
            });
        }
    
        // 暴露到全局变量
        window.invoke = {
            share: function (data, callback) {
                _invoke('share', data, callback);
            },
            scan: function (data, callback) {
                _invoke('scan', data, callback);
            },
            login: function (data, callback) {
                _invoke('login', data, callback);
            },
        }
    })(window);
    
    
    
    
    

    (5)内置上线
    将封装的代码打包,叫做 invoke.js ,内置到客户端
    客户端每次启动 webview ,都默认执行 invoke.js
    本地加载,免去网络加载的时间,更快
    本地加载,没有网络请求,黑客看不到 schema 协议,更安全

    解答:
    通讯的基本形式:调用能力,传递参数,监听回调
    对 schema 协议的理解和使用
    调用 schema 代码的封装
    内置上线的好处:更快、更安全

    你可能感兴趣的:(面试,react.js,vue.js,javascript,前端)