前端基本功-常見概念(一) 點這裏
前端基本功-常見概念(二) 點這裏
前端基本功-常見概念(三) 點這裏javascript
let 是更完美的var,不是全局變量,具備塊級函數做用域,大多數狀況不會發生變量提高。
const 定義常量值,不可以從新賦值,若是值是一個對象,能夠改變對象裏邊的屬性值
var存在的問題html
let、const特性前端
var a = [] for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i) } } a[5]() // 10
在上述代碼中,變量i是var聲明的,在全局範圍類都有效。因此每一次循環,新的i值都會覆蓋舊值,致使最後輸出都是10
而若是對循環使用let語句的狀況,那麼每次迭代都是爲x建立新的綁定代碼以下vue
var a = [] for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i) } } a[5]() // 5
function showNum(i) { return function () { console.log(i) } } var a = [] for (var i = 0; i < 5; i++) { a[i] = showNum(i) }
var a = [] for (var i = 0; i < 5; i++) { a[i] = (function (i) { return function () { console.log(i) } })(i) } a[2]()
本節參考文章:前端面試之ES6篇java
在討論重排(迴流)與重繪以前,咱們要知道:node
HTML
成DOM
,把CSS
解析成CSSOM
,DOM
和CSSOM
合併就產生了Render Tree
。RenderTree
,咱們就知道了全部節點的樣式,而後計算他們在頁面上的大小和位置,最後把節點繪製到頁面上。Render Tree
的計算一般只須要遍歷一次就能夠完成,但table
及其內部元素除外,他們可能須要屢次計算,一般要花3倍於同等元素的時間,這也是爲何要避免使用table
佈局的緣由之一。一句話:重排必將引發重繪,重繪不必定會引發重排。
當Render Tree中部分或所有元素的尺寸、結構、或某些屬性發生改變時,瀏覽器從新渲染部分或所有文檔的過程稱爲重排。jquery
會致使重排的操做:git
一些經常使用且會致使重排的屬性和方法:github
當頁面中元素樣式的改變並不影響它在文檔流中的位置時(例如:color、background-color、visibility等),瀏覽器會將新樣式賦予給元素並從新繪製它,這個過程稱爲重繪。面試
迴流比重繪的代價要更高。
有時即便僅僅迴流一個單一的元素,它的父元素以及任何跟隨它的元素也會產生迴流。
現代瀏覽器會對頻繁的迴流或重繪操做進行優化:
瀏覽器會維護一個隊列,把全部引發迴流和重繪的操做放入隊列中,若是隊列中的任務數量或者時間間隔達到一個閾值的,瀏覽器就會將隊列清空,進行一次批處理,這樣能夠把屢次迴流和重繪變成一次。
當你訪問如下屬性或方法時,瀏覽器會馬上清空隊列:
由於隊列中可能會有影響到這些屬性或方法返回值的操做,即便你但願獲取的信息與隊列中操做引起的改變無關,瀏覽器也會強行清空隊列,確保你拿到的值是最精確的。
CSS
JavaScript
本節參考文章:[瀏覽器的迴流與重繪 (Reflow & Repaint)](https://juejin.im/post/5a9923e9518825558251c96a)
瀏覽器緩存分爲強緩存和協商緩存,優先讀取強制緩存。
當客戶端請求某個資源時,獲取緩存的流程以下:
協商緩存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】這兩對Header來管理的
Last-Modified 表示本地文件最後修改日期,瀏覽器會在request header加上If-Modified-Since(上次返回的Last-Modified的值),詢問服務器在該日期後資源是否有更新,有更新的話就會將新的資源發送回來
可是若是在本地打開緩存文件,就會形成 Last-Modified 被修改,因此在 HTTP / 1.1 出現了 ETag
Etag就像一個指紋,資源變化都會致使ETag變化,跟最後修改時間沒有關係,ETag能夠保證每個資源是惟一的
If-None-Match的header會將上次返回的Etag發送給服務器,詢問該資源的Etag是否有更新,有變更就會發送新的資源回來
ETag的優先級比Last-Modified更高
推薦必讀:http協商緩存VS強緩存、瀏覽器緩存知識小結及應用、緩存(二)——瀏覽器緩存機制:強緩存、協商緩存
JavaScript中的內存分爲棧內存和堆內存。棧內存和堆內存區別:棧內存運行效率比堆內存高,空間相對堆內存來講較小。
區別:
token和cookie舉例,token就是說你告訴我你是誰就能夠。
cookie 舉例:服務員看你的身份證,給你一個編號,之後,進行任何操做,都出示編號後服務員去看查你是誰。token 舉例:直接給服務員看本身身份證,服務器不須要去查看你是誰,不須要保存你的會話。
小結:
Token 徹底由應用管理,因此它能夠避開同源策略
Token 能夠避免 CSRF 攻擊
Token 能夠是無狀態的,能夠在多個服務間共享
若是你的用戶數據可能須要和第三方共享,或者容許第三方調用 API 接口,用 Token,若是之上本身的那就無所謂了。
本節參考文章:cookie和token的五點區別
推薦必讀:先後端常見的幾種鑑權方式
cookie分爲cookie機制和session機制(請大神判斷正確性)
Session: 是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據能夠保存在集羣、數據庫、文件中,經過在服務器端記錄信息肯定用戶身份
Cookie: 是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式,經過在客戶端記錄信息肯定用戶身份
若是說Cookie機制是經過檢查客戶身上的「通行證」來肯定客戶身份的話,那麼Session機制就是經過檢查服務器上的「客戶明細表」來確認客戶身份。Session至關於程序在服務器上創建的一份客戶檔案,客戶來訪的時候只須要查詢客戶檔案表就能夠了。
cookie機制
cookie能夠經過設置domain屬性值,能夠不一樣二級域名下共享cookie,而Storage不能夠,好比http://image.baidu.com
的cookie http://map.baidu.com
是能夠訪問的,前提是Cookie的domain設置爲.http://baidu.com
,而Storage是不能夠的
session機制
當程序須要爲某個客戶端的請求建立一個session時,
比較
Cookie 以名/值對形式存儲
例如username=John Doe,這裏的數據是string類型,如要是其餘格式注意進行格式轉換。
JavaScript 可使用 document.cookie 屬性來建立 、讀取、及刪除 cookie。JavaScript 中,建立 cookie 以下所示:
document.cookie="username=John Doe";
您還能夠爲 cookie 添加一個過時時間(以 UTC 或 GMT 時間)。默認狀況下,cookie 在瀏覽器關閉時刪除:
document.cookie="username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT";
您可使用 path 參數告訴瀏覽器 cookie 的路徑。默認狀況下,cookie 屬於當前頁面。
document.cookie="username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT; path=/";
設置cookie
function setCookie(cname,cvalue,exdays){ var SetTime = new Date(); //設置過時時間 SetTime.setTime(SetTime.getTime()+(exdays*24*60*60*1000)); //設置過時時間 var expires = "expires="+SetTime.toGMTString(); //設置過時時間 document.cookie = cname + "=" + cvalue + "; " + expires; //建立一個cookie }
讀取cookie
function getCookie(c_name){ if (document.cookie.length>0) { c_start=document.cookie.indexOf(c_name + "=") if (c_start!=-1){ c_start=c_start + c_name.length+1 c_end=document.cookie.indexOf(";",c_start) if (c_end==-1) c_end=document.cookie.length return unescape(document.cookie.substring(c_start,c_end)) } } return "" }
刪除cookie
將cookie的有效時間改爲昨天。
添加/修改cookie並設定過時時間:
$.cookies.set('cookie_id', 'cookie_value', { hoursToLive: 10 });
這裏設置的是過時時間是10小時, 還能夠這樣設置過時時間:
expireDate = new Date(); expireDate.setTime( expireDate.getTime() + ( 10 * 60 * 60 * 1000 ) ); $.cookies.set('cookie_id', 'cookie_value', {expiresAt:expireDate});
獲取cookie
$.cookies.get('cookie_id');
刪除cookie
$.cookies.del('cookie_id');
大小:官方建議是5M存儲空間
類型:只能操做字符串,在存儲以前應該使用JSON.stringfy()方法先進行一步安全轉換字符串,取值時再用JSON.parse()方法再轉換一次
存儲的內容: 數組,圖片,json,樣式,腳本。。。(只要是能序列化成字符串的內容均可以存儲)
區別:sessionStorage將數據臨時存儲在session中,瀏覽器關閉,數據隨之消失,localStorage將數據存儲在本地,理論上來講數據永遠不會消失,除非人爲刪除
注意:數據是明文存儲,毫無隱私性可言,絕對不能用於存儲
保存數據
localStorage.setItem( key, value ); sessionStorage.setItem(keyName,value); // 將value存儲到key字段中 //或者 sessionStorage.keyName='value';
讀取數據
localStorage.getItem( key ); sessionStorage.getItem(keyName); //獲取指定key的本地存儲的值 //或者 var keyName=sessionStorage.key;
刪除單個數據
localStorage.removeItem( key ); sessionStorage.removeItem( key );
刪除所有數據
localStorage.clear( ); sessionStorage.clear( );
獲取索引的key
localStorage.key( index ); sessionStorage.key( index );
能夠經過監聽 window 對象的 storage 事件並指定其事件處理函數,當頁面中對 localStorage 或 sessionStorage 進行修改時,則會觸發對應的處理函數
window.addEventListener('storage',function(e){ console.log('key='+e.key+',oldValue='+e.oldValue+',newValue='+e.newValue); })
localstorage是瀏覽器多個標籤共用的存儲空間,能夠用來實現多標籤之間的通訊(ps:session是會話級的存儲空間,每一個標籤頁都是單獨的
觸發事件的時間對象(e 參數值)有幾個屬性:
key : 鍵值。
oldValue : 被修改前的值。
newValue : 被修改後的值。
url : 頁面url。
storageArea : 被修改的 storage 對象。
共同點:都是保存在瀏覽器端、且同源的,都受同源策略的制約。
區別:
本節參考文章:緩存(三)——數據存儲...
其餘閱讀:關於Cookie、session和Web Storage
事件指能夠被 JavaScript 偵測到的行爲。即鼠標點擊、頁面或圖像載入、鼠標懸浮於頁面的某個熱點之上、在表單中選取輸入框、確認表單、鍵盤按鍵等操做。
事件一般與函數配合使用,當事件發生時函數纔會執行。
事件名稱:click/mouseover/blur("不帶on")
事件處理程序(事件偵聽器):響應某個事件的函數,名稱爲:onclick/onmouseove/onblur,例如<button onclick="alert('hello')"></button>
冒泡:往上
捕獲:向下
事件流指從頁面中接收事件的順序,也可理解爲事件在頁面中傳播的順序。
DOM2級事件規定的事件流包括三個階段:
(1)事件捕獲階段(2)處於目標階段(3)事件冒泡階段。
當事件發生時,最早獲得通知的是window,而後是document,由上至下逐級依次而入,直到真正觸發事件的那個元素(目標元素)爲止,這個過程就是捕獲。
接下來,事件會從目標元素開始起泡,由下至上逐級依次傳播,直到window對象爲止,這個過程就是冒泡。
因此捕獲比冒泡先執行。
但願註冊在DOM元素上的事件處理程序在捕獲階段仍是在冒泡階段觸發,取決於 addEventListener() 方法的第三個參數爲 true 仍是 false 。
其中DOM3級事件在DOM2的基礎之上添加了更多的事件類型。
描述DOM事件捕獲的具體流程
window-->document-->html(document.documentElement)-->body(document.body)...
DOM級別一共能夠分爲4個級別:DOM0級,DOM1級,DOM2級和DOM3級,
而DOM事件分爲3個級別:DOM0級事件處理,DOM2級事件處理和DOM3級事件處理。
其中1級DOM標準中並無定義事件相關的內容,因此沒有所謂的1級DOM事件模型。
DOM0:element.onclick = function(){}
DOM2:element.addEventlistenter('click',function(){},flase)
DOM3:element.addEventlistenter('keyup',function(){},flase)
HTML事件處理程序
<button onclick="alert('hello')"></button> <button onclick="doSomething()"></button> <button onclick="try{doSomething();}catch(err){}"></button>
DOM0級事件處理程序
btn.onclick=function(){ alert("hello"); }
btn.onclick = null;//來刪除指定的事件處理程序。
若是咱們嘗試給事件添加兩個事件,如:
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.onclick=function(){ alert("hello"); } btn.onclick=function(){ alert("hello again"); } </script>
輸出,hello again,很明顯,第一個事件函數被第二個事件函數給覆蓋掉了, 因此,DOM0級事件處理程序不能添加多個,也不能控制事件流究竟是捕獲仍是冒泡。
DOM2級事件處理程序
addEventListener() ---添加事件偵聽器
函數均有3個參數,
第一個參數是要處理的事件名(不帶on前綴的纔是事件名)
第二個參數是做爲事件處理程序的函數
第三個參數是一個boolean值,默認false表示使用冒泡機制,true表示捕獲機制。
<button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.addEventListener('click',hello,false); btn.addEventListener('click',helloagain,false); function hello(){ alert("hello"); } function helloagain(){ alert("hello again"); } </script>
removeEventListener() //刪除事件偵聽器
能夠綁定多個事件處理程序,可是注意,若是定義了一摸同樣時監聽方法,是會發生覆蓋的,即一樣的事件和事件流機制下相同方法只會觸發一次,事件觸發的順序是添加的順序
``` // 爲了兼容IE瀏覽器和標準的瀏覽器,咱們須要編寫通用的方法來處理: var EventUtil = { addHandler: function (element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, removeHandler: function (element, type, handler) { if (element.removeEventListener()) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } } }; ```
事件對象是用來記錄一些事件發生時的相關信息的對象,但事件對象只有事件發生時纔會產生,而且只能是事件處理函數內部訪問,在全部事件處理函數運行結束後,事件對象就被銷燬!
//currentTarget、eventPhase 一個例子: <button id="btn">點擊</button> <script> var btn=document.getElementById("btn"); btn.ddEventListener('click', doCurrent, true); // 判斷事件的屬性 function doCurrent(event) { //獲取當前事件觸發的div var target = event.currentTarget; //經過判斷事件的event.eventPhase屬性返回事件傳播的當前階段 //1:捕獲階段、2:正常事件派發和3:起泡階段。 //獲得當前階段和id值並輸出 var msg = (event.eventPhase == 1 ? '捕獲階段:' : '冒泡階段:')+ target.attributes["id"].value;; console.log(msg); } </script>
固然,事件對象也存在必定的兼容性問題,在IE8及之前本版之中,經過設置屬性註冊事件處理程序時,調用的時候並未傳遞事件對象,須要經過全局對象window.event來獲取。解決方法以下:
function getEvent(event) { event = event || window.event; }
在IE瀏覽器上面是event事件是沒有preventDefault()這個屬性的,因此在IE上,咱們須要設置的屬性是returnValue
window.event.returnValue=false
stopPropagation()也是,因此須要設置cancelBubble,cancelBubble是IE事件對象的一個屬性,設置這個屬性爲true能阻止事件進一步傳播。
event.cancelBubble=true
常見屬性 | 解析 |
---|---|
event.preventDefault() | 阻止默認行爲 |
event.stopPropagation() | 阻止冒泡。使用了stopPropagation()以後,事件就不能進一步傳播了,同時若是是同一個div上有捕獲和冒泡兩種事件監聽,在捕獲階段傳播阻止後冒泡階段事件監聽也不會觸發。 |
event.stopImmediatePropagation() | 使用了stopImmediatePropagation()以後,綁定的後續事件監聽都會忽略。 |
event.currentTarget | 當前綁定的事件 |
event.target | 事件代理時 點擊的元素 |
關於捕獲和冒泡:理解 addEventListener、捕獲和冒泡
jq
// 添加一個適當的事件監聽器 $('#foo').on('custom', function(event, param1, param2) { alert(param1 + "\n" + param2); }); $('#foo').trigger('custom', ['Custom', 'Event']);
原生Event:
var eve = new Event('custome') element.addEventListenter('custome',function(){ console.log('custome') }) element.dispatchEvent(eve)
原生CustomEvent
// 添加一個適當的事件監聽器 obj.addEventListener("custom", function(e) { console.log(JSON.stringify(e.detail)); }) // 建立並分發事件 var event = new CustomEvent("custom", {"detail":{"Custom":true}}); obj.dispatchEvent(event)
本節參考文章:一個能拖動,能調整大小,能更新bind值的vue指令-vuedragx
事件委託就是利用事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。
案例一:
<button id="btnAdd">添加</button> <ul id="ulList"> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var btnAdd = document.getElementById('btnAdd'); var ulList = document.getElementById('ulList'); var list = document.getElementsByTagName('li'); var num = 3; btnAdd.onclick = function () { num++; var li = document.createElement('li'); li.innerHTML = num; ulList.appendChild(li) } for (i = 0; i < list.length; i++) { list[i].onclick = function(){ alert(this.innerHTML); } } </script> //例子說明,咱們爲ul添加新的li, //其中對li標籤元素綁定了click事件, //可是發現,後增長的元素沒有辦法觸發咱們的click事件。
解決方法:事件委託
<button id="btnAdd">添加</button> <ul id="ulList"> <li class="class-1">1</li> <li class="class-1">2</li> <li class="class-1">3</li> </ul> <script> var btnAdd = document.getElementById('btnAdd'); var ulList = document.getElementById('ulList'); var num = 3; ulList.onclick = function(event){ var event = event || window.event; var target = event.target || event.srcElement; // if (target.matches('li.class-1')) //#ulList 元素下的 li 元素(而且它的 class 爲 class-1) if(target.nodeName.toLowerCase() == 'li'){ alert(target.innerHTML); } } btnAdd.onclick = function () { num++; var li = document.createElement('li'); li.innerHTML = num; ulList.appendChild(li); } </script>
案例二:
點擊「添加」按鈕添加一個按鈕,點擊添加的按鈕移除這個按鈕
<div class="wrap" id="wrap"> <div class="btn" data-type="btn" data-feat="add">添加</div> <div class="btn" data-type="btn" data-feat="delete">繪畫</div> <div class="btn" data-type="btn" data-feat="delete">散步</div> <div class="btn" data-type="btn" data-feat="delete">靜坐</div> </div>
document.getElementById('wrap').addEventListener('click', function(e){ var target = e.target; while(target !== this){ var type = target.dataset.type; if(type == 'btn'){ var feat = target.dataset.feat; switch(feat){ case 'add': this.innerHTML += '<div class="btn" data-type="btn" data-feat="delete">靜坐</div>' return; case 'delete': target.parentNode.removeChild(target); return; } } target = target.parentNode; } }, false);
適合用事件委託的事件:click,mousedown,mouseup,keydown,keyup,keypress。
推薦閱讀:JavaScript 事件委託詳解
本節參考文章:前端小知識--JavaScript事件流
二者都是外部引用 CSS 的方式,可是存在必定的區別:
本節參考文章:前端面試題-url、href、src
document.write只能重繪整個頁面
innerHTML能夠重繪頁面的一部分
isPrototypeOf() 與 instanceof 運算符不一樣。
function Foo() {} function Bar() {} function Baz() {} Bar.prototype = Object.create(Foo.prototype); Baz.prototype = Object.create(Bar.prototype); var baz = new Baz(); //isPrototypeOf console.log(Baz.prototype.isPrototypeOf(baz)); // true console.log(Bar.prototype.isPrototypeOf(baz)); // true console.log(Foo.prototype.isPrototypeOf(baz)); // true console.log(Object.prototype.isPrototypeOf(baz)); // true if (Foo.prototype.isPrototypeOf(baz)) { // do something safe } //instanceof console.log(baz instanceof Baz); // true console.log(baz instanceof Bar); // true console.log(baz instanceof Foo); // true console.log(baz instanceof Object); // true
var obj1 = { name: 'esw' } var obj2 = Object.create(obj1) // isPrototypeOf()方法 Object.prototype.isPrototypeOf(obj1) // true obj1.isPrototypeOf(obj2) // true Object.prototype.isPrototypeOf(obj2) // true
在javascript中咱們每建立一個對象,該對象都會得到一個__proto__
屬性(該屬性是個對象),該屬性指向建立該對象的構造函數的原型
即prototype
,同時__proto__
對象有一個constructor
屬性指向該構造函數。這裏咱們須要注意的是隻有函數纔有prototype
,每一個對象(函數也是對象)都有__proto__
,Object
自己是個構造函數。舉例來講:
var obj = new Object() // 也可使用對象字面量建立,但使用Object.create()狀況會不同 // Object自己是個構造函數 Object instanceof Function // true obj.__proto__ === Object.prototype // true obj.__proto__.constructor === Object // true // 咱們通常習慣這樣寫 obj.constructor === Object // true
當咱們訪問obj.constructor
的時候,obj
自己是沒有constructor
屬性的,但屬性訪問會沿着__proto__
向上查找,即在obj.__proto__
裏面尋找constructor
屬性,若是找到了就返回值,若是未找到則繼續向上查找直到obj.__proto__.__proto__...(__proto__) === null
爲止,沒有找到則返回undefined。這樣由__proto__
構成的一條查找屬性的線稱爲‘原型鏈’。
本節參考文章:從新認識javascript對象(三)——原型及原型鏈、一篇文章帶你進一步瞭解object屬性
1.淺拷貝只能複製值類型的屬性。對於引用類型的屬性,複製先後的兩個對象指向同一內存地址,操做其中一個對象的引用類型屬性,另外一個對象也會相應發生改變;也就是說只有改變值類型的屬性兩個對象纔不會相互影響。
2.深拷貝不只能夠複製值類型的屬性,還能夠複製引用類型的屬性,不管兩個對象怎麼改變都不會相互影響。
淺複製
var obj = { a : 1, b: { c: 2 } } // 淺複製 function lowerClone(obj){ var newObj=obj.constructor === Array ? [] : {}; for(var i in obj){ newObj[i]=obj[i] } return newObj; } var objClone = lowerClone(obj) objClone.a // 1 obj.a // 1 objClone.a = 100 // 改變複製對象的值類型屬性,值類型屬性的值不變 obj.a // 1 objClone.b.c = 200 // 改變複製對象的引用類型的屬性,引用類型的屬性值改變 obj.b.c //200
深複製
var obj = { a : 1, b: { c: 2 } } function deepClone(obj){ if( typeof obj != 'object'){ return obj; } var newObj=obj.constructor === Array ? [] : {}; for(var i in obj){ newObj[i]=deepClone(obj[i]); } return newObj; } var objClone = deepClone(obj) objClone.a // 1 obj.a // 1 objClone.a = 100 // 改變複製對象的值類型屬性,值類型屬性的值不變 obj.a // 1 objClone.b.c = 200 // 改變複製對象的引用類型的屬性,引用類型的屬性值不變 obj.b.c // 2
本節參考文章:javascript淺複製與深複製
apply 、 call 、bind 三者都是用來改變函數的this對象的指向的;
apply 、 call 、bind 三者第一個參數都是this要指向的對象,也就是想指定的上下文;
apply 、 call 、bind 三者均可以利用後續參數傳參;
bind是返回對應函數,便於稍後調用;apply 、call 則是當即調用 。
call apply 的區別是他們指定參數的方式不一樣。
案例
function fn(a,b){ console.log(this); console.log(a); console.log(b); } // bind(this,args...) bf = fn.bind("Bind this",10); // 沒有任何輸出,也就是說沒有執行這個函數 bf(); // "Bind this",10,undefined bf(20);// 「Bind this」,10,20 // 原函數不受影響 fn(1,2); //window, 1,2 bf2 = fn.bind("Bind this",1,2); bf2(); // "Bind this",1,2 // call(this,args...) fn.call("Call this",1) // "Call this",1,undefined fn.call("Call this",1,2) // "Call this",1,2 // apply(this,[args]) fn.apply("Apply this",[1]) // "Apply this",1,undefined fn.apply("Apply this",[1,2]) // "Apply this",1,2