前端笔试题[1]

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的值。

参考文章:

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的操作的定义如下:

image

简单理解就是:

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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值