成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

前端面試之路三(javascript高級(jí)篇)

adam1q84 / 2644人閱讀

摘要:原型原型的實(shí)際應(yīng)用原型如何實(shí)現(xiàn)它的擴(kuò)展性原型的實(shí)際應(yīng)用和的簡(jiǎn)單使用是原型方法是原型方法是原型方法是原型方法如何使用原型空對(duì)象源碼中,這里的處理情況比較復(fù)雜。

原型

原型的實(shí)際應(yīng)用

原型如何實(shí)現(xiàn)它的擴(kuò)展性

原型的實(shí)際應(yīng)用
jquery和zepto的簡(jiǎn)單使用

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如何使用原型
//空對(duì)象
var zepto = {}

zepto.init = function(){
    //源碼中,這里的處理情況比較復(fù)雜。但因?yàn)槭潜敬沃皇轻槍?duì)原型,因此這里就弱化了
    var slice = Array.prototype.slice
    var dom = slice.call(document.querySelectorAll(selector))
    return zepto.Z(dom,selector)
}

//即使用zepto時(shí)候的$
var $ = function(selector){
    return zepto.init(selector)
}
//這就是構(gòu)造函數(shù)
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 
簡(jiǎn)單的zepto實(shí)現(xiàn)

myZepto.js實(shí)現(xiàn)

(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關(guān)鍵字,第一步就找到了構(gòu)造函數(shù)
    return new jQuery.fn.init(selector);
}

//定義構(gòu)造函數(shù)
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
原型的擴(kuò)展性
如何體現(xiàn)原型的擴(kuò)展性

總結(jié)zeptojquery原型的使用

插件機(jī)制

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

init.prototype = jQuery.fn;

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

因?yàn)橐獢U(kuò)展插件

//做一個(gè)簡(jiǎn)單的插件
$.fn.getNodeName = function(){
    return this[0].nodeName
}
好處

只有$會(huì)暴露在window全局變量上

將插件擴(kuò)展統(tǒng)一到$.fn.xxx這一接口,方便使用

異步 什么是單線(xiàn)程,和異步有什么關(guān)系

單線(xiàn)程:只有一個(gè)線(xiàn)程,同一時(shí)間只能做一件事,兩段JS不能同時(shí)執(zhí)行

原因:避免DOM渲染的沖突

解決方案:異步

為什么js只有一個(gè)線(xiàn)程:避免DOM渲染沖突

瀏覽器需要渲染DOM

JS可以修改DOM結(jié)構(gòu)

JS執(zhí)行的時(shí)候,瀏覽器DOM渲染會(huì)暫停

兩端JS也不能同時(shí)執(zhí)行(都修改DOM就沖突了)

webworker支持多線(xiàn)程,但是不能訪問(wèn)DOM

?

什么是event-loop

事件輪詢(xún),JS實(shí)現(xiàn)異步的具體解決方案

同步代碼,直接執(zhí)行(主線(xiàn)程)

異步函數(shù)先放在異步隊(duì)列中(任務(wù)隊(duì)列)

待同步函數(shù)執(zhí)行完畢,輪詢(xún)執(zhí)行異步隊(duì)列的函數(shù)

setTimeout(function(){
    console.log(1);
},100);              //100ms之后才放入異步隊(duì)列中,目前異步隊(duì)列是空的
setTimeout(function(){
    console.log(2);  //直接放入異步隊(duì)列
})
console.log(3)       //直接執(zhí)行

//執(zhí)行3之后,異步隊(duì)列中只有2,把2拿到主線(xiàn)程執(zhí)行
//2執(zhí)行完之后,異步隊(duì)列中并沒(méi)有任務(wù),所以一直輪詢(xún)異步隊(duì)列
//直到100ms之后1放入異步隊(duì)列,將1拿到主線(xiàn)程中執(zhí)行
$.ajax({
    url:"./data.json",
    success:function(){        //網(wǎng)絡(luò)請(qǐng)求成功就把success放入異步隊(duì)列
        console.log("a");
    }
})

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

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

//打印結(jié)果:
//d    //d   
//c    //c  
//a    //b   
//b    //a   

//真實(shí)環(huán)境不會(huì)出現(xiàn)dacb
解決方案存在的問(wèn)題

問(wèn)題一:沒(méi)按照書(shū)寫(xiě)方式執(zhí)行,可讀性差

問(wèn)題二:callback中不容易模塊化

是否用過(guò)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); //返回一個(gè)XHR對(duì)象

?

jQuery1.5之后

第一種寫(xiě)法:

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對(duì)象

第二種寫(xiě)法:

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

無(wú)法改變JS異步和單線(xiàn)程的本質(zhì)

只能從寫(xiě)法上杜絕callback這種形式

它是一種語(yǔ)法糖,但是解耦了代碼

很好的提現(xiàn):開(kāi)放封閉原則(對(duì)擴(kuò)展開(kāi)放對(duì)修改封閉)

使用jQuery Deferred

給出一段非常簡(jiǎn)單的代碼,使用setTimeout函數(shù)

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

wait();

新增需求:要在執(zhí)行完成之后進(jìn)行某些特別復(fù)雜的操作,代碼可能會(huì)很多,而且分好幾步

function waitHandle(){
    var dtd = $.Deferred();         //創(chuàng)建一個(gè)deferred對(duì)象
    
    var wait = function(dtd){       // 要求傳入一個(gè)deferred對(duì)象
        var task = function(){
            console.log("執(zhí)行完成");
            dtd.resolve();          //表示異步任務(wù)已完成
            //dtd.reject()          // 表示異步任務(wù)失敗或者出錯(cuò)
        };
        setTimeout(task,2000);
        return dtd;
    }
    //注意,這里已經(jīng)要有返回值
    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
總結(jié):dtdAPI可分成兩類(lèi),用意不同

第一類(lèi):dtd.resolve 、 dtd.reject

第二類(lèi):dtd.thendtd.done、dtd.fail

這兩類(lèi)應(yīng)該分開(kāi),否則后果嚴(yán)重!

可以在上面代碼中最后執(zhí)行dtd.reject()試一下后果

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


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

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

監(jiān)聽(tīng)式調(diào)用:只能被動(dòng)監(jiān)聽(tīng),不能干預(yù)promise的成功和失敗

可以jQuery1.5對(duì)ajax的改變舉例

說(shuō)明如何簡(jiǎn)單的封裝、使用deferred

說(shuō)明promise和Defrred的區(qū)別

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

Promise的基本使用和原理
基本語(yǔ)法回顧
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)
})
異常捕獲

規(guī)定:then只接受一個(gè)函數(shù),最后統(tǒng)一用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) {
    //最后統(tǒng)一捕獲異常
    console.log(ex)
})
多個(gè)串聯(lián)
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("第一個(gè)圖片加載完成", img1.width);
    return result2;   //重要
}).then(function(img2) {
    console.log("第二個(gè)圖片加載完成", img2.width);
}).catch(function(ex) {
    console.log(ex);
})

Promise.allPromise.race

Promise.all接收一個(gè)promise對(duì)象的數(shù)組

待全部完成后,統(tǒng)一執(zhí)行success

Promise.all([result1, result2]).then(datas => {
        //接收到的datas是一個(gè)數(shù)組,依次包含了多個(gè)promise返回的內(nèi)容
        console.log(datas[0]);
        console.log(datas[1]);
})

Promise.race接收一個(gè)包含多個(gè)promise對(duì)象的數(shù)組

只要有一個(gè)完成,就執(zhí)行success

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

Promise標(biāo)準(zhǔn)

狀態(tài)

三種狀態(tài):pending,fulfilled,rejected

初始狀態(tài):pending

pending變?yōu)?b>fulfilled,或者pending變?yōu)?b>rejected

狀態(tài)變化不可逆

then

promise必須實(shí)現(xiàn)then這個(gè)方法

then()必須接收兩個(gè)函數(shù)作為標(biāo)準(zhǔn)

then返回的必須是一個(gè)promise實(shí)例

沒(méi)有return就是默認(rèn)返回的result

Promise總結(jié)

基本語(yǔ)法

如何異常捕獲(Errorreject)

多個(gè)串聯(lián)-鏈?zhǔn)綀?zhí)行的好處

Promise.allPromise.race

Promise標(biāo)準(zhǔn) - 狀態(tài)變化、then函數(shù)

介紹一下async/await(和Promise的區(qū)別、聯(lián)系)

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

koa框架 generator替換async/await

解決的是異步執(zhí)行和編寫(xiě)邏輯

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

用法

使用await,函數(shù)后面必須用async標(biāo)識(shí)

await后面跟的是一個(gè)Promise實(shí)例

需要balbel-polyfill(兼容)

特點(diǎn)

使用了Promise,并沒(méi)有和Promise沖突

完全是同步的寫(xiě)法,再也沒(méi)有回調(diào)函數(shù)

但是:改變不了JS單線(xiàn)程、異步的本質(zhì)

總結(jié)一下當(dāng)前JS異步的方案 虛擬DOM vdom 是什么?為何會(huì)存在 vdom?
什么是vdom

virtual dom,虛擬DOM

用JS模擬DOM結(jié)構(gòu)

DOM變化的對(duì)比,放在JS層來(lái)做(圖靈完備語(yǔ)言:能實(shí)現(xiàn)各種邏輯的語(yǔ)言)

提高重繪性能

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,因?yàn)閏lass是js的保留字

瀏覽器最耗費(fèi)性能就是DOM操作,DOM操作非常昂貴
現(xiàn)在瀏覽器執(zhí)行JS速度非???br>這就是vdom存在的原因

一個(gè)需求場(chǎng)景
//將該數(shù)據(jù)展示成一個(gè)表格
//隨便修改一個(gè)信息,表格也隨著修改
[
    {
        name: "zhangsan",
        age: 20,
        address: "beijing"
    },
    {
        name: "lisi",
        age: 21,
        address: "shanghai"
    },
    {
        name: "wangwu",
        age: 22,
        address: "guangzhou"
    }
]
jQery實(shí)現(xiàn)
//渲染函數(shù)
funciton render(data) {
    //此處省略n行
}

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


//初始化時(shí)渲染
render(data)
//render函數(shù)具體寫(xiě)法
function render(data) {
    $container = $("#container");
    //清空現(xiàn)有內(nèi)容
    $container.html("");
    //拼接table
    var $table = $("")
    $table.append($(""))
    data.forEach(item => {
        $table.append($(""))
        $container.append($table)
    });

}

//只執(zhí)行了一次渲染,相對(duì)來(lái)說(shuō)還是比較高效的
//DOM渲染是最昂貴的,只能盡量避免渲染
遇到的問(wèn)題

DOM操作是“昂貴”的,JS運(yùn)行效率高

盡量減少DOM操作,而不是"推倒重來(lái)"(清空重置)

項(xiàng)目越復(fù)雜,影響就越嚴(yán)重

vdom可解決這個(gè)問(wèn)題

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

//瀏覽器默認(rèn)創(chuàng)建出來(lái)的DOM節(jié)點(diǎn),屬性是非常多的

//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如何應(yīng)用,核心API是什么
介紹snabbdom

一個(gè)實(shí)現(xiàn)vdom的庫(kù),vue升級(jí)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函數(shù)
{
    tar: "ul",
    attrs: {
        id: "list"
    },
    children: [
    {
        tag: "li",
        attrs: {
            className: "item",
            children: ["item1"]
        }
    },
    {
        tag: "li",
        attrs: {
            className: "item"
        },
        children: ["item2"]
        }
    ]
}
對(duì)應(yīng)的vnode
var vnode = h("ul#list", {}, [
    h("li.item", {}, "Item1"),
    h("li.item", {}, "Item")
])
patch函數(shù)
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)
})
  
重新實(shí)現(xiàn)前面的demo(用snabbdom實(shí)現(xiàn))
 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  //存儲(chǔ)當(dāng)前vnode結(jié)果

    }

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

h函數(shù)的用法

 h("<標(biāo)簽名>",{...屬性...},[...子元素...])
 
 h("<標(biāo)簽名>",{...屬性...},"...")

patch函數(shù)用法

patch(container,vnode) 

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

linux中的diff:找出兩個(gè)文件中的不同:

diff log1.txt log2.txt

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

git diff xxxx.js

網(wǎng)上的一些在線(xiàn)差異化網(wǎng)站

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

diff算法并不是vdom提出的概念,一直存在
現(xiàn)在應(yīng)用到vdom中,對(duì)比的是兩個(gè)虛擬dom

去繁就簡(jiǎn)

diff算法非常復(fù)雜,實(shí)現(xiàn)難度很大,源碼量很大

去繁就簡(jiǎn),講明白核心流程,不關(guān)心細(xì)節(jié)

面試官大部分也不清楚細(xì)節(jié),但是很關(guān)心核心流程

去繁就簡(jiǎn)之后,依然具有很大的挑戰(zhàn)性

vdom為何要使用diff

DOM操作是“昂貴”的,因此盡量減少DOM操作

找出本次DOM必須更新的節(jié)點(diǎn)來(lái)更新,其他的不更新

“找出”的過(guò)程,就需要diff算法

diff算法實(shí)現(xiàn)
diff實(shí)現(xiàn)的過(guò)程

patch(container,vnode)

如何用vnode生成真實(shí)的dom節(jié)點(diǎn)

 {
    tag: "ul",
    attrs: {
        id: "list"
    },
    children: [
        {
            tag: "li",
            attrs: {
                className: "item"
            },
            children:["item 1"]
        }
    ]
}
  • Item 1
簡(jiǎn)單實(shí)現(xiàn)算法
function createElement(vnode) {
    var tag = vnode.tag;
    var attrs = vnode.attrs || {};
    var children = vnode.children || [];
    if (!tag) {
        return null
    }
    //創(chuàng)建元素
    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"]
            }
        }
    ]
}
對(duì)比:
{
    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"]
            }
        }
    ]
}
 

簡(jiǎn)單實(shí)現(xiàn)
 function updateChildren(vnode, newVnode) {
    var children = vnode.children || [];
    var newChildren = newVnode.children || [];

    //遍歷現(xiàn)有的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)
        }
    });
}

節(jié)點(diǎn)新增和刪除

節(jié)點(diǎn)重新排序

節(jié)點(diǎn)屬性、樣式、事件綁定

如何積極壓榨性能

MVVM和Vue使用jQuery和使用框架的區(qū)別
數(shù)據(jù)與視圖的分離,解耦(封閉開(kāi)放原則)

jquery中數(shù)據(jù)與視圖混合在一起了,不符合開(kāi)放封閉原則

vue:通過(guò)Vue對(duì)象將數(shù)據(jù)和View完全分離開(kāi)來(lái)了

以數(shù)據(jù)驅(qū)動(dòng)視圖

jquery完全違背了這種理念,jquery直接修改視圖,直接操作DOM

vue對(duì)數(shù)據(jù)進(jìn)行操作不再需要引用相應(yīng)的DOM對(duì)象,通過(guò)Vue對(duì)象這個(gè)vm實(shí)現(xiàn)相互的綁定。以數(shù)據(jù)驅(qū)動(dòng)視圖,只關(guān)心數(shù)據(jù)變化,DOM操作被封裝

對(duì)MVVM的理解

MVC


MVVM

Model:數(shù)據(jù),模型

View:視圖、模板(視圖和模型是分離的)

ViewModel:連接Model和View

MVVM不算是創(chuàng)新,ViewModel算是一種微創(chuàng)新

是從mvc發(fā)展而來(lái),結(jié)合前端場(chǎng)景的創(chuàng)新

如何實(shí)現(xiàn)MVVM
Vue三要素

響應(yīng)式vue如何監(jiān)聽(tīng)到data的每個(gè)屬性變化

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

渲染vue的模板如何渲染成html`,以及渲染過(guò)程

vue中如何實(shí)現(xiàn)響應(yīng)式
什么是響應(yīng)式

修改data屬性之后,vue立刻監(jiān)聽(tīng)到(然后立刻渲染頁(yè)面)

data屬性被代理到vm上

Object.defineProperty

ES5中加入的API,所以Vue不支持低版本瀏覽器(因?yàn)榈桶姹緸g覽器不支持這個(gè)屬性)

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) //可以監(jiān)聽(tīng)到
    obj.name = "list"
模擬實(shí)現(xiàn)
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) {
    //命中閉包。新建一個(gè)函數(shù),保證key的獨(dú)立的作用域
    (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如何解析模板
模板是什么

本質(zhì):字符串;有邏輯,如v-if,if-if,嵌入JS變量...

與html格式很像,但有很大區(qū)別(靜態(tài)),最終還是要轉(zhuǎn)化為html顯示

模板最終必須轉(zhuǎn)換成JS代碼

1、因?yàn)橛羞壿?v-for,v-if),必須用JS才能實(shí)現(xiàn)(圖靈完備)

2、轉(zhuǎn)換為html渲染頁(yè)面,必須用JS才能實(shí)現(xiàn)

因此,模板最重要轉(zhuǎn)換成一個(gè)JS函數(shù)(render函數(shù))

render函數(shù)
先了解with()的使用
function fn1() {
    with(obj) {
        console.log(name);
        console.log(age);
        getAddress()
    }
}
最簡(jiǎn)單的一個(gè)示例

{{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函數(shù)中

thisvm

pricethis.price,即data中的price

_cthis._cvm._c

更復(fù)雜的一個(gè)例子
    
  • {{item}}
如何尋找render函數(shù):code.render

模板如何生成render函數(shù):

vue2.0開(kāi)始就支持預(yù)編譯,我們?cè)陂_(kāi)發(fā)環(huán)境下寫(xiě)模板,經(jīng)過(guò)編譯打包,產(chǎn)生生產(chǎn)環(huán)境的render函數(shù)(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是怎么實(shí)現(xiàn)的?

v-on:click是怎么實(shí)現(xiàn)的

v-for是怎么實(shí)現(xiàn)的

render函數(shù)與DOM

已經(jīng)解決了模板中"邏輯"(v-for,v-if)的問(wèn)題

還剩下模板生成html的問(wèn)題

另外,vm_c是什么?render函數(shù)返回了什么

vm._c其實(shí)就相當(dāng)于snabbdom中的h函數(shù)

render函數(shù)執(zhí)行之后,返回的是vnode

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

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

updateComponent中實(shí)現(xiàn)了vdompatch

頁(yè)面首次渲染執(zhí)行updateComponent

data中每次修改屬性,執(zhí)行updataCommponent

vue的實(shí)現(xiàn)流程
第一步:解析模板成render函數(shù)

with的用法

模板中所有的信息都被render函數(shù)包含

模板中用到的data中的屬性,都變成了js變量

模板中的v-modelv-if、v-on都變成了js邏輯

render函數(shù)返回vnode

第二步:響應(yīng)式監(jiān)聽(tīng)

Object.defineProperty

data屬性代理到vm

第三步:首次渲染,顯示頁(yè)面,且綁定依賴(lài)

初次渲染,執(zhí)行updateaComponent,執(zhí)行vm._render()

執(zhí)行render函數(shù),會(huì)訪問(wèn)到vm.listvm.title

會(huì)被響應(yīng)式的get方法監(jiān)聽(tīng)到(為什么監(jiān)聽(tīng)get?直接監(jiān)聽(tīng)set不就行了嗎?)

data中有很多屬性,有些會(huì)被用到,有些可能不會(huì)被用到

被用到的會(huì)走到get,不被用到的不會(huì)走get

未走到get中的屬性,set的時(shí)候我們也無(wú)需關(guān)系

避免不必要的重復(fù)渲染

執(zhí)行updateComponent,會(huì)走到vdompatch方法

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

第四步:data屬性變化,觸發(fā)rerender

屬性修改,被響應(yīng)式的set監(jiān)聽(tīng)到

set中執(zhí)行updateaComponetn

updateComponent重新執(zhí)行vm._render()

生成的vnodeprevVnode,通過(guò)patch進(jìn)行對(duì)比

渲染到html

// ## 組件化和React

Hybrid

移動(dòng)端占大部分流量,已經(jīng)遠(yuǎn)遠(yuǎn)超過(guò)pc

一線(xiàn)互聯(lián)網(wǎng)公司都有自己的APP

這些APP中有很大比例的前端代碼,拿微信舉例,你每天瀏覽微信的內(nèi)容,很多都是前端

hybrid是什么,為何用hybrid,如何實(shí)現(xiàn)
hybrid文字解釋

hybrid即“混合”,即前端和客戶(hù)端的混合開(kāi)發(fā)

需前端開(kāi)發(fā)人員和客戶(hù)端人員配合完成

某些環(huán)節(jié)也可能涉及到server

存在價(jià)值,為何會(huì)用hybrid

可以快速迭代更新【關(guān)鍵】(無(wú)須app審核)

體驗(yàn)流暢(和NA的體驗(yàn)基本類(lèi)似)

減少開(kāi)發(fā)和溝通成本,雙端公用一套代碼

webview

是app中的一個(gè)組件(app可以有webview,也可以沒(méi)有)

用于加載h5頁(yè)面,即一個(gè)小型的瀏覽器內(nèi)核

file://協(xié)議

其實(shí)在一開(kāi)始接觸html開(kāi)發(fā),已經(jīng)使用file協(xié)議了

加載本地文件,快


網(wǎng)絡(luò)加載,慢

“協(xié)議”、“標(biāo)準(zhǔn)的重要性”

要做到和原生一樣的體驗(yàn),就必須要求加載速度特別的快,{{BANNED}}的快,和客戶(hù)端幾乎一樣的快

hybrid適用場(chǎng)景

使用NA:體驗(yàn)要求極致,變化不頻繁(如頭條的首頁(yè))

使用hybrid:體驗(yàn)要求高,變化頻繁(如頭條的新聞詳情頁(yè))

使用h5:體驗(yàn)無(wú)要求,不常用(如舉報(bào),反饋等頁(yè)面)

hybrid具體實(shí)現(xiàn)

前端做好靜態(tài)頁(yè)面(html js css),將文件交給客戶(hù)端

客戶(hù)端拿到前端靜態(tài)頁(yè)面,以文件的形式存儲(chǔ)在app中

客戶(hù)端在一個(gè)webview中

使用file協(xié)議加載靜態(tài)頁(yè)面

app發(fā)布之后,如何實(shí)時(shí)更新

分析

要替換每個(gè)客戶(hù)端的靜態(tài)文件

只能客戶(hù)端來(lái)做(客戶(hù)端是我們開(kāi)發(fā)的)

客戶(hù)端去serve下載最新的靜態(tài)文件

我們維護(hù)server的靜態(tài)文件

具體實(shí)現(xiàn)

分版本有版本號(hào),如201803211015

將靜態(tài)文件壓縮成zip包,上傳到服務(wù)端

客戶(hù)端每次啟動(dòng),都去服務(wù)端檢查版本號(hào)

如果服務(wù)端版本號(hào)大于客戶(hù)端版本號(hào),就去下載最新的zip包

下載完之后解壓包,然后將現(xiàn)有文件覆蓋

hybridh5區(qū)別
優(yōu)點(diǎn):

體驗(yàn)更好,跟NA體驗(yàn)基本一致

可快速迭代,無(wú)須app審核【關(guān)鍵】

缺點(diǎn):

開(kāi)發(fā)成本高。聯(lián)調(diào)、測(cè)試、查bug都比較麻煩

運(yùn)維成本高。(參考此前講過(guò)的更新上線(xiàn)的流程)

適用場(chǎng)景:

hybrid:產(chǎn)品的穩(wěn)定功能,體驗(yàn)要求高,迭代頻繁

h5:?jiǎn)未蔚倪\(yùn)營(yíng)活動(dòng)(如xx紅包)或不常用功能

hybrid適合產(chǎn)品型,h5適合運(yùn)營(yíng)型

JS和客戶(hù)端通訊
前端如何獲取內(nèi)容

新聞詳情頁(yè)適用hybrid,前端如何獲取新聞內(nèi)容

不能用ajax獲取。第一跨域(ajaxhttp協(xié)議),第二速度慢。

客戶(hù)端獲取新聞內(nèi)容,然后JS通訊拿到內(nèi)容,再渲染。

JS和客戶(hù)端通訊的基本形式

schema協(xié)議簡(jiǎn)介和使用

之前介紹了http(s)和file協(xié)議

schema協(xié)議--前端和客戶(hù)端通訊的約定

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使用的封裝

我們希望封裝后達(dá)到的效果:

/*傻瓜式調(diào)用,而且不用再自己定義全局函數(shù) */
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)
}
//打開(kāi)掃一掃
function invokeScan(data,callback){
    _invoke("scan",data,callback)
}
//暴露給全局
window.invoke = {
    share:invokeShare,
    login:invokeLogin,
    scan:invokeScan
}
function _invoke(action,data,callback){
    //拼接schema協(xié)議
    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) {

    //調(diào)用schema封裝
    function _invoke(action, data, callback) {
        //拼裝schema協(xié)議
        var schema = "myapp://utils/" + action;
        //拼接參數(shù)
        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)
內(nèi)置上線(xiàn)

將以上封裝的代碼打包,叫做invoke.js,內(nèi)置到客戶(hù)端

客戶(hù)端每次啟動(dòng)webview,都默認(rèn)執(zhí)行invoke.js

本地加載,沒(méi)有網(wǎng)絡(luò)請(qǐng)求,黑客看到不到schema協(xié)議,更安全

總結(jié)

通訊的基本形式:調(diào)用能力,傳遞參數(shù),監(jiān)聽(tīng)回調(diào)

對(duì)schema協(xié)議的理解和使用

調(diào)用schema代碼的封裝

內(nèi)置上線(xiàn):更快更安全

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/100360.html

相關(guān)文章

  • 架構(gòu)師之路

    摘要:因?yàn)橛脩?hù)不用在第一次進(jìn)入應(yīng)用時(shí)下載所有代碼,用戶(hù)能更快的看到頁(yè)面并與之交互。譯高階函數(shù)利用和來(lái)編寫(xiě)更易維護(hù)的代碼高階函數(shù)可以幫助你增強(qiáng)你的,讓你的代碼更具有聲明性。知道什么時(shí)候和怎樣使用高階函數(shù)是至關(guān)重要的。 Vue 折騰記 - (10) 給axios做個(gè)挺靠譜的封裝(報(bào)錯(cuò),鑒權(quán),跳轉(zhuǎn),攔截,提示) 稍微改改都能直接拿來(lái)用~~~喲吼吼,喲吼吼..... 如何無(wú)痛降低 if else 面...

    NikoManiac 評(píng)論0 收藏0
  • javascript知識(shí)點(diǎn)

    摘要:模塊化是隨著前端技術(shù)的發(fā)展,前端代碼爆炸式增長(zhǎng)后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調(diào)也不等同于異步。將會(huì)討論安全的類(lèi)型檢測(cè)惰性載入函數(shù)凍結(jié)對(duì)象定時(shí)器等話(huà)題。 Vue.js 前后端同構(gòu)方案之準(zhǔn)備篇——代碼優(yōu)化 目前 Vue.js 的火爆不亞于當(dāng)初的 React,本人對(duì)寫(xiě)代碼有潔癖,代碼也是藝術(shù)。此篇是準(zhǔn)備篇,工欲善其事,必先利其器。我們先在代...

    Karrdy 評(píng)論0 收藏0
  • 前端開(kāi)發(fā)-從入門(mén)到Offer - 收藏集 - 掘金

    摘要:一些知識(shí)點(diǎn)有哪些方法方法前端從入門(mén)菜鳥(niǎo)到實(shí)踐老司機(jī)所需要的資料與指南合集前端掘金前端從入門(mén)菜鳥(niǎo)到實(shí)踐老司機(jī)所需要的資料與指南合集歸屬于筆者的前端入門(mén)與最佳實(shí)踐。 工欲善其事必先利其器-前端實(shí)習(xí)簡(jiǎn)歷篇 - 掘金 有幸認(rèn)識(shí)很多在大廠工作的學(xué)長(zhǎng),在春招正式開(kāi)始前為我提供很多內(nèi)部推薦的機(jī)會(huì),非常感謝他們對(duì)我的幫助?,F(xiàn)在就要去北京了,對(duì)第一份正式的實(shí)習(xí)工作也充滿(mǎn)期待,也希望把自己遇到的一些問(wèn)題和...

    sf_wangchong 評(píng)論0 收藏0
  • 筆記 - 收藏集 - 掘金

    摘要:目錄如何用提高效率后端掘金經(jīng)常有人說(shuō)我應(yīng)該學(xué)一門(mén)語(yǔ)言,比如之類(lèi),但是卻不知道如何入門(mén)。本文將通過(guò)我是如何開(kāi)發(fā)公司年會(huì)抽獎(jiǎng)系統(tǒng)的后端掘金需求出現(xiàn)年會(huì)將近,而年會(huì)抽獎(jiǎng)環(huán)節(jié)必不可少,但是抽獎(jiǎng)系統(tǒng)卻還沒(méi)有。 云盤(pán)一個(gè)個(gè)倒下怎么辦?無(wú)需編碼,手把手教你搭建至尊私享云盤(pán) - 工具資源 - 掘金微盤(pán)掛了,360倒了,百度云盤(pán)也立了Flag。能讓我們?cè)谠贫藘?chǔ)存分享文件的服務(wù)越來(lái)越少了。 買(mǎi)一堆移動(dòng)硬盤(pán)...

    Alex 評(píng)論0 收藏0
  • 前端最強(qiáng)面經(jīng)匯總

    摘要:獲取的對(duì)象范圍方法獲取的是最終應(yīng)用在元素上的所有屬性對(duì)象即使沒(méi)有代碼,也會(huì)把默認(rèn)的祖宗八代都顯示出來(lái)而只能獲取元素屬性中的樣式。因此對(duì)于一個(gè)光禿禿的元素,方法返回對(duì)象中屬性值如果有就是據(jù)我測(cè)試不同環(huán)境結(jié)果可能有差異而就是。 花了很長(zhǎng)時(shí)間整理的前端面試資源,喜歡請(qǐng)大家不要吝嗇star~ 別只收藏,點(diǎn)個(gè)贊,點(diǎn)個(gè)star再走哈~ 持續(xù)更新中……,可以關(guān)注下github 項(xiàng)目地址 https:...

    wangjuntytl 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看

            
            <
            nameageaddress
            " + item.name + "" + item.age + "" + item.address + "