JS中的闭包及面向对象编程 — 3、案例练习&面试题解析

1、面试题讲解(闭包练习)

建议大家每一道题都要画图(尤其是复杂些的题目),画图过程能让我们把基础知识掌握的更加扎实,而且更加有效的算出正确的答案

先自己计算,都完成后再开始测试答案,千万不要直接输出看答案,这样就没有意义了

一、选择题

1、

console.log(a);
var a = 12;
function fn(){
    console.log(a);
    var a = 13;
}
fn();
console.log(a);

// A、undefined 12 13
// B、undefined undefined 12 
// C、undefined undefined 13
// D、有程序报错
复制代码

=> 在当前作用域当中,js代码自上而下执行之前,首先变量提升=>声明一个变量:var a;声明加定义一个函数fn

=> 代码自上而下执行:输出a,a在变量提升阶段声明过但是没有赋值,所以结果是undefined

=> 接下来全局的变量a=12,函数fn在变量提升阶段已经声明加定义过了,所以不用管

=> fn()执行:形成一个私有作用域(形参赋值:无;变量提升:声明一个变量var a => 以后在函数体中出现的所有a都是私有变量), 所以函数体中输出的a是undefined,然后var a=13(私有变量赋值为13)

=> 私有变量a和全局变量a没有关系,所以最后输出全局下的a是12

// => 选:B

2、

console.log(a);
var a = 12;
function fn(){
    console.log(a);
    a = 13;
}
fn();
console.log(a);

// A、undefined 12 13
// B、undefined undefined 12 
// C、undefined undefined 13
// D、有程序报错
复制代码

// => 在当前作用域当中,js代码自上而下执行之前,首先变量提升=>声明一个变量:var a;声明加定义一个全局函数fn

// => 代码自上而下执行:输出a,a在变量提升阶段声明过但是没有赋值,所以结果是undefined

// => 接下来全局的变量a=12,函数fn在变量提升阶段已经声明加定义过了,所以不用管

// => fn()执行:形成一个私有作用域 =>形参赋值:无;变量提升:无 => 这里出现的变量就不再是私有变量了,通过作用域链向上级查找, // 这里的上级作用域是window,所以函数体中输出的a是window下的a=>12,然后a=13,a是全局变量,这里全局变量a赋值为13

// => 所以最后输出全局下的a是13

// => 选A

3、


console.log(a);
a = 12;
function fn(){
    console.log(a);
    a = 13;
}
fn();
console.log(a);

// A、undefined 12 13
// B、undefined undefined 12 
// C、undefined undefined 13
// D、有程序报错
复制代码

// => 在当前作用域当中,js代码自上而下执行之前,首先变量提升=>声明一个变量:无;声明加定义一个全局函数fn

// => 代码自上而下执行:输出a,这里没有a,程序报错(js当中当前代码报错后面代码都不会执行了)

// => 选D

前3道题是关于变量提升以及简单的作用域链的问题

4、

var foo=1;
function bar(){
    if(!foo){
        var foo=10;
    }
    console.log(foo);
}
bar();


// A、1
// B、10
// C、undefined
// D、报错
复制代码

// => window全局作用域下的变量提升:声明var foo;声明加定义bar,bar是一个函数,所以开辟一个新堆内存AAAFFF111

// => 堆内存AAAFFF111把函数体中的代码以字符串形式存储起来,把地址AAAFFF111赋值给bar

// => 全局下代码自上而下执行:foo=1,bar()在变量提升时已经声明加定义过,不用管了

// => bar()执行,会形成一个私有作用域,私有作用域中形参赋值:无,变量提升(在浏览器当中不管条件是否成立,都要进行变量提升): // 声明变量var foo(以后在当前私有作用域出现的foo都是私有变量),接下来代码自上而下执行

// => foo是私有的,没有赋值:undefined,if(!foo) => !undefined => 把undefined转化成布尔类型值再取反 => // undefined转化成布尔值是false,再取反为true,条件成立,则私有变量foo=10,所以输出foo结果是10

// => foo()执行完后,当前作用域将销毁(没有被外界占用)

// => 选B

2、面试题讲解(逻辑与和逻辑或)

&&逻辑与 ||逻辑或

1、在条件判断中

&&:所有条件都为真,整体才为真

||:只要有一个条件为真,整体就为真
复制代码

2、在赋值操作中

||:A||B 首先看A的真假,A为真返回的是A的值,A为假返回B的值(不管B是啥)

    1||2 => 1

    0||false => false

&&:A&&B 首先看A的真假,A为假返回A的值,A为真返回B的值

    1&&2 =>2
    
    0&&false =>false
复制代码

3、&&逻辑与 ||逻辑或在实际项目中的运用

优秀的库源码中很少会用if(){},一般都逻辑运算符或者三元运算符判断


function fn(num,callBack){
    // => 如果num没有传递值:让其默认值是0
    // if(typeof num === 'undefined') num = 0; // 这样写麻烦
    num = num || 0;//=>真实项目中应用逻辑或实现默认值的设置操作
    // (但是这种写法不那么严谨,如果num传的false、null、NaN、空字符串、得到的结果都是0)

    // => 如果callBack传递是是一个函数,就把这个函数执行
    // if (typeof callBack === 'function') {
    //     callBack();
    // };
      callBack && callBack();//=>如果传了callBack,左边callBack有值,只有左边callBack为真时才会执行右边的callBack(),
    //如果没有传这个callBack,左边的callBac为undefined则返回undefined,不会再执行右边的callBack()

};
fn(100,function(){

});

复制代码

4、即有逻辑与又有逻辑或,逻辑与的优先级高于逻辑或

0||2&&false||3 


// => 先执行2&&false,2是真返回右边的false

// => 0||false||3

// => 0||false,0是假返回右边的false

// => false||3,false是假返回右边的3

// => 结果是:3
复制代码

1、

var foo='hello';
(function(foo){
    console.log(foo);
    var foo=foo||'world';
    console.log(foo);
})(foo);
console.log(foo);


// A、hello hello hello
// B、undefined world hello
// C、hello world world
// D、以上答案都不正确
复制代码

=> 全局下变量提升:var foo;

=> 代码自上而下执行,全局下变量foo=hello;自执行函数执行,把全局foo的值当做实参传递给私有作用域的形参, 全局下foo的值是hello传给形参;

=> 变量提升完后代码自上而下执行形成一个私有作用域:形参赋值,让foo=hello(我们的foo和外面defoo是不同的变量, 虽然是同一个值hello,但是一个是私有的,一个是全局的),

=> 接下来var foo=foo||'world';但是私有作用域中已经有一个foo并且有值hello,所以这里的var foo不用再重新声明, foo=hello是真,var foo = foo || 'world'返回左边的foo:hello不用再执行右边的;所以console.log(foo):hello

=> 最后输出全局变量foo:hello

=> 选A

3、面试题讲解(闭包练习题)

var a = 9;
function fn(){
    a = 0;
    return function (b) {
        return b + a++;
    }
}
var f = fn();
console.log(f(5));
console.log(fn()(5));
console.log(f(5));
console.log(a);
复制代码

4、面试题讲解(数据类型练习题)

var ary = [1,2,3,4];
function fn(ary) {
    ary[0] = 0;
    ary = [0];
    ary[0] = 100;
    return ary;
}
var res = fn(ary);
console.log(ary);
console.log(res);
复制代码

=> 全局作用域下的变量提升:声明两个变量arr和res,声明加定义fn,fn是一个函数开辟堆内存AAAFFF111存代码字符串, 将地址AAAFFF111赋值给fn

=> 代码自上而下执行,全局下的ary是一个数组,数组是引用数据类型中的对象数据类型的,所以开辟一个堆内存AAAFFF22存内容, (对于对象来说存属性名和属性值),将地址AAAFFF22赋值给ary。fn()在变量提升时已经完成了声明加定义,所以不管。

=> 接下来给全局变量res赋值,var res = fn(ary):把fn(ary)执行的结果返回给res;fn(ary)=>把全局ary存储的值当做实参传 递给fn的形参=>fn(AAAFFF222);所以fn(ary)执行相当于fn(AAAFFF222)执行

=> fn(AAAFFF222)执行形成私有作用域:形参赋值:ary=AAAFFF222;此时ary通过地址AAAFFF222指向AAAFFF222堆内存,但是 这个私有作用域下的ary和全局作用域下的ary不是同一个变量

(私有变量和全局变量互不干扰没有任何直接关系,但是可能存在间接关系。 比如这里:私有和全局的ary都通过地址AAAFFF222指向AAAFFF222堆内存,不是同一个变量但是最终指向同一个堆内存

所以私有变量ary现在通过地址AAAFFF222修改AAAFFF222堆内存的内容,也会影响到全局变量ary)

接下来变量提升:无

代码自上而下执行:

=> ary[0] = 0:私有变量ary通过地址AAAFFF222找到AAAFFF222堆内存,把AAAFFF222堆内存当中索引为0的对象的值改为0

=> ary = [0]:[0]是一个新数组(引用数据类型中的对象数据类型),开辟新堆内存BBBFFF111存属性名和属性值,把地址 BBBFFF111赋值给ary

之前ary通过地址AAAFFF222找到AAAFFF222堆内存,现在ary通过地址BBBFFF111找到BBBFFF111堆内存(一个变量只能存一个地址 ,只能指向一个堆内存)所以ary不再指向AAAFFF222堆内存,此时的私有变量ary和全局变量ary没有任何关系了

=> ary[0] = 100:通过地址BBBFFF111找到BBBFFF111堆内存,把BBBFFF111堆内存当中索引为0的对象的值改为100

=> return ary:return BBBFFF111,所以fn(AAAFFF222)执行后的返回结果是BBBFFF111,所以var res = fn(ary):把fn(ary)执行 的结果返回给res=>res = BBBFFF111

当前作用域不销毁(因为当前作用域当中的堆内存BBBFFF111被外面的res占用了)

=> 所以全局下的ary通过地址AAAFFF222找到AAAFFF222堆内存输出[0, 2, 3, 4],全局下的res通过地址BBBFFF111找到BBBFFF111堆内存 输出[100]

=> 结果:输出[0, 2, 3, 4]和[100]

总结:

遇到数组(数组是引用数据类型中的对象数据类型的)、对象、函数这种引用数据类型:开空间、存内容(对于对象来说存属 性名和属性值)、地址的赋值

一个变量只能指向一个空间(一个变量只能存一个地址,只能指向一个堆内存)

函数执行会形成一个私有作用域,保护里面的私有变量不受外界的干扰(闭包机制),但是私有变量和全局变量没有直接的关系, 可能会存在间接的关系 => 当私有变量和全局变量都是引用数据类型的值,而且指向的是相同的堆内存时,它们会存在间接关系

5、面试题讲解(闭包练习题)

1、

function fn(i){
    return function(n){
        console.log(n+(i++));
    }
}
var f = fn(10);
f(20);
fn(20)(40);
fn(30)(50);
f(30);

// 30 60 80 41
复制代码

2、

function fn(i){
    return function(n){
        console.log(n+(--i));
    }
}
var f = fn(2);
f(3);
fn(4)(5);
fn(6)(7);
f(8);

// 4 8 12 8
复制代码

6、面试题详解(闭包个this综合练习题)

var num = 10;
var obj = {num: 20};
obj.fn = (function(num){
    this.num = num * 3;
    num++;
    return function (n) {
        this.num += n;
        num++;
        console.log(num);
    }
})(obj.num);
var fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num, obj.num);
复制代码

=> 全局作用域下变量提升:var num; var obj; var fn;

=> 代码自上而下执行:

  • 全局下变量num赋值为10,
  • obj是一个对象,开辟新的堆内存AAAFFF111,存储属性名属性值,把堆内存地址AAAFFF111赋值给obj
  • obj.fn = ...:相当于给obj加一个属性名fn,fn的结果是一个自执行函数的返回值。(obj通过地址AAAFFF111找到堆内存AAAFFF111,给堆内存AAAFFF111增加一个属性名fn,属性名fn对应的属性值是一个自执行函数的返回值)

=> 自执行函数执行形成一个私有作用域A

  • 形参赋值:num = obj.num = 20(以后在作用域A出现的num都是私有的)

  • 变量提升:无

    接下来代码自上而下执行:

  • this.num = num * 3:自执行函数执行,this是window,num是私有变量(形参num=20),所以这句话等价于:

    window.num = 私有变量num*3 = 20*3 = 60 ,这时全局变量 num = 60

  • num++:让A中私有变量num累加1,此时A中 num = 20 + 1 = 21

  • return 函数:函数是引用数据类型,开辟一个新的堆内存BBBFFF111,存储代码字符串,把堆内存地址BBBFFF111赋值给return:return BBBFFF111(堆内存BBBFFF111隶属于作用域A)

最后自执行函数执行完后,需要把返回值(返回的BBBFFF111这个地址)赋值给obj.fn(obj下的fn)

由于作用域A里面的堆内存BBBFFF111被A外面的堆内存AAAFFF111占用,所以作用域A不销毁

=> var fn = obj.fn = BBBFFF111

=> fn(5):fn = BBBFFF111,fn(5)执行相当于执行堆内存BBBFFF11里面的代码块,fn(5)执行形成一个私有作用域

  • 形参赋值:n = 5

  • 变量提升:无

    接下来代码自上而下执行:

  • this.num += n:this.num += 5,this:window(fn(5)执行前面没有点,所以this还是window),所以等价于

    window.num += 5:60 += 5 = 65 (全局变量num=65)

  • num++:num不是私有的,查找上级作用域A(fn = BBBFFF111,BBBFFF111在作用域A中开辟的,所以说fn(5)的上级作用域是A),num++:A中的 num++ = 21 + 1 = 22

fn(5)执行完毕后,将销毁(没有被外界占用)

=> obj.fn(10):obj.fn = BBBFFF111,所以 obj.fn()执行 相当于堆内存BBBFFF111中的代码块执行形成私有作用域

  • 形参赋值:n = 10

  • 变量提升:无

    代码自上而下执行

  • this.num += 10:此时的this是obj,相当于

    obj.num += 10:20 += 10 = 30 (obj下的num=30)

  • num++:num不是私有变量,通过BBBFFF111向上级查找,找到作用域A里面的 num = 22;A中的 num++ = 22 + 1 = 23;

obj.fn(10)执行完毕后,将销毁(没有被外界占用)

=> 输出全局变量 num = 65,obj.num = 30

=> 结果:22 23 65 30

7、面试题讲解(原型和原型链练习题)

function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype = {
    y: 400,
    getX: function () {
        console.log(this.x);
    },
    getY: function () {
        console.log(this.y);
    },
    sum:function () {
        console.log(this.x + this.y);
    }
};
// Fn.prototype.getX = function () {
//     console.log(this.x);
// };
// Fn.prototype.getY = function () {
//     console.log(this.y);
// };
var f1 = new Fn;
var f2 = new Fn;
console.log(f1.getX === f2.getX);//false
console.log(f1.getY === f2.getY);//true
console.log(f1.__proto__.getY === Fn.prototype.getY);//true
console.log(f1.__proto__.getX === f2.getX);//false
console.log(f1.getX === Fn.prototype.getX);//false
console.log(f1.constructor);//Object
console.log(Fn.prototype.__proto__.constructor);//Object
f1.getX();// =>this:f1 f1.x=>100
f1.__proto__.getX();// =>this:f1.__proto__(Fn.prototype) =>Fn.prototype.x =>undefined
f2.getY();//=>this:f2 f2.y=>200
Fn.prototype.getY();//=>this:Fn.prototype Fn.prototype.y=>400
f1.sum();//=>this:f1 f1.x+f1.y=>300
Fn.prototype.sum();//=>this:Fn.prototype Fn.prototype.x+Fn.prototype.y=>undefined+400=>NaN
复制代码

先把关于类、实例、内置类、原型的堆内存画出来(只画堆内存不画栈内存)

=> Fn堆内存存储代码字符串,所有的函数都有prototype属性,浏览器会默认为其开一个堆内存(Fn.prototype)并且天生自带属性:constructor(构造函数)它的的值就是当前函数本身(Fn);

由于Fn.prototype是对象类型,每个对象类型都自带属性__proto__指向自己所属类的原型,Fn.prototype对象的实例是Object的实例(因为不知道Fn.prototype是怎么new出来的,但是所有对象都是Object的实例)

所以Fn.prototype.proto 指向Object.prototype

=> Object类是函数数据类型的(类是一个函数),所以开辟一个堆内存存储代码字符串(这里叫做native code:很多内置的代码字符串),

所有的函数都有prototype属性,浏览器会默认为其开一个堆内存并且天生自带属性:constructor(构造函数)它的的值就是当前函数本身(Object);

由于Object.prototype是对象类型,每个对象类型都自带属性__proto__指向自己所属类的原型,所以Object的__proto__指向Object的原型prototype(指向了自己),所以Object内置类的基类原型上的__proto__:null (Object.prototype.proto = null)

看到Fn第一想到的就是以上四步骤(对应图第一排的四个图)

=> Fn.prototype = {}:是一个对象,自己开辟堆内存存放属性名和属性值(getX、getY、sum这三个也是函数,也应该有自己的堆内存,这里省略没有画),对象都有__proto__属性

所有对象都是Obeject的一个实例,所以Fn.prototype的__proto__指向Object.prototype(每个对象类型都自带属性__proto__指向自己所属类的原型)

最后把这个 {} 的空间地址赋值给Fn.prototype(之前Fn的prototype指向Fn.prototype,现在Fn的prototype指向自己开辟的堆内存,Fn.prototype过段时间会销毁,所以Fn.prototype.proto = Object.prototype这个指向也没有了)

=> f1和f2都是new Fn出来的结果(new Fn不传参的时候加不加括号都无所谓),new Fn相当于把堆内存Fn里面的代码都执行一遍,this是当前实例f1和f2,所以Fn中的 this.xxx=xxx 相当于 f1.xxx = xxxx 和 f2.xxx = xxx(给f1和f2增加私有属性和属性值)

f1和f2都是一个实例(实例是对象数据类型),所以有__proto__属性(所有对象都有__proto__属性),并且都指向所属类Fn的原型(这时Fn的原型是自己开辟的空间)

结果分析:

=> f1.getX === f2.getX:false

=> f1.getY === f2.getY:true(原型上提供的属性和方法是公有属性)

=> f1.proto.getY === Fn.prototype.getY:true

=> f1.proto.getX === f2.getX:false

=> f1.getX === Fn.prototype.getX:false

=> f1.constructor:Object(Fn.prototype = {}:批量在原型上扩展方法的时候要加上constructor:Fn,不然会随着prototype指向改变而丢失)

=> Fn.prototype.proto.constructor:Object

=> f1.getX():执行私有的getX()方法,this是f1,所以输出:this.x = f1.x = 100

=> f1.proto.getX():执行公共getX()方法,this是 f1.proto(相当于Fn.prototype),Fn.prototype.x = undefined

=> f2.getY():私有中没有,通过__proto__找到公有中的getY()方法并且执行输出this.y,但是此时getY()中的this是f2,所以输出 f2.y = 200

=> Fn.prototype.getY():this是Fn.prototype,输出:this.y 相当于输出:Fn.prototype.y = 400

=> f1.sum():f1.sum()私有方法中没有,通过__proto__找到公有方法sum()并且执行输this.x+this.y,f1.sum()中的this是f1,f1.x+f1.y = 300

=> Fn.prototype.sum():Fn.prototype.sum()执行输出this.x+this.y,Fn.prototype.sum()中的this是Fn.prototype,相当于:Fn.prototype.x+Fn.prototype.y = undefined+400 = NaN

总结:

堆内存的作用:存储引用数据类型值的(相当于一个存储的仓库) ;对象存储的是键值对 ;函数存储的是代码字符串

  • 每个函数都有pototype
  • 浏览器会给pototype这个对象开辟新的堆内存
  • 所有对象都有__proto__属性指向所属类的原型
  • 所有对象都是Object的实例(所有对象所属类都是Object)
  • 批量在原型上扩展方法的时候要加上constructor:Fn,不然会随着prototype指向改变而丢失
  • 函数数据类型值:普通函数、所有类(内置类和自定义类)也是函数

8、面试题讲解(循环绑定事件,索引引发的问题)

以下代码的功能是要实现5个input按钮循环绑定click事件,绑定完成后点击1、2、3、4、5五个按钮分别会alert输出0、1、2、3、4、5五个字符。(腾讯)

  • 请问如下代码是否能实现?
  • 如果不能实现那么现在的效果是什么样的?
  • 应该做怎样的修改才能达到我们想要的效果,并说明原理?
<body>
    <div id="btnBox">
        <input type="button" value="button_1"/>
        <input type="button" value="button_2"/>
        <input type="button" value="button_3"/>
        <input type="button" value="button_4"/>
        <input type="button" value="button_5"/>
    </div>
<script type="text/javascript">
    var btnBox = document.getElementById('btnBox'),
        inputs = btnBox.getElementsByTagName('input');
    var l = inputs.length;
    for(var i = 0;i < l; i++){// => 循环结束i=5
        inputs[i].onclick = function(){
            alert(i);
        }
    }
    // 解决方案三种(自定义属性、闭包、ES6的块级作用域)
    ------------------------------------------------------------------------------------------------------------
    // 1、自定义属性
    for(var i = 0;i < l; i++){
        inputs[i].myIndex = i;
        inputs[i].onclick = function(){
            alert(this.myIndex);
        }
    }
    
    ------------------------------------------------------------------------------------------------------------
    //2、闭包解决(当前私有作用域和全局作用域之间加一层不销毁的作用域把索引i保存下来:自执行函数执行)
    for(var i = 0;i < l; i++){
        ~function(i){
            inputs[i].onclick = function(){//再执行这个方法往上级作用域查找i时,上级作用域就是闭包了
                alert(i);
            }
        }(i)//循环5次形成5个私有作用域,每个私有作用域里的i互不影响 (形参i是作用域的私有变量)
        
    }
    
    //3、闭包解决(自执行函数执行,但是循环5次会形成5个不销毁的闭包(私有作用域),消耗内存,性能不好)
    for(var i = 0;i < l; i++){
        inputs[i].onclick = (function(i){//绑定的时候有个自执行函数,把全局i当做实参传给闭包的形参
            return function () {//自执行函数执行返回这个方法给点击事件
                alert(i);//点击执行这个方法用到的i不是私有的,像上级闭包作用域查找i,此时也不会去找全局下的i
            }
        })(i)
    }
    
    ------------------------------------------------------------------------------------------------------------
    //4、ES6块级作用域概念(用let定义变量有块级作用域的概念)每次循环,这个大括号都相当于一个单独的块级作用域,
    //向上查找找的是本次块级作用域里面存的i的值,而不是全局的i
    for(let i = 0;i < l; i++){
        inputs[i].onclick = function(){
            alert(i);
        }
    }
</script>
</body>
复制代码

上面的代码为啥不行?

所有的事件绑定都是异步编程

循环给每个input绑定方法的时候并没有执行这个点击事件,当触发点击事件,执行方法的时候,循环早已经结束了

答:(突出两点:作用域,闭包以及作用域链必须要突出来。面试技巧:这样的话才能用简短的语言回答到面试官想 要的核心点上,否则说一大堆,面试官也不满意,会认为你的总结能力和语言表达能力不好。语言表达、沟通能力性 格特点技术能力每项评分)

1、 所有的事件绑定都是异步编程,循环给每个input绑定方法的时候并没有执行这个点击事件,当触发点击事件, 执行方法的时候,循环早已经结束,全局下的i=5

2、 当执行这个点击事件的方法形成的私有作用域,里面用到的i并不是私有变量,向上级作用域查找,此时windin下的 i是5,所以弹不出来索引值

根据你回答的面试官可能会继续问:能给我详细说一下同步和异步编程吗?

答: 浏览器的多线程,js的单线程是怎么协作的;等待任务队列和主任务队列是怎么依次来完成的;js同步异步编程的 话会遇到哪些问题(详细说:突出基础功、语言表达能力、实战应用当中遇到的问题、)

什么是同步编程和异步编程?

同步:js中当前这个任务没有完成,下面的任务都不会执行,只有等当前任务彻底完成才会执行下面的任务

异步:js中当前任务没有完成,需要等一会在完成,此时我们可以继续执行下面的任务

js是单线程,浏览器是多线程的。

9、面试题讲解(用友面试题关于this的处理)

1、

var fullName = 'language';
var obj = {
    fullName: 'javascript',
    prop: {
        getFullName: function () {
            return this.fullName;
        }
    }
};
console.log(obj.prop.getFullName());//=>this:obj.prop=>this.fullName=obj.prop.fullName=undefined
var test = obj.prop.getFullName;
console.log(test());//=>this:window=>this.fullName =window.fullName = language
复制代码

2、

var name = 'window';
var Tom = {
    name: 'Tom',
    show: function () {
        console.log(this.name);
    },
    wait: function () {
        var fun = this.show;
        fun();
    }
};
Tom.wait();

// => this:Tom => var fun = this.show = Tom.show => fun()的this是window,fun()执行相当于Tom.show里面的代码字符串
// this.name = window.name = 'window'

// 注意:Tom.show是把Tom.show()这个方法的地址赋值给fun,fun()执行相当于执行这个地址里面的代码字符串,但是fun()前面没有点,
// 所以fun()的this是window,输出this.name就是输出window.name
复制代码

10、面试题讲解(腾讯面试题,原型和原型链)

function fun() {
    this.a = 0;
    this.b = function () {
        alert(this.a);
    }
}
fun.prototype = {
    b: function () {
        this.a = 20;
        alert(this.a);
    },
    c: function () {
        this.a = 30;
        alert(this.a);
    }
}
var my_fun = new fun();// 实例my_fun 类fun
my_fun.b(); //=> 私有的方法b 弹0
my_fun.c(); //=> 公有的方法c 弹30
var my_fun2 = new fun;
console.log(my_fun2.a);// 0
my_fun2.__proto__.c();
console.log(my_fun2.a);// 0
console.log(my_fun.__proto__.a);// 30
复制代码

=> new fun():fun()是类,my_fun是它的实例

=> fun()是类,fun()方法中的 this.xxx = xxx 都相当于给它的实例(my_fun.b()、my_fun.c())增加私有属性a = 0、b = function()

=> fun.prototype = {}: fun.prototype指向一个新的堆内存替换原有浏览器默认开辟的prototype堆内存(注意constructor的丢失), 里面有两个公用方法(b、c)

=> my_fun.b():私有里面有方法b,执行私有方法b,输出this.a,this:my_fun,this.a = my_fun.a = 0,最后alert '0'

=> my_fun.c():私有里面没有方法c,执行公有方法c,输出this.a,this:my_fun,this.a = 30, my_fun.a = 30(把当前实例私有属性A 修改为30),最后alert '30'

=> my_fun2 = new fun:fun()加不加括号没关系,实例my_fun2里面也有私有属性(a = 0,b = function),不同实例之间的私有属性没有 任何关系,所以输出my_fun2.a = 0

=> my_fun2.proto.c():this是my_fun2.proto,my_fun2.proto.a = 30(当前实例通过原型链在类的公有属性上增加了一个a:30) ,所以alert '30', 但是私有属性 a:0 没变,所以

=> console.log(my_fun2.a):0

=> console.log(my_fun.proto.a):通过my_fun2.proto.c()在原型上加了个公有属性a:30,此时实例my_fun通过原型链__proto__找到 公有属性a:30(私有属性之间没有关系,但是一个实例通过原型链__proto__把公有的东西改了,另外一个实例通过原型链再找公有的也会跟着改),所以输出30

11、面试题讲解(其它主流公司关于闭包以及面向对象的面试)

自由性问答题:回答的时候更难,需要用简短的语言去答到每个知识点并且答到点上

5、如何实现数组去重?(乐视TV)

面向对象,原型,原型链,基于内置类的原型扩展方法 一步到位写成这样

思路:

使用自执行函数形成闭包,然后通过原型上扩展一个数组去重的方法(方法加前缀)

一个临时存储的对象

用数组最后一项替换到当前重复项,再把最后一项删除掉,(这里用splice删除会导致后面所有项的索引改变,耗性能) 最后i--(最后一项放到当前项,下一次比较还是从当前序列号去做比较,拿最后一项去比较)

// 自执行函数执行形成闭包保护里面东西不受外界干扰
~(function(){
    var pro = Array.prototype;
    // => 原型上扩展一个数组去重方法(方法加前缀)
    pro.myDistinct = function myDistinct(){
        var obj = {};
        for (var i = 0; i < this.length; i++) {//通过[].myDistinct()使用这个方法,this就是当前要操作的数组 
            var item = this[i];// 保存数组每一项
                if (typeof obj[item] !== 'undefined') {//听过当前对象里面有没有这个属性名判断当前对象有没有这
                    //一项,有就表示数组里也出现过这一项              
                    this[i] = this[this.length - 1];//用数组最后一项替换到当前重复项
                    this.length--;//再把最后一项删除掉
                    i--;//最后一项放到当前项,下一次比较还是从当前序列号去做比较,拿最后一项去比较
                    continue;
                }
            obj[item] = item;//对象里面没有这个属性就存起来
        }
        obj = null;//obj只是临时存储,用完后需要手动释放堆内存
        return this;//为了实现链式写法,最后把去重后的数组返回,这样返回的结果还是一个数组,可以继续使用下一个方法
    }
})();
复制代码

6、document.parentNode和document.parentnode的区别?(腾讯)

1、js严格区分大小写

  • parentNode:获取父节点,document.parentNode:文档是整个页面的根节点,没有父节点,所以 document.parentNode = null
  • parentnode:没有这个属性,所以 document.parentnode = undefined

2、这道题实际考察的是null和undefined的区别:

parentNode这个属性是有的,只不过document.parentNode的值没有,所以是null;parentnode就没有这个属性名,所以值是undefined

null:有这个属性但是没有值,后期可能会赋值(一般来说后期都会赋值)

undefined:压根就没有这个东西

3、什么情况下会出现undefined

  • 1、变量提升:只声明未定义默认值就是undefined

  • 2、严格模式下:没有明确的执行主体,this就是undefined

  • 3、对象没有这个属性名,属性值是undefined

  • 4、函数定义形参不传值。默认就是undefined

  • 5、函数没有返回值(没有return或者return),默认返回的就是undefined

  • ...

4、什么情况下会出现null

  • 1、手动设置变量的值或者对象某一个属性值为null(此时不赋值,后面会赋值)

  • 2、在js的DOM元素获取中,如果没有获取到指定的元素对象,结果一般都是null

  • 3、Object.prototype.__proto__的值也是null

  • 4、正则捕获的时候,如果没有捕获到结果,默认也是null

  • ...

7、怎么规避多人开发函数重名的问题(百度搜索)

两种办法:

1、使用闭包:每个人把自己的代码放到闭包当中避免冲突,但是相互之间调用需要把闭包中的方法暴露在全局当中(暴露多了可能也会冲突)

2、单列模式:实现当前模块的所有属性和方法全部放在同一个命名空间下,实现一个分类的作用,以后用的时候直接通过命名空间找里面的方法,

单列模式比闭包更好(单列模式具体怎么做的话,看之前写的单例模式)

8、Javascript如何实现面向对象中的继承?(百度移动)

后面写到后再说

9、你理解的闭包作用是什么,优缺点?(乐视)

闭包的作用:

1、保护:形成一个私有作用域保护里面的私有变量不受外界的干扰,项目开发中多人协作开发的时候,经常会把每个人写的代码放到一个闭包当中,防止冲突。

对于保护的话:jQuery就是利用而闭包的保护机制来开发的,首先通过自执行函数执行形成一个私有作用域,在里面有个类叫jQuery,然后这个类通过 window.jQuery = window.$ = jQuery 把它暴露在全局,jq的源码就是这么做的

2、保存:形成一个不销毁的栈内存,把一些值保存起来,项目中很多时候会用到,比如说做选项卡的时候除了用自定义属性解决以外,还可以通过闭包,让每次循环的时候形成一个不销毁的私有作用域,把所需要用到的索引都保存起来。

缺点:耗内存(因为闭包会形成不销毁的栈内存,就耗内存),所以说在项目中有一条优化法则:尽量减少闭包的使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值