http://www.cnblogs.com/Candybunny/category/811557.html
閉包和this
一.閉包
最開始了解閉包是在一個函數内部定義一個函數,可以在外面的環境裡進行調用。現在對于閉包的了解是利用函數來儲存作用域内的對象。
了解閉包首先要了解執行上下文,變量對象,活動對象,作用域鍊。因為執行上下文在函數執行後會銷毀,是以變量也同時消失,但是為了一些特殊的應用場景,是以需要在函數執行後依舊可以通路到函數内的變量。js語言将函數作為“一等公民”,可以作為參數進行傳遞,同時每個函數也擁有其作用域鍊,如果内部函數作為變量傳回,那麼它将帶着它的作用域鍊一同傳回。因為内部函數的作用域鍊中包含着外部函數的變量,函數聲明和參數等,是以,外部函數即使被執行完了,變量也不會銷毀,因為他們依舊被内部函數的作用域鍊引用。 舉個例子:
1 2 3 4 5 6 7 8 | |
執行結果是‘local scope’。因為在傳回的bar函數被調用的時候,會先從自己的作用域鍊查找,沒有的話會逐級再向上查找,直到找到scope對象,輸出結果結束。
二.this
如果把上面的代碼增加一句話,在傳回的函數中輸出this。
1 2 3 4 5 6 7 8 9 | |
結果是’local scope’,’window’。this指向的是global對象,在浏覽器中就是window。 對this的了解是:
1)它是在解析函數的時候确定的,即執行上下文的一個屬性,運作過程中并不會改變。
1 2 3 4 5 6 | |
2)this根據調用函數的對象或者表達式形式确定。
1 2 3 4 | |
調用函數的對象是Global,即window
1 2 3 4 5 6 7 8 9 10 11 12 | |
輸出20,10,調用函數的對象分别是zoo和foo
1 2 3 4 5 6 7 8 9 10 | |
()表達式并沒有改變函數本身的調用值,是以傳回‘local x’,指派表達式改變了函數本身的調用值,是以傳回‘global x’。
3)new操作符調用的函數this指向構造函數對象
當函數作為構造器被調用的時候:
1 2 3 4 5 6 7 | |
在這種情況下,new操作符會生成一個新的對象,即構造函數定義的對象。 在對象建立之後,然後所有“A”函數中this的值會設定為新建立的對象。
可以通過函數對this值進行人為幹預:
4)call和apply函數
1 2 3 4 5 6 7 8 9 10 | |
通過call和apply的第一個參數改變this的值。
參考文章:
ECMA-262-3 in detail. Chapter 3. This.
原型和原型鍊
對于js中原型的了解,我僅處在初級階段。我的了解是,js的原型是為了實作“類”的概念,可以使得對象的方法可以通用,實作類的繼承。雖然js中并沒有類的概念,但是多數情況下,人們還是偏向于使用面向對象的概念在程式設計。
Js所有的函數都有一個prototype屬性,這個屬性引用了一個對象,即原型對象,也簡稱原型。當我們用js來模拟類時就涉及到了原型鍊的概念。原型鍊和作用域鍊的作用很相似,都是用來定義查詢變量的一種規則。當你在自身查找不到某個變量或者方法的時候,原型鍊會根據規則繼續向上查找,直到頂端為止。
比如,在定義一個“類”的時候,通常會先定義一個構造函數,裡面包含了執行個體屬性。
function Person (name, age) {
this.name = name;
this.age = age;
}
他可能包含了一些通用的方法(類方法),比如讀取姓名和年齡,安排班級等
Person.prototype.getName = function () {
return this.name;
}
Person.prototype.getAge = function () {
return this.age;
}
當我們需要給一個類定義通用的方法的時候,我們需要在它的原型上定義屬性,這樣,所有通過構造函數生成的執行個體都可以調用該方法。
繼承的實作也是因為有原型的存在得以實作,通用的實作繼承方法如下:
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function () {
return this.name;
}
Person.prototype.getAge = function () {
return this.age;
}
Person.prototype.setAge = function (age) {
this.age = age;
}
function Teacher(name,age,no){
//實作子類屬性的繼承,每個執行個體都有屬于自己的屬性,同時需要注意參數的順序,call函數隻看重順序,不在乎參數名稱
Person.call(this, name, age);
this.classNo = no;
}
Teacher.prototype = new Person(); //将父類的對象指派給子類,原型對象,這樣子類就繼承了父類的方法
// 在支援ES5的浏覽器推薦下面的寫法:(建立一個新的對象,内容是Person.prototype,将其指派給Teacher.prototype,這樣就将Person的原型和Teacher的原型相關聯,并不會産生副作用)// Teacher.prototype = Object.new(Person.prototype);
Teacher.prototype.getInfo = function () {
console.log('name:' + this.name + 'age:' + this.age + 'classNo:' + this.classNo);
}
var teacherLi = new Teacher('Li', 30, 2);
teacherLi.getInfo(); //name:Liage:30classNo:2
teacherLi.setAge(28);
teacherLi.getInfo(); //name:Liage:28classNo:2
一. new操作符的含義
當我們使用new操作符時,實際上就是建立一個對象。但在實際運作當中,new是建立了兩個對象,并将其互相關聯。例如上面的例子中var teacherLi = newTeacher('Li', 30, 2); 這句話,new操作符建立了一個對象并将它指派給teacherLi,并将teacherLi和Teacher.prototype相聯系,teacherLi.__proto__ === Teacher.prototype。
在ES5規範裡,針對new的操作的定義如下:

簡單了解就是:
1. 建立一個對象
2. 将對象的内部__proto__屬性和構造函數的prototype相關聯
3. 利用構造函數給執行個體對象屬性指派
4. 如果構造函數沒有顯示傳回對象,則傳回步驟一建立的對象
二.constructor
Foo.prototype.constructor === Foo
Foo.prototype的.constructor屬性隻是Foo函數在聲明時的預設屬性。如果prototype被重新指派聲明,那麼constructor就不知道是指向誰了,它會根據原型鍊一直檢索,直到檢索到最上層Object對象。
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 建立一個新原型對象
var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true!
三.prototype&__proto__
對象的__proto__屬性指向它關聯的prototype對象。可以簡單的了解為執行個體的__proto__屬性指向它的原型對象。
function Foo(a) {
this.a = a;
}
var foo = new Foo('foo');
console.log(foo.__proto__ === Foo.prototype);
console.log(Foo.__proto__ === Function.prototype);
console.log(Function.__proto__ === Function.prototype);
console.log(Function.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);
以上的答案都是true,可以看到當你用new辨別符來建立一個屬性時它會預設應用建立原型鍊,一直關聯到Object這個對象原型。
在js的内置類型當中:
Number.__proto__ === Function.prototype // true
Boolean.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // true
Array.__proto__ === Function.prototype // true
RegExp.__proto__ === Function.prototype // true
Error.__proto__ === Function.prototype // true
Date.__proto__ === Function.prototype // true
Math.__proto__ === Object.prototype // true
JSON.__proto__ === Object.prototype // true
因為Function.prototype.__proto__ === Object.prototype得值是true,是以,函數也是對象。
之前看到過一套測試題,放在這裡以供思考:
function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}
//請寫出以下輸出結果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
解釋位址:http://web.jobbole.com/85122/
iframe高度自适應
前兩天在網上看到了一道面試題,問iframe高度自适應的問題。發現自己之前幾乎沒有關注過iframe的問題,是以在這裡記錄一下。
原題目是: 頁面A的域名是:http://www.taobao.com,頁面B的域名是http://www.tmall.com,如果A使用iframe引用頁面B,如何做到iframe的高度自适應(即B内容有多高,iframe就有多高)
在這裡首先分析一下如果不涉及跨域或者隻是主域相同,子域不同的情況下的解決方案:
父頁面代碼:
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta http-equiv="Content-Type" content="text/html" charset="UTF-8">
5 <title>Document</title>
6 </head>
7 <body>
8 <iframe id="iframe1" name="iframe1" src="http://www.iframe.com" frameborder="0" style="width:100%"></iframe>
9 <script>
10 // document.domain = "iframe.com"; 主域相同,子域不同
11 function setIframe(childrenIF) {
12
13 // contentWindow 相容各個浏覽器,可取得子視窗的 window 對象。
14 // contentDocument Firefox 支援,> ie8 的ie支援。可取得子視窗的 document 對象。
15 var childrenWin = childrenIF.contentWindow || childrenIF.contentDocument.parentWindow;
16 if (childrenWin.document.body) {
17 // document.documentElement傳回文檔對象(document)的根元素(例如,HTML文檔的 <html> 元素)。
18 childrenIF.height = childrenWin.document.documentElement.offsetHeight || childrenWin.document.body.offsetHeight;
19 }
20
21 }
22 window.onload = function() {
23 setIframe(document.querySelector('#iframe1'));
24 }
25 </script>
26 </body>
27 </html>
看到張鑫旭的部落格裡說到另一種方法,是在iframe頁面傳遞一個參數給父頁面,告知其高度。父頁面取到參數後再給iframe高度指派。
大緻原理在子頁面iframe裡定義
// 為了防止window.location.hash産生跨域問題,可以直接寫死hostUrl位址:利用window.top.location = 父頁面位址(寫死) + 錨點
var hostUrl = window.location.hash.slice(1);
hostUrl += "#height=" + 1294;
window.top.location = hostUrl;
然後将子頁面嵌入到父頁面中,父頁面提取location中的height數值,進而更改iframe高度。
var iframeHeight = function() {
var hash = window.location.hash.slice(1);
if (hash && /height=/.test(hash)) {
iframe.height = hash.replace("height=", "");
}
setTimeout(iframeHeight, 200);
};
iframeHeight();
可以參考:小tip:iframe高度動态自适應
這裡思考了一下是不是可以不寫死頁面的位址:
假設面試題目中提到的頁面A:www.taobao.com内部嵌入頁面B:www.tmall.com頁面,要讓B頁面高度自适應的解決方案
參考各種資料,可以利用中間代理頁面agent.html來完成。
主要原理是agent頁面和A頁面同源,将agent頁面嵌入到B頁面擷取到B頁面寬高後,通過url傳遞寬高值。通過agent來操作A頁面中嵌入的B頁面寬高。
1. 在A(taobao)頁面嵌入iframe
<iframe src="http://www.tmall.com" id="Iframe" frameborder="0" scrolling="no" style="border:0px;"></iframe>
2. 在B(tmall)頁面嵌入agent_iframe,擷取B頁面的寬高。(将擷取到的寬高通過url傳遞)
<iframe id="agent_iframe" height="0" width="0" src="http://www.taobao.com/agent.html" style="display:none" ></iframe>
<script type="text/javascript">
(function autoHeight(){
var tmall_width = Math.max(document.body.scrollWidth,document.body.clientWidth);
var tmall_height = Math.max(document.body.scrollHeight,document.body.clientHeight);
var agent_iframe = document.getElementById("agent_iframe");
// 這裡通過hash傳遞tmall的寬高
agent_iframe.src = agent_iframe.src + "#" + tmall_width + "|" + tmall_height;
})();
</script>
3. 在agent.html插入代碼(因為agent和A頁面是相同域名,是以可以通過子元素來控制父元素的父元素[因為agent是嵌入在B頁面的,B頁面嵌入在A頁面,是以agent可以控制A頁面的元素,此處為多層嵌套,有點繞]的寬高)
<script type="text/javascript">
var tmall_iframe = window.parent.parent.document.getElementById("Iframe");
var hash_url = window.location.hash;
if (hash_url.indexOf("#")>=0){
var hash_width = hash_url.split("#")[1].split("|")[0]+"px";
var hash_height = hash_url.split("#")[1].split("|")[1]+"px";
tmall_iframe.style.width = hash_width;
tmall_iframe.style.height = hash_height;
}
</script>
總結
個人認為,如果父頁面的位址是固定的,我覺得直接寫死位址是比較友善直覺的方法。當然還有很多其他方法可以實作高度自适應。
詳見:iframe高度自适應的6個方法
看到的筆試題,總結在這裡吧!
1.運用JS設定cookie、讀取cookie、删除cookie
function setCookie (name, value) {
let duringDay = 30;
let exp = new Date();
// setTime() 方法以毫秒設定 Date 對象。
exp.setTime(exp.getTime() + duringDay*24*60*60*1000);
// 防止Cookie中不允許需要儲存的字元串中有“;”出現。有些作業系統,在解釋中文的字元串時候常常會出現亂碼的現象。避免儲存資料中出現非英文字母、非數字的字元。運用escape編碼
document.cookie = name + '=' + escape(value) + ';expires=' + exp.toGTMString();
}
// setCookie('ga', 'aaaaa');
function getCookie (searchName) {
let rsObj = {};
let rsArray = document.cookie.split(';');
rsArray.map((cv,index,array)=>{
let item = cv.split('=');
//去掉空格
let name = unescape(item[0].split(' ').join(''));
let value = unescape(item[1]);
rsObj[name] = value;
});
/* 或者利用正則
let arr;
let reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
if(arr=document.cookie.match(reg)) {
return unescape(arr[2]);
}
else {
return null;
}
*/
return rsObj[searchName];
}
// getCookie('ga');
function deleteCookie(delName) {
let exp = new Date();
exp.setTime(exp.getTime() - 1);
let val = getCookie(delName);
if (val) {
// toGMTString() 方法可根據格林威治時間 (GMT) 把 Date 對象轉換為字元串,并傳回結果。
// Thu, 29 Dec 2016 10:48:00 GMT
document.cookie = delName + '=' + val + ';expires=' + exp.toGTMString();
}
}
// deleteCookie('ga');
2. 請編寫一個JavaScript函數 parseQueryString,它的用途是把URL參數解析為一個對象,如:var url = “http://witmax.cn/index.php?key0=0&key1=1&key2=2″;
function parseQueryString () {
let query = window.location.search.substring(1);
let arr = query.split('&');
let obj = {};
arr.map((cv,index,array)=>{
let item = cv.split('=');
let name = decodeURIComponent(item[0]);
let value = decodeURIComponent(item[1]);
obj[name] = value;
});
return obj;
}
// let foo = parseQueryString();
// console.log(foo);