http://www.cnblogs.com/Candybunny/category/811557.html
闭包和this
一.闭包
最开始理解闭包是在一个函数内部定义一个函数,可以在外面的环境里进行调用。现在对于闭包的理解是利用函数来保存作用域内的对象。
理解闭包首先要理解执行上下文,变量对象,活动对象,作用域链。因为执行上下文在函数执行后会销毁,因此变量也同时消失,但是为了一些特殊的应用场景,因此需要在函数执行后依旧可以访问到函数内的变量。js语言将函数作为“一等公民”,可以作为参数进行传递,同时每个函数也拥有其作用域链,如果内部函数作为变量返回,那么它将带着它的作用域链一同返回。因为内部函数的作用域链中包含着外部函数的变量,函数声明和参数等,因此,外部函数即使被执行完了,变量也不会销毁,因为他们依旧被内部函数的作用域链引用。 举个例子:
1
2
3
4
5
6
7
8
|
var
scope =
'global scope'
;
function
foo() {
var
scope =
'local scope'
;
return
function
bar() {
console.log(scope);
}
}
foo()();
|
执行结果是‘local scope’。因为在返回的bar函数被调用的时候,会先从自己的作用域链查找,没有的话会逐级再向上查找,直到找到scope对象,输出结果结束。
二.this
如果把上面的代码增加一句话,在返回的函数中输出this。
1
2
3
4
5
6
7
8
9
|
var
scope =
'global scope'
;
function
foo() {
var
scope =
'local scope'
;
return
function
bar() {
console.log(scope);
console.log(
this
);
}
}
foo()();
|
结果是’local scope’,’window’。this指向的是global对象,在浏览器中就是window。 对this的理解是:
1)它是在解析函数的时候确定的,即执行上下文的一个属性,运行过程中并不会改变。
1
2
3
4
5
6
|
function
foo() {
function
bar () {
...
}
this
= bar;
//会报错
}
|
2)this根据调用函数的对象或者表达式形式确定。
1
2
3
4
|
function
foo() {
console.log(
this
);
}
foo();
|
调用函数的对象是Global,即window
1
2
3
4
5
6
7
8
9
10
11
12
|
var
foo = {
x:10,
bar:
function
() {
console.log(
this
.x);
}
}
var
zoo = {
x:20
}
zoo.bar = foo.bar;
zoo.bar();
foo.bar();
|
输出20,10,调用函数的对象分别是zoo和foo
1
2
3
4
5
6
7
8
9
10
|
x =
'global x'
;
var
foo = {
x:
'local x'
,
bar:
function
() {
console.log(
this
.x);
}
}
foo.bar();
(foo.bar)();
(foo.bar = foo.bar)();
|
()表达式并没有改变函数本身的调用值,因此返回‘local x’,赋值表达式改变了函数本身的调用值,因此返回‘global x’。
3)new操作符调用的函数this指向构造函数对象
当函数作为构造器被调用的时候:
1
2
3
4
5
6
7
|
function
A() {
alert(
this
);
// newly created object, below - "a" object
this
.x = 10;
}
var
a =
new
A();
alert(a.x);
// 10
|
在这种情况下,new操作符会生成一个新的对象,即构造函数定义的对象。 在对象创建之后,然后所有“A”函数中this的值会设置为新创建的对象。
可以通过函数对this值进行人为干预:
4)call和apply函数
1
2
3
4
5
6
7
8
9
10
|
x =
'global x'
;
var
foo = {
x:
'local x'
,
bar:
function
(n) {
console.log(
this
.x);
console.log(n);
}
}
foo.bar.call({x:
'call x'
},
'call'
);
foo.bar.apply({x:
'apply x'
}, [
'apply'
]);
|
通过call和apply的第一个参数改变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();
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>
总结
个人认为,如果父页面的地址是固定的,我觉得直接写死地址是比较方便直观的方法。当然还有很多其他方法可以实现高度自适应。
看到的笔试题,总结在这里吧!
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);