资讯专栏INFORMATION COLUMN

前端面试之路三(javascript高级篇)

adam1q84 / 2602人阅读

摘要:原型原型的实际应用原型如何实现它的扩展性原型的实际应用和的简单使用是原型方法是原型方法是原型方法是原型方法如何使用原型空对象源码中,这里的处理情况比较复杂。

原型

原型的实际应用

原型如何实现它的扩展性

原型的实际应用
jquery和zepto的简单使用

jquery test1

jquery test2

jquery test3

jquery test in div

 var $p = $("p")
$p.css("font-size","40px")   //css是原型方法
console.log($p.html())       //html是原型方法

var $div1 = $("#div1")
$div1.css("color","blue")     //css是原型方法
console.log($div1.html())     //html是原型方法
zepto如何使用原型
//空对象
var zepto = {}

zepto.init = function(){
    //源码中,这里的处理情况比较复杂。但因为是本次只是针对原型,因此这里就弱化了
    var slice = Array.prototype.slice
    var dom = slice.call(document.querySelectorAll(selector))
    return zepto.Z(dom,selector)
}

//即使用zepto时候的$
var $ = function(selector){
    return zepto.init(selector)
}
//这就是构造函数
function Z(dom, selector) {
    var i, len = dom ? dom.length : 0
    for (i = 0; i < len; i++) {
        this[i] = dom[i]
    }
    this.length = len
    this.selector = selector || ""
}  

zepto.Z = function(dom, selector) {
    return new Z(dom, selector)
}
$.fn = {
    constructor:zepto.Z,
    css:function(key,value){},
    html:function(value){}
}

zepto.Z.prototype = Z.prototype = $.fn 
简单的zepto实现

myZepto.js实现

(function(window) {
    var zepto = {};

    function Z(dom, selector) {
        var i, len = dom ? dom.length : 0;
        for (i = 0; i < len; i++) {
            this[i] = dom[i];
        }
        this.length = len;
        this.selector = selector || "";
    }

    zepto.Z = function(dom, selector) {
        return new Z(dom, selector);
    }

    zepto.init = function(selector) {
        var slice = Array.prototype.slice;
        var dom = slice.call(document.querySelectorAll(selector));
        return zepto.Z(dom, selector);
    }

    var $ = function(selector) {
        return zepto.init(selector);
    }

    window.$ = $;

    $.fn = {
        css: function(key, value) {
            console.log(key, value);
        },
        html: function() {
            return "html";
        }
    }

    Z.prototype = $.fn

})(window)
jquery如何使用原型
var jQuery = function(selector){
    //注意new关键字,第一步就找到了构造函数
    return new jQuery.fn.init(selector);
}

//定义构造函数
var init =  jQuery.fn.init = function(selector){
    var slice = Array.prototype.slice;
    var dom = slice.call(document.querySelectorAll(selector));

    var i,len=dom?dom.length:0;
    for(i = 0;i
原型的扩展性
如何体现原型的扩展性

总结zeptojquery原型的使用

插件机制

为什么要把原型放在$.fn

init.prototype = jQuery.fn;

zepto.Z.prototype = Z.prototype = $.fn

因为要扩展插件

//做一个简单的插件
$.fn.getNodeName = function(){
    return this[0].nodeName
}
好处

只有$会暴露在window全局变量上

将插件扩展统一到$.fn.xxx这一接口,方便使用

异步 什么是单线程,和异步有什么关系

单线程:只有一个线程,同一时间只能做一件事,两段JS不能同时执行

原因:避免DOM渲染的冲突

解决方案:异步

为什么js只有一个线程:避免DOM渲染冲突

浏览器需要渲染DOM

JS可以修改DOM结构

JS执行的时候,浏览器DOM渲染会暂停

两端JS也不能同时执行(都修改DOM就冲突了)

webworker支持多线程,但是不能访问DOM

 

什么是event-loop

事件轮询,JS实现异步的具体解决方案

同步代码,直接执行(主线程)

异步函数先放在异步队列中(任务队列)

待同步函数执行完毕,轮询执行异步队列的函数

setTimeout(function(){
    console.log(1);
},100);              //100ms之后才放入异步队列中,目前异步队列是空的
setTimeout(function(){
    console.log(2);  //直接放入异步队列
})
console.log(3)       //直接执行

//执行3之后,异步队列中只有2,把2拿到主线程执行
//2执行完之后,异步队列中并没有任务,所以一直轮询异步队列
//直到100ms之后1放入异步队列,将1拿到主线程中执行
$.ajax({
    url:"./data.json",
    success:function(){        //网络请求成功就把success放入异步队列
        console.log("a");
    }
})

setTimeout(function(){
    console.log("b")
},100)

setTimeout(function(){
    console.log("c");
})
console.log("d")

//打印结果:
//d    //d   
//c    //c  
//a    //b   
//b    //a   

//真实环境不会出现dacb
解决方案存在的问题

问题一:没按照书写方式执行,可读性差

问题二:callback中不容易模块化

是否用过jQuery的Deferred

jQuery1.5的变化

使用jQuery Deferred

初步引入Promise概念

 

jQuery1.5之前
var ajax = $.ajax({
    url:"./data.json",
    success:function(){
        console.log("success1");
        console.log("success2");
        console.log("success3");
    },
    error:function(){
        console.log("error");
    }
})
console.log(ajax); //返回一个XHR对象

 

jQuery1.5之后

第一种写法:

var ajax = $.ajax("./data.json");
ajax.done(function(){
    console.log("success1")
})
.fai(function(){
    console.log("fail")
})
.done(function(){
    console.log("success2");
})
console.log(ajax); //deferred对象

第二种写法:

var ajax = $.ajax("./data.json");
ajax.then(function(){
    console.log("success1")
},function(){
    console.log("error1");
})
.then(function(){
    console.log("success2");
},function(){
    console.log("error");
})

无法改变JS异步和单线程的本质

只能从写法上杜绝callback这种形式

它是一种语法糖,但是解耦了代码

很好的提现:开放封闭原则(对扩展开放对修改封闭)

使用jQuery Deferred

给出一段非常简单的代码,使用setTimeout函数

var wait = function(){
    var task = function(){
        console.log("执行完成");
    }
    setTimeout(task,2000)
}

wait();

新增需求:要在执行完成之后进行某些特别复杂的操作,代码可能会很多,而且分好几步

function waitHandle(){
    var dtd = $.Deferred();         //创建一个deferred对象
    
    var wait = function(dtd){       // 要求传入一个deferred对象
        var task = function(){
            console.log("执行完成");
            dtd.resolve();          //表示异步任务已完成
            //dtd.reject()          // 表示异步任务失败或者出错
        };
        setTimeout(task,2000);
        return dtd;
    }
    //注意,这里已经要有返回值
    return wait(dtd);
}


//使用
var w = waithandle()
w.then(function(){
    console.log("ok1");
},function(){
    console.log("err2");
})
.then(function(){
    console.log("ok2");
},function(){
    console.log("err2");
})
//还有w.wait w.fail
总结:dtdAPI可分成两类,用意不同

第一类:dtd.resolvedtd.reject

第二类:dtd.thendtd.donedtd.fail

这两类应该分开,否则后果严重!

可以在上面代码中最后执行dtd.reject()试一下后果

使用dtd.promise()
function waitHandle(){
    var dtd = $.Deferred();
    var wait = function(){
        var task = function(){
            console.log("执行完成");
            dtd.resolve();
        }
        setTimeout(task,2000)
        return dtd.promise();  //注意这里返回的是promise,而不是直接返回deferred对象
    }
    return wait(dtd)
}


var w = waitHandle();   //promise对象
$.when(w).then(function(){
    console.log("ok1");
},function(){
    console.log("err1");
})

//w.reject()  //w.reject is not a function

监听式调用:只能被动监听,不能干预promise的成功和失败

可以jQuery1.5对ajax的改变举例

说明如何简单的封装、使用deferred

说明promise和Defrred的区别

要想深入了解它,就需要知道它的前世今生

Promise的基本使用和原理
基本语法回顾
fucntion loadImg() {
    var promise = new Promise(function(resolve,reject) {
        var img = document.getElementById("img")
        img.onload = function(){
            resolve(img)
        }
        img.onerror  = function(){
            reject()
        }
    })
    return promise
}

var src = ""
var result = loadImg(src)
result.then(function() {
    console.log(1, img.width)
    return img
}, fucntion() {
    console.log("error 1")
}).then(function(img) {
    console.log(1, img.width)
})
异常捕获

规定:then只接受一个函数,最后统一用catch捕获异常

var src = ""
var result = loadImg(src)
result.then(function() {
    console.log(1, img.width)
    return img
}).then(function(img) {
    console.log(1, img.width)
}).catch(function(ex) {
    //最后统一捕获异常
    console.log(ex)
})
多个串联
var scr1 = "https://www.imooc.com/static/img/index/logo_new.png";
var result1 = loadImg(src1);
var src2 = "https://www.imooc.com/static/img/index/logo_new1.png";
var result2 = loadImg(src2);


result1.then(function(img1) {
    console.log("第一个图片加载完成", img1.width);
    return result2;   //重要
}).then(function(img2) {
    console.log("第二个图片加载完成", img2.width);
}).catch(function(ex) {
    console.log(ex);
})

Promise.allPromise.race

Promise.all接收一个promise对象的数组

待全部完成后,统一执行success

Promise.all([result1, result2]).then(datas => {
        //接收到的datas是一个数组,依次包含了多个promise返回的内容
        console.log(datas[0]);
        console.log(datas[1]);
})

Promise.race接收一个包含多个promise对象的数组

只要有一个完成,就执行success

Promise.race([result1, result2]).then(data => {
    //data即最先执行完成的promise的返回值
    console.log(data);
})

Promise标准

状态

三种状态:pending,fulfilled,rejected

初始状态:pending

pending变为fulfilled,或者pending变为rejected

状态变化不可逆

then

promise必须实现then这个方法

then()必须接收两个函数作为标准

then返回的必须是一个promise实例

没有return就是默认返回的result

Promise总结

基本语法

如何异常捕获(Errorreject)

多个串联-链式执行的好处

Promise.allPromise.race

Promise标准 - 状态变化、then函数

介绍一下async/await(和Promise的区别、联系)

es7提案中,用的比较多,babel也已支持

koa框架 generator替换async/await

解决的是异步执行和编写逻辑

then只是将callback拆分了
var w = watiHandle()
w.then(function(){...},function(){...})
.then(function(){...},function(){...})
async/await是最直接的同步写法
const load = async function(){
    const result1 = await loadImg(src1)
    console.log(result1)
    const result2 = await loadImg(src2)
    console.log(result2)
}
load()

用法

使用await,函数后面必须用async标识

await后面跟的是一个Promise实例

需要balbel-polyfill(兼容)

特点

使用了Promise,并没有和Promise冲突

完全是同步的写法,再也没有回调函数

但是:改变不了JS单线程、异步的本质

总结一下当前JS异步的方案 虚拟DOM vdom 是什么?为何会存在 vdom?
什么是vdom

virtual dom,虚拟DOM

用JS模拟DOM结构

DOM变化的对比,放在JS层来做(图灵完备语言:能实现各种逻辑的语言)

提高重绘性能

DOM
  • Item 1
  • Item 2
虚拟DOM
{
    tag: "ul",
    attrs: {
        id: "list"
    },
    children: [{
            tag: "li",
            attrs: { className: "item" },
            children: ["item1"]
        },
        {
            tag: "li",
            attrs: { className: "item" },
            children: ["item2"]
        }
    ]
}

//className代替class,因为class是js的保留字

浏览器最耗费性能就是DOM操作,DOM操作非常昂贵
现在浏览器执行JS速度非常快
这就是vdom存在的原因

一个需求场景
//将该数据展示成一个表格
//随便修改一个信息,表格也随着修改
[
    {
        name: "zhangsan",
        age: 20,
        address: "beijing"
    },
    {
        name: "lisi",
        age: 21,
        address: "shanghai"
    },
    {
        name: "wangwu",
        age: 22,
        address: "guangzhou"
    }
]
jQery实现
//渲染函数
funciton render(data) {
    //此处省略n行
}

//修改信息
$("#btn-change").click(function(){
    data[1].age = 30;
    data[2].address = "shenzhen";
    render(data);
})


//初始化时渲染
render(data)
//render函数具体写法
function render(data) {
    $container = $("#container");
    //清空现有内容
    $container.html("");
    //拼接table
    var $table = $("")
    $table.append($(""))
    data.forEach(item => {
        $table.append($(""))
        $container.append($table)
    });

}

//只执行了一次渲染,相对来说还是比较高效的
//DOM渲染是最昂贵的,只能尽量避免渲染
遇到的问题

DOM操作是“昂贵”的,JS运行效率高

尽量减少DOM操作,而不是"推倒重来"(清空重置)

项目越复杂,影响就越严重

vdom可解决这个问题

var div = document.createElement("div");
var item,result = "";
for(item in div){
    result += "|" + item;
}
console.log(result);

//浏览器默认创建出来的DOM节点,属性是非常多的

//result
|align|title|lang|translate|dir|dataset|hidden|tabIndex|accessKey|draggable|spellcheck|autocapitalize|contentEditable|isContentEditable|inputMode|offsetParent|offsetTop|offsetLeft|offsetWidth|offsetHeight|style|innerText|outerText|onabort|onblur|oncancel|oncanplay|oncanplaythrough|onchange|onclick|onclose|oncontextmenu|oncuechange|ondblclick|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|ondurationchange|onemptied|onended|onerror|onfocus|oninput|oninvalid|onkeydown|onkeypress|onkeyup|onload|onloadeddata|onloadedmetadata|onloadstart|onmousedown|onmouseenter|onmouseleave|onmousemove|onmouseout|onmouseover|onmouseup|onmousewheel|onpause|onplay|onplaying|onprogress|onratechange|onreset|onresize|onscroll|onseeked|onseeking|onselect|onstalled|onsubmit|onsuspend|ontimeupdate|ontoggle|onvolumechange|onwaiting|onwheel|onauxclick|ongotpointercapture|onlostpointercapture|onpointerdown|onpointermove|onpointerup|onpointercancel|onpointerover|onpointerout|onpointerenter|onpointerleave|nonce|click|focus|blur|namespaceURI|prefix|localName|tagName|id|className|classList|slot|attributes|shadowRoot|assignedSlot|innerHTML|outerHTML|scrollTop|scrollLeft|scrollWidth|scrollHeight|clientTop|clientLeft|clientWidth|clientHeight|attributeStyleMap|onbeforecopy|onbeforecut|onbeforepaste|oncopy|oncut|onpaste|onsearch|onselectstart|previousElementSibling|nextElementSibling|children|firstElementChild|lastElementChild|childElementCount|onwebkitfullscreenchange|onwebkitfullscreenerror|setPointerCapture|releasePointerCapture|hasPointerCapture|hasAttributes|getAttributeNames|getAttribute|getAttributeNS|setAttribute|setAttributeNS|removeAttribute|removeAttributeNS|hasAttribute|hasAttributeNS|toggleAttribute|getAttributeNode|getAttributeNodeNS|setAttributeNode|setAttributeNodeNS|removeAttributeNode|closest|matches|webkitMatchesSelector|attachShadow|getElementsByTagName|getElementsByTagNameNS|getElementsByClassName|insertAdjacentElement|insertAdjacentText|insertAdjacentHTML|requestPointerLock|getClientRects|getBoundingClientRect|scrollIntoView|scrollIntoViewIfNeeded|animate|computedStyleMap|before|after|replaceWith|remove|prepend|append|querySelector|querySelectorAll|webkitRequestFullScreen|webkitRequestFullscreen|scroll|scrollTo|scrollBy|createShadowRoot|getDestinationInsertionPoints|ELEMENT_NODE|ATTRIBUTE_NODE|TEXT_NODE|CDATA_SECTION_NODE|ENTITY_REFERENCE_NODE|ENTITY_NODE|PROCESSING_INSTRUCTION_NODE|COMMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE|DOCUMENT_FRAGMENT_NODE|NOTATION_NODE|DOCUMENT_POSITION_DISCONNECTED|DOCUMENT_POSITION_PRECEDING|DOCUMENT_POSITION_FOLLOWING|DOCUMENT_POSITION_CONTAINS|DOCUMENT_POSITION_CONTAINED_BY|DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC|nodeType|nodeName|baseURI|isConnected|ownerDocument|parentNode|parentElement|childNodes|firstChild|lastChild|previousSibling|nextSibling|nodeValue|textContent|hasChildNodes|getRootNode|normalize|cloneNode|isEqualNode|isSameNode|compareDocumentPosition|contains|lookupPrefix|lookupNamespaceURI|isDefaultNamespace|insertBefore|appendChild|replaceChild|removeChild|addEventListener|removeEventListener|dispatchEvent
vdom如何应用,核心API是什么
介绍snabbdom

一个实现vdom的库,vue升级2.0借鉴了snabbdom的算法

var container = document.getElementById("container")

var vnode = h("div#container.two.classes", { on: { click: someFn } }, [
    h("span", { style: { fontWeight: "bold" }, "This is bold" }),
    "and this is just normal text",
    h("a", { props: { href: "/foo" } }, "I"ll take you places")
])

//patch into empty DOM element - this modifies the DOM as a side effect
patch(container, vnode)

var newVnode = h("div#container.two.classes", { on: { click: anotherEventHandle } }, [
        h("span", { style: { fontWeight: "normal", fontStyle: "italic" } }, "this is now italic type"),
        "and this is still just normal text",
        h("a", { props: { href: "/bar" } }, "I"ll take you places")
    ])
    //send `patch` invocation

patch(vnode, newVnode); //Snabbdom efficiently updates the old view to the new state
h函数
{
    tar: "ul",
    attrs: {
        id: "list"
    },
    children: [
    {
        tag: "li",
        attrs: {
            className: "item",
            children: ["item1"]
        }
    },
    {
        tag: "li",
        attrs: {
            className: "item"
        },
        children: ["item2"]
        }
    ]
}
对应的vnode
var vnode = h("ul#list", {}, [
    h("li.item", {}, "Item1"),
    h("li.item", {}, "Item")
])
patch函数
var vnode = h("ul#list", {}, [
    h("li.item", {}, "Item1"),
    h("li.item", {}, "Item2")
])

var container = document.getElementById("container")
patch(container, vnode)

//模拟改变
var btnChange = document.getElementById("btn-change")
btnChange.addEventListener("click", function() {
    var newVnode = h("ul#list", {}, [
        h("li.item", {}, "Item 111"),
        h("li.item", {}, "Item 222"),
        h("li.item", {}, "Item 333")
    ])
    patch(vnode, newVnode)
})
  
重新实现前面的demo(用snabbdom实现)
 var snabbdom = window.snabbdom
    var patch = snabbdom.init([
        snabbdom_class,
        snabbdom_props,
        snabbdom_style,
        snabbdom_eventlisteners
    ])
    var h = snabbdom.h
    var container = document.getElementById("container")
    var btnChange = document.getElementById("btn-change")
    var vnode
    var data = [{
            name: "zhangsan",
            age: 20,
            address: "beijing"
        },
        {
            name: "zhangsan",
            age: 20,
            address: "shanghai"
        },
        {
            name: "zhangsan",
            age: 20,
            address: "shenzhen"
        }
    ]
    
    data.unshift({
        name:"姓名:",
        age:"年龄:",
        address:"地址:"
    })


    render(data);

    function render(data){
        var newVnode = h("table",{},data.map(function(item){
            var tds = [],i
            for(i in item){
                if(item.hasOwnProperty(i)){
                    tds.push(h("td",{},item[i]+""))
                }
            }
            return h("tr",{},tds)
        }))
        if(vnode){
            patch(vnode,newVnode)
        }else{
            patch(container,newVnode)
        }
        vnode = newVnode  //存储当前vnode结果

    }

  
    btnChange.addEventListener("click",function(){
        data[1].age = 30
        data[2].address = "深圳"
        //re-render
        render(data)
    })
核心API

h函数的用法

 h("<标签名>",{...属性...},[...子元素...])
 
 h("<标签名>",{...属性...},"...")

patch函数用法

patch(container,vnode) 

patch(vnode,newVnode)  //rerender
介绍一下diff算法
什么是diff算法

linux中的diff:找出两个文件中的不同:

diff log1.txt log2.txt

git diff:修改之前和修改之后版本的差异

git diff xxxx.js

网上的一些在线差异化网站

http://tool.oschina.net/diff/

diff算法并不是vdom提出的概念,一直存在
现在应用到vdom中,对比的是两个虚拟dom

去繁就简

diff算法非常复杂,实现难度很大,源码量很大

去繁就简,讲明白核心流程,不关心细节

面试官大部分也不清楚细节,但是很关心核心流程

去繁就简之后,依然具有很大的挑战性

vdom为何要使用diff

DOM操作是“昂贵”的,因此尽量减少DOM操作

找出本次DOM必须更新的节点来更新,其他的不更新

“找出”的过程,就需要diff算法

diff算法实现
diff实现的过程

patch(container,vnode)

如何用vnode生成真实的dom节点

 {
    tag: "ul",
    attrs: {
        id: "list"
    },
    children: [
        {
            tag: "li",
            attrs: {
                className: "item"
            },
            children:["item 1"]
        }
    ]
}
  • Item 1
简单实现算法
function createElement(vnode) {
    var tag = vnode.tag;
    var attrs = vnode.attrs || {};
    var children = vnode.children || [];
    if (!tag) {
        return null
    }
    //创建元素
    var elem = document.createElement(tag);
        //属性
    var attrName;
    for (attrName in atts) {
        if (attrs.hasOwnProperty(attrName)) {
            elem.setAttribute(attrName, attrs[attrName])
        }
    }
    //子元素
    children.array.forEach(childVnode => {
        elem.appendChild(createElement(childVnode))
    });
    return elem;
}
 

patch(vnode,newVnode)

 {
    tag: "ul",
    attrs: { id: "list" },
    children: [{
            tag: "li",
            attrs: { className: "item" },
            children: ["Item 1"]
        },
        {
            tag: "li",
            attrs: {
                className: "item",
                children: ["Item 2"]
            }
        }
    ]
}
对比:
{
    tag: "ul",
    attrs: { id: "list" },
    children: [{
            tag: "li",
            attrs: { className: "item" },
            children: ["Item 1"]
        },
        {
            tag: "li",
            attrs: {
                className: "item",
                children: ["Item 222"]
            }
        },
        {
            tag: "li",
            attrs: {
                className: "item",
                children: ["Item 3"]
            }
        }
    ]
}
 

简单实现
 function updateChildren(vnode, newVnode) {
    var children = vnode.children || [];
    var newChildren = newVnode.children || [];

    //遍历现有的children
    children.forEach((child, index) => {
        var newChild = newChildren[index];
        if (newChild == null) {
            return;
        }
        if (child.tag === newChild.tag) {
            updateChildren(child, newChild)
        } else {
            replaceNode(child, newChild)
        }
    });
}

节点新增和删除

节点重新排序

节点属性、样式、事件绑定

如何积极压榨性能

MVVM和Vue使用jQuery和使用框架的区别
数据与视图的分离,解耦(封闭开放原则)

jquery中数据与视图混合在一起了,不符合开放封闭原则

vue:通过Vue对象将数据和View完全分离开来了

以数据驱动视图

jquery完全违背了这种理念,jquery直接修改视图,直接操作DOM

vue对数据进行操作不再需要引用相应的DOM对象,通过Vue对象这个vm实现相互的绑定。以数据驱动视图,只关心数据变化,DOM操作被封装

对MVVM的理解

MVC


MVVM

Model:数据,模型

View:视图、模板(视图和模型是分离的)

ViewModel:连接Model和View

MVVM不算是创新,ViewModel算是一种微创新

是从mvc发展而来,结合前端场景的创新

如何实现MVVM
Vue三要素

响应式vue如何监听到data的每个属性变化

模板引擎:vue的模板如何被解析,指令如何处理

渲染vue的模板如何渲染成html`,以及渲染过程

vue中如何实现响应式
什么是响应式

修改data属性之后,vue立刻监听到(然后立刻渲染页面)

data属性被代理到vm上

Object.defineProperty

ES5中加入的API,所以Vue不支持低版本浏览器(因为低版本浏览器不支持这个属性)

var obj = {};
    var _name = "zhangsan";

    Object.defineProperty(obj,"_name",{
        get:function(){
            console.log("get");
            return _name;
        },
        set:function(newVal){
            console.log(newVal);
            _name = newVal;
        }
    });

    console.log(obj.name) //可以监听到
    obj.name = "list"
模拟实现
var vm = new Vue({
    el: "#app",
    data: {
        price: 100,
        name: "zhangsan"
    }
})
var vm = {}
var data = {
    name: "zhangsan",
    price: 100
}

var key, value
for (key in data) {
    //命中闭包。新建一个函数,保证key的独立的作用域
    (function(key) {
        Object.defineProperty(vm, key, {
            get: function() {
                console.log("get")
                return data[key]
            },
            set: function(newVal) {
                console.log("set")
                data[key] = newVal
            }
        })
    })(key)
}
vue如何解析模板
模板是什么

本质:字符串;有逻辑,如v-if,if-if,嵌入JS变量...

与html格式很像,但有很大区别(静态),最终还是要转化为html显示

模板最终必须转换成JS代码

1、因为有逻辑(v-for,v-if),必须用JS才能实现(图灵完备)

2、转换为html渲染页面,必须用JS才能实现

因此,模板最重要转换成一个JS函数(render函数)

render函数
先了解with()的使用
function fn1() {
    with(obj) {
        console.log(name);
        console.log(age);
        getAddress()
    }
}
最简单的一个示例

{{price}}

with(this) {    //this:vm
    return _c(
        "div", 
        {
            attrs: { "id": "app" }
        }, 
        [
            _c("p",[_v(_s(price))]     )  //price代理到了vm上
        ]
    )
}
//vm._c
ƒ (a, b, c, d) { return createElement(vm, a, b, c, d, false); }

//vm._v
ƒ createTextVNode (val) {
  return new VNode(undefined, undefined, undefined, String(val))
}

//vm._s
ƒ toString (val) {
  return val == null? "": typeof val === "object"? JSON.stringify(val, null,2): String(val)
}

模板中所有信息都包含在了render函数中

thisvm

pricethis.price,即data中的price

_cthis._cvm._c

更复杂的一个例子
    
  • {{item}}
如何寻找render函数:code.render

模板如何生成render函数:

vue2.0开始就支持预编译,我们在开发环境下写模板,经过编译打包,产生生产环境的render函数(JS代码)

with(this){  // this 就是 vm
            return _c(
                "div",
                {
                    attrs:{"id":"app"}
                },
                [
                    _c(
                        "div",
                        [
                            _c(
                                "input",
                                {
                                    directives:[
                                        {
                                            name:"model",
                                            rawName:"v-model",
                                            value:(title),
                                            expression:"title"
                                        }
                                    ],
                                    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))])})
                            )
                        ]
                    )
                ]
            )
        }
//vm._l
 function renderList(val,render) {
        var ret, i, l, keys, key;
        if (Array.isArray(val) || typeof val === "string") {
            ret = new Array(val.length);
            for (i = 0, l = val.length; i < l; i++) {
                ret[i] = render(val[i], i);
            }
        } else if (typeof val === "number") {
            ret = new Array(val);
            for (i = 0; i < val; i++) {
                ret[i] = render(i + 1, i);
            }
        } else if (isObject(val)) {
            keys = Object.keys(val);
            ret = new Array(keys.length);
            for (i = 0, l = keys.length; i < l; i++) {
                key = keys[i];
                ret[i] = render(val[key], key, i);
            }
        }
        if (isDef(ret)) {
            (ret)._isVList = true;
        }
        return ret
    }

v-model是怎么实现的?

v-on:click是怎么实现的

v-for是怎么实现的

render函数与DOM

已经解决了模板中"逻辑"(v-for,v-if)的问题

还剩下模板生成html的问题

另外,vm_c是什么?render函数返回了什么

vm._c其实就相当于snabbdom中的h函数

render函数执行之后,返回的是vnode

vm._update(vnode) {
    const prevVnode = vm._vnode
    vm._vnode = vnode
    if (!prevVnode) {
        vm.$sel = vm.__patch__(vm.$sel, vnode)    //与snabbdom中的patch函数差不多
    } else {
        vm.$sel = vm.__patch__(prevVnode, vnode)
    }
}

funciton updateComponent() {
    //vm._render即上面的render函数,返回vnode
    vm._update(vm._render())
}

updateComponent中实现了vdompatch

页面首次渲染执行updateComponent

data中每次修改属性,执行updataCommponent

vue的实现流程
第一步:解析模板成render函数

with的用法

模板中所有的信息都被render函数包含

模板中用到的data中的属性,都变成了js变量

模板中的v-modelv-ifv-on都变成了js逻辑

render函数返回vnode

第二步:响应式监听

Object.defineProperty

data属性代理到vm

第三步:首次渲染,显示页面,且绑定依赖

初次渲染,执行updateaComponent,执行vm._render()

执行render函数,会访问到vm.listvm.title

会被响应式的get方法监听到(为什么监听get?直接监听set不就行了吗?)

data中有很多属性,有些会被用到,有些可能不会被用到

被用到的会走到get,不被用到的不会走get

未走到get中的属性,set的时候我们也无需关系

避免不必要的重复渲染

执行updateComponent,会走到vdompatch方法

patchvnode渲染成DOM,初次渲染完成

第四步:data属性变化,触发rerender

属性修改,被响应式的set监听到

set中执行updateaComponetn

updateComponent重新执行vm._render()

生成的vnodeprevVnode,通过patch进行对比

渲染到html

// ## 组件化和React

Hybrid

移动端占大部分流量,已经远远超过pc

一线互联网公司都有自己的APP

这些APP中有很大比例的前端代码,拿微信举例,你每天浏览微信的内容,很多都是前端

hybrid是什么,为何用hybrid,如何实现
hybrid文字解释

hybrid即“混合”,即前端和客户端的混合开发

需前端开发人员和客户端人员配合完成

某些环节也可能涉及到server

存在价值,为何会用hybrid

可以快速迭代更新【关键】(无须app审核)

体验流畅(和NA的体验基本类似)

减少开发和沟通成本,双端公用一套代码

webview

是app中的一个组件(app可以有webview,也可以没有)

用于加载h5页面,即一个小型的浏览器内核

file://协议

其实在一开始接触html开发,已经使用file协议了

加载本地文件,快


网络加载,慢

“协议”、“标准的重要性”

要做到和原生一样的体验,就必须要求加载速度特别的快,{{BANNED}}的快,和客户端几乎一样的快

hybrid适用场景

使用NA:体验要求极致,变化不频繁(如头条的首页)

使用hybrid:体验要求高,变化频繁(如头条的新闻详情页)

使用h5:体验无要求,不常用(如举报,反馈等页面)

hybrid具体实现

前端做好静态页面(html js css),将文件交给客户端

客户端拿到前端静态页面,以文件的形式存储在app中

客户端在一个webview中

使用file协议加载静态页面

app发布之后,如何实时更新

分析

要替换每个客户端的静态文件

只能客户端来做(客户端是我们开发的)

客户端去serve下载最新的静态文件

我们维护server的静态文件

具体实现

分版本有版本号,如201803211015

将静态文件压缩成zip包,上传到服务端

客户端每次启动,都去服务端检查版本号

如果服务端版本号大于客户端版本号,就去下载最新的zip包

下载完之后解压包,然后将现有文件覆盖

hybridh5区别
优点:

体验更好,跟NA体验基本一致

可快速迭代,无须app审核【关键】

缺点:

开发成本高。联调、测试、查bug都比较麻烦

运维成本高。(参考此前讲过的更新上线的流程)

适用场景:

hybrid:产品的稳定功能,体验要求高,迭代频繁

h5:单次的运营活动(如xx红包)或不常用功能

hybrid适合产品型,h5适合运营型

JS和客户端通讯
前端如何获取内容

新闻详情页适用hybrid,前端如何获取新闻内容

不能用ajax获取。第一跨域(ajaxhttp协议),第二速度慢。

客户端获取新闻内容,然后JS通讯拿到内容,再渲染。

JS和客户端通讯的基本形式

schema协议简介和使用

之前介绍了http(s)和file协议

schema协议--前端和客户端通讯的约定

function invokScan() {
    window["_invok_scan_callback"] = function(result){
        alert(result)
    }
    var iframe = document.createElement("iframe")
    iframe.style.display = "none"
    iframe.src = "weixin://dl/scan?k1=v1&k1=v2&callback=_invok_scan_callback" //重要
    var body = document.body
    body.appendChild(iframe)
    setTimeout(() => {
        body.removeChild(iframe)
        iframe = none
    });
}
document.getElementById("btn").addEventListener("click", function() {
    invokScan();
})
schema使用的封装

我们希望封装后达到的效果:

/*傻瓜式调用,而且不用再自己定义全局函数 */
window.invok.share({title:"xxxx",content:"xxx"},funciton(result){
    if(result.error === 0){
        alert("分享成功")
    }else{
        //分享失败
        alert(result.message)
    }
})
//分享
function invokeShare(data,callback){
    _invoke("share",data,callback)
}
//登录
function invokeLogin(data,callback){
    _invoke("login",data,callback)
}
//打开扫一扫
function invokeScan(data,callback){
    _invoke("scan",data,callback)
}
//暴露给全局
window.invoke = {
    share:invokeShare,
    login:invokeLogin,
    scan:invokeScan
}
function _invoke(action,data,callback){
    //拼接schema协议
    var schema = "myapp://utils";
    scheam += "/" + action;
    scheam += "?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
(function(window, undefined) {

    //调用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 += "allback = callbackName"
    }

    //暴露到全局变量
    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)
内置上线

将以上封装的代码打包,叫做invoke.js,内置到客户端

客户端每次启动webview,都默认执行invoke.js

本地加载,没有网络请求,黑客看到不到schema协议,更安全

总结

通讯的基本形式:调用能力,传递参数,监听回调

对schema协议的理解和使用

调用schema代码的封装

内置上线:更快更安全

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100360.html

相关文章

  • 架构师之路

    摘要:因为用户不用在第一次进入应用时下载所有代码,用户能更快的看到页面并与之交互。译高阶函数利用和来编写更易维护的代码高阶函数可以帮助你增强你的,让你的代码更具有声明性。知道什么时候和怎样使用高阶函数是至关重要的。 Vue 折腾记 - (10) 给axios做个挺靠谱的封装(报错,鉴权,跳转,拦截,提示) 稍微改改都能直接拿来用~~~哟吼吼,哟吼吼..... 如何无痛降低 if else 面...

    NikoManiac 评论0 收藏0
  • javascript知识点

    摘要:模块化是随着前端技术的发展,前端代码爆炸式增长后,工程化所采取的必然措施。目前模块化的思想分为和。特别指出,事件不等同于异步,回调也不等同于异步。将会讨论安全的类型检测惰性载入函数冻结对象定时器等话题。 Vue.js 前后端同构方案之准备篇——代码优化 目前 Vue.js 的火爆不亚于当初的 React,本人对写代码有洁癖,代码也是艺术。此篇是准备篇,工欲善其事,必先利其器。我们先在代...

    Karrdy 评论0 收藏0
  • 前端开发-从入门到Offer - 收藏集 - 掘金

    摘要:一些知识点有哪些方法方法前端从入门菜鸟到实践老司机所需要的资料与指南合集前端掘金前端从入门菜鸟到实践老司机所需要的资料与指南合集归属于笔者的前端入门与最佳实践。 工欲善其事必先利其器-前端实习简历篇 - 掘金 有幸认识很多在大厂工作的学长,在春招正式开始前为我提供很多内部推荐的机会,非常感谢他们对我的帮助。现在就要去北京了,对第一份正式的实习工作也充满期待,也希望把自己遇到的一些问题和...

    sf_wangchong 评论0 收藏0
  • 笔记 - 收藏集 - 掘金

    摘要:目录如何用提高效率后端掘金经常有人说我应该学一门语言,比如之类,但是却不知道如何入门。本文将通过我是如何开发公司年会抽奖系统的后端掘金需求出现年会将近,而年会抽奖环节必不可少,但是抽奖系统却还没有。 云盘一个个倒下怎么办?无需编码,手把手教你搭建至尊私享云盘 - 工具资源 - 掘金微盘挂了,360倒了,百度云盘也立了Flag。能让我们在云端储存分享文件的服务越来越少了。 买一堆移动硬盘...

    Alex 评论0 收藏0
  • 前端最强面经汇总

    摘要:获取的对象范围方法获取的是最终应用在元素上的所有属性对象即使没有代码,也会把默认的祖宗八代都显示出来而只能获取元素属性中的样式。因此对于一个光秃秃的元素,方法返回对象中属性值如果有就是据我测试不同环境结果可能有差异而就是。 花了很长时间整理的前端面试资源,喜欢请大家不要吝啬star~ 别只收藏,点个赞,点个star再走哈~ 持续更新中……,可以关注下github 项目地址 https:...

    wangjuntytl 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<
nameageaddress
" + item.name + "" + item.age + "" + item.address + "