前端面试的坑(三)

JAVASCRIPT中的闭包解析http://www.cnblogs.com/blackgan/p/5727280.html

学习javaScript已经有一段时间了,在这段时间里,已经感受到了JavaScript的种种魅力,这是一门神奇的语言,同时也是一门正在逐步完善的语言,相信在大家的逐步修改中,这门语言会逐步的完善下去,在上一篇随笔中,和大家分享了JavaScript中独有的类中的继承方式,今天呢,就跟大家分享一下我这几天一直在搞,却还是搞的不是很透彻的闭包问题,对于一个初学者而言,JavaScript中的闭包无疑是一个难点,而且也是我们必须要掌握的一个重点,那么今天我就跟大家分享一下我在学习闭包的时候的感悟以及相应的理解,首先,我们在一个函数中有一个变量: 代码1

复制代码
1 function people(){
2     var age = 12;//函数中有一个变量
3    ( function (){ //在函数中我们再定义一个自调用函数
4           alert(age);
5     })();
6    
7 }    
复制代码

在函数people中,有着变量age,大家应该之前都有了解到JavaScript中特有的变量作用域的问题,函数本身就是一个作用域,函数里边可以访问外边的变量,而函数外边并不能访问函数里边的变量,这样,我们就遇到了一个问题,如果我们想要获取函数中的变量age怎么办?如代码1中所示,因为自调用函数在people中,所以它可以访问它外边的变量,所以自调用函数可以访问age,那么我们就想,将这个自调用函数return出来不就行了吗?代码2:

复制代码
1 function people(){
2      var age = 12;
3      return function (){
4          alert(age);
5      }
6  }
7  var xiaoMing = new people();
8  xiaoMing();//会弹出文本框12,说明在people外边调用到了内部变量age
复制代码

好了,通过这种方法,我们获得了函数内部的变量,那么这就叫做闭包(closure),上边的功能就是它的第一个功能,可以在函数外部通过闭包获得函数内部的变量,从形状上看,闭包就是函数里边再定义一个函数并返回,从书上以及各个博客上看到了对闭包的解释各种各样,我对闭包的理解而言,整个函数就相当于一个房子,这个房子没有门,只有一个天窗,我们在下边无法看见房子里边放的东西,而闭包就像是一个梯子,让我们爬上去可以通过天窗看到房子里边放的东西,就是连接函数外边和里边的一座桥梁,闭包对于我们的便利性是不言而喻的,然而,事物总是有着相反的一面,闭包有他的好,当然也有它的坏,现在,我们可以先用闭包做一个累加的例子:代码3:

复制代码
1 function func(){
2      var count=10;
3      return function(){
4          return ++count;
5      }
6  }
7  var ss = new func();
8  alert(ss());//弹出11
9  alert(ss());//弹出12
复制代码

通过代码3我们可以看出,每次调用func中返回的闭包时,count是在累加的,这时我们心中肯定就会有很多疑惑,不是说好的函数是一个作用域吗?执行完了函数func它体内的变量应该不存在了呀?怎么每次调用的时候它都在累加呢?这就是闭包的第二个作用了,当闭包在函数体外执行时,它的体内保存了原来算是它的父类的函数的整个执行环境以及它体内调用的变量,这时他们的关系就像我们盖房子一样,func就像地基一样,ss就是我们的房顶,这时我们站在房顶上,那么地基会消失吗?显然是不能的,这时闭包的一个优点,同时也算是它的一个缺点,就是它将一个内部变量一直放到了内存中释放不了,有可能会造成内存泄露,但这也是它的一个优点,可以将作用域链拉长,但这个功能在for循环中是一个经常出错的地方:

代码4:

复制代码
 1 function func(){
 2      var arr=[];
 3      for (var i=0;i<5;i++){
 4          arr[i]=function (){//注意此处,往数组中存放的是一个函数
 5              return i;
 6          }
 7      }
 8      return arr;
 9  }
10  var arr=func();
11  for(var i=0;i<5;i++){
12      console.log(arr[i]());//这时会返回五个5
13  }
复制代码

这时我们是否也会感到同样的疑惑,这不是应该返回0-4吗?这正是闭包在应用中的一个坑,这时,我们可以这样理解,当往arr[0]中存放第一个闭包函数时,函数并没有执行,那么这时闭包中就存放了i的一个链接,它也不知道i的值是多少,当func执行完了,这时闭包中存放在i的一个作用域链接,当执行arr数组中的函数时,由于数组中的函数没有i这个变量,所以它要向上一级去寻找,这时,它就找到了func中,而此时func中i的值已经变为5了,所以输出的每一个数都是5.那么有因就有果,有好就有坏,有错误我们就有方法去解决:

复制代码
 1 function func(){
 2      var arr=[];
 3      for (var i=0;i<5;i++){
 4          arr[i]=(function (num){
 5              return i;
 6          })(i);//这时使用自调用函数
 7      }
 8      return arr;
 9  }
10  var arr=func();
11  for(var i=0;i<5;i++){
12      console.log(arr[i]);//这时会输出0-4
13  }
复制代码

修改的方法一:就是将闭包改为一个自调用函数这时,传入arr[i]中的不再是一个函数,而是传入的参数i,以此类推,我们还可以返回一个闭包,只不过要让闭包中存放一个确定的值,而不再是一个变量:

复制代码
 1 function func(){
 2      var arr=[];
 3      for (var i=0;i<5;i++){
 4          arr[i]=function (num){
 5              return function(){
 6                  return num;
 7              }
 8          }(i);
 9      }
10      return arr;
11  }
12  var arr=func();
13  for(var i=0;i<5;i++){
14      console.log(arr[i]());//这时会输出0-4
15  }
复制代码

使用这种方案的时候,用新创建的函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,所以在使用闭包时,尽量不要将后续会发生变化的变量放到闭包中.同时,闭包不仅会改变函数中的变量的使用范围变广,它同时也会改变this的作用域:

复制代码
 1  var name="kkkk";
 2 function People(){
 3     this.name="lala",
 4     this.getName=function(){
 5         return function(){
 6             alert(this.name)
 7         };
 8     }
 9 }
10 var xiaoMing=new People();
11 xiaoMing.getName()();//这时会弹出kkkk
复制代码

这时,当我们返回一个闭包时,它当中存放的this绑定的对象是window,而不是People,其中的原理:我的理解就是,当你返回一个函数时,它其中的this是封闭在其中的那时候还没有执行函数,所以this的指向还没有确定,当在对象外执行闭包时,this回去找它要指向的对象,这时它就会指向window,我们可以用that来捕获this:

复制代码
 1  var name="kkkk";
 2 function People(){
 3     this.name="lala",
 4     this.getName=function(){
 5         var that=this;
 6         return function(){
 7             alert(that.name)
 8         };
 9     }
10 }
11 var xiaoMing=new People();
12 xiaoMing.getName()();
复制代码

这时再输出就是lala了,当然,我们不仅可以使用这种方式来捕获this,我们还可以使用之前所学的对象冒充的方法来执行函数:

复制代码
 1 var name="kkkk";
 2 function People(){
 3     this.name="lala",
 4     this.getName=function(){
 5         return function(){
 6             alert(this.name)
 7         };
 8     }
 9 }
10 var xiaoMing=new People();
11 xiaoMing.getName().apply(xiaoMing,[]);
复制代码

使用这种方式同样可以达到我们想要的目的,同时,我们使用call也是可以的.到了这里,大家应该对闭包有了一个初步的了解吧,闭包这种东西,可能理解的时候好理解一些,但是当我们实际应用的时候,刚开始可能会感觉到十分的吃力,但我们应该相信,熟能生巧,而且闭包这种东西,可能我们每天都在用,可对于它内在的原理还是一知半解,这个一定要多做练习,多做实验,当你有疑问的时候,就把代码打下来去运行一下,看一下结果,再想一下为什么会是这种结果,大家,一起加油!

 

本博文是博主原创,如有转载请说明出处!


JAVASCRIPT中的对象继承关系

  相信每个学习过其他语言的同学再去学习JavaScript时就会感觉到诸多的不适应,

这真是一个颠覆我们以前的编程思想的一门语言,先不要说它的各种数据类型以及表达

式的不同了,最让我们头疼,恐怕就是面向对象的部分了,在JavaScript中,是没有给定一

个创建对象的关键词的,它不像Java中一个class就可以创建一个对象,在JavaScript中,

对象是一个十分松散的的key-value对的组合,通常,我们在创建对象时,可以通过{}来直

接生成一个对象,就像我们之前所学的,对象中有属性,有行为,这里我们称为方法,那我们

就先来创建一个对象:

          var zhangSan ={name:"张三",age:14}

这就是一个简单的对象,这种方式的优点是简单直接,一眼就可以看出来他的属性和方法

但是,这种方法的缺点也很明显,当我们要创建多个对象时,我们就需要一个一个的去创建,

一个一个的去赋值,这是十分麻烦而且也不太显示的方法,那么我们可以用工厂法来创建:

                         

复制代码
 1 function createPeople(name,food){
 2     var people = new object();
 3     people.name=name;
 4     people.eat=function(food){
 5      alert(food);
 6      }
 7     return people;
 8 }
 9 var zhangSan=createPeople("zhangSan","豆腐");
10 zhangSan.eat();//这时会弹出豆腐.
复制代码

这种方法可以创建多个对象,用个俗一点的话说,就是你想要造多少个人就可以造多少个人,

哈哈,那么你以为你这是很好的创建对象的方法了吗?错了,现在我们又有一个问题,就是,我

们现在用的这种工厂方法,你并不知道你创建的是一个什么样的类,你创建的people归根结

底还是由new object()来创建的,和用字面量的方式定义一个类的方法并没有本质上的区别,

同样的,我也可以由这个方法创建一条狗,一只猫,一头大象,一条板凳,等等,这样子,类的意义

还有什么存在的必要呢,这时,就出现了用构造方法创建类的方法见代码2:

复制代码
 1 function createPeople(name,age,food){
 2     this.name=name;
 3     this.age=age;
 4     this.food=food;
 5     this.eat=function(){
 6      alert(this.name+"爱吃"+this.food;
 7     }
 8 }
 9 var zhangSan = new createPeople("张三",13,"豆腐");
10 zhangSan.eat();//这是会弹出张三爱吃豆腐这句话
复制代码

采用这种方法,我们可以避免了上边说的那种不知道创建的是什么类型的对象的那种情况,这

里我们要来介绍一下构造方法来创建对象,如果不加new,createPeople()只是一个非常普通

的函数,但是在它前边加上new之后,这一切都会变得不同,代码2中第九行的那句话可以分解

为两句话来理解:

1 var zhangSan={};//第一步首先创建一个空对象,
2 createPeople.apply(zhangSan arguments);//这一步将函数中的this绑定到zhangSan上,然后就是执行函数中的代码

好了,采用构造函数创建对象,我们可以将这个函数理解成一个类,由这个类可以创造属于它的

对像,而不是像上边的工厂方法那样每个对象都没有明确的分类,了解javascript内存管理机智

的我们应该都知道,在Javascript中,你每次创建一个对象,堆中就要为这个对象的方法和属性

分配一个内存空间,但是对于很多对象来说,它们的属性大都不尽相同,但是很多时候它们的很

多方法都是相同的,不同的只是传入的参数的不同而已,这样子是很浪费我们的内存的,这时候,

我们就先去了解一下对象的原型链:

  每个函数内都有一个prototype属性,这个prototype属性执行它们的prototype对象,而

每一个函数的protocoltype对象中都有一个constructor属性,这个属性指向这个函数本身,

所以问题就来,这是一个很绕口的问题,那么我们就来张图理解一下:

那你说这跟我创建的每一个对象上的方法有神马关系呢?当然有关系了,当我们使用new一个构造方法来创建一个对象的时候,我们创建的那个对象中,也会有一个_proto_属性,这个属性指向构造函数的原型对象,也就是说,在我们对象实例和构造函数之间,它们都指向对象原型,我们每一个对象中都可以继承构造函数原型对象中的属性和方法.那么这就引出了我们要说的内容,就是将我们每个对象都需要的方法放到我们的prototype对象中:

复制代码
1 function createPeo(userName,hobby){
2     this.userName=userName;
3     this.hobby=hobby;
4     createPeo.prototype.eat=function (){//在对象原型上定义函数,来节省内存;
5         alert(this.userName+"喜欢吃"+this.hobby);
6     }
7 }
8 var shang  = new createPeo("张珊","炒面");
9 var liSi   = new createPeo("李四","撸串");//这样就可以极大的节省了我们的内存,而且每一个对象都能调用到我们的方法 .
复制代码

好了,现在我们已经成功的创建了一个类,并且由这个类创建了我么所需要的对象,那么我们都知道,有了类,有了对象,我们总是会下意识的去背了起来,累的三大特征:封装,继承,多态,那么在JavaScript中,类的封装是怎么来实现的呢?这个在明天讲述闭包的时候我们再进行讨论,今天我们先来讨论一下类中都有的继承,在我们JavaScript中,类的继承并不像Java中的那样用一个class...extend就行了,在JavaScript中,我们并没有一个class继承的概念,我们用的是很随意的一个原型链的执行继承,就是通过prototype属性来指向想要继承的对象的原型对象,那么我们就开始想,那么我直接用prototype属性指向不就行了吗?

复制代码
1 function Dog(name,age){
2     this.name=name;
3     this.age=age;
4 }
5 function LittleDog(){}
6 LittleDog.prototype=Dog.prototype;//你以为这样就完成了原型继承,只不过,这样子直接指向原型链的话,你在修改子类的时候,相应的就会修改父类.并且,既然他两个都指向一个原型,那你直接用Dog来创建不就完了了吗,这就失去了继承的意义.
复制代码

所以,直接将原型链指向父类是不对的,说到这里,我们就可以用一个空函数来作为中转完成原型链的继承:

复制代码
function Dog(name,age){
    this.name=name;
    this.age=age;
    Dog.prototype.eat=function(){alert("我今年"+this.age+"岁"); }
}
function LittleDog(name,age){
    this.name=name;
    this.age=age;
}


function F(){}
F.prototype=Dog.prototype;
LittleDog.prototype=new F();
LittleDog.prototype.constructor=LittleDog; 
var xiaoGou=new LittleDog("旺财",13);
xiaoGou.eat();//这时用小狗调用父类中的方法,返回一个弹框,说明继承成功.
复制代码

通过这种中间函数来完成类的继承.它并没有改变原有Dog中的原型链条,同时也完成了继承,如果想要在LitteDog中添加原型方法,就可以在new F()中创建方法和属性,当然,我们也可以将这种继承方法封装成一个函数,这样,我们程序的执行效率和美观程度就大大的提升了:

复制代码
1 function inherits(child,parent){
2      var f = function(){}
3      f.prototype = parent.prototype;
4      child.prototype = new f();
5      child.prototype.constructor=child;
6 }//这是一个封装函数,再进行类的继承时,可以用这个函数来套用.

复制代码

 在类的继承中,除了我们上边说的这种原型继承,还有类继承,以及类继承与原型继承的混用,那么我将在下一章给大家讲解.本博文是博主自创,如果转载请说明出处,谢谢!在下学艺不精,文中如果有什么错误还请高手指正,谢谢!


JAVASCRIPT中的对象继承关系(2)

  在上一章中,我们着重介绍了JavaScript中类之间的原型继承关系:原型继承对于继承类中的方法来说是很方便的.那么我们今天就来看一下继承中的类继承以及类继承和原型继承的混用,所谓类继承,就是使用call或者apply方法来进行冒充继承:

复制代码
1 function Desk(size,height){
2     this.size=size;
3      this.height=height;
4 }
5 function MJDesk(size,height){
6     Desk.call(this,size,height);//这种就叫类继承.
7 }
8  var mj = new MJDesk(10,123);
复制代码

  像上面这种就是我们要使用的类继承,用这种继承,我们可以访问类中的方法和属性,但是无法访问父类原型中的方法和属性,这种方法别名冒充继承,顾名思义,就是一个假的继承,所以,假的当然不能继承真的原型,所以,类继承的缺点也是很明显的,当我们使用的多的时候,就会造成内存的浪费.由此,我们就出现了类继承和原型继承混合使用的方法:

复制代码
 1 function Desk(size,height){
 2      this.size=size;
 3      this.height=height;
 4  }
 5  function MJDesk(size,height){
 6      Desk.call(this,size,height);//这种就叫类继承.
 7  }
 8 MJDesk.prototype=new Desk();//原型继承
 9 var mj = new MJDesk(12,12);
10 //当然此处的原型继承用我们上一章讲的使用一个空函数进行继承的方式更好.
复制代码

当然,我们现在使用最多的就是这种二者混合用的方式了!

JavaScript:JSON

JSON是一种数据格式,它并不从属于JavaScript,很多语言都有JSON的解析器和序列化器。

语法

JSON可以表示三种类型:

  • 简单值:使用与JavaScript相同的语法,可以在JSON中表示字符串、数值、布尔值和null。
  • 对象:表示一组无序的键值对。键值对中的值可以是简单值,也可以是复杂数据类型。
  • 数值:表示一组有序的值的列表,数组的值可以是任意类型。

JSON不支持变量、函数或对象实例,是一种表示结构化数据的格式。

简单值

JSON数据形式:

5    // 数值
"Hello JavaScript"    // 字符串
null

对象

与JavaScript不同,JSON中的对象要求给属性加上引号

{
    "name": "萧萧弈寒";
    "age": 100
}

// 属性的值也可以是复杂类型
{
    "school": {
        "name": "hafo", 
        "location": "Harbin"
    }
}

数组

JSON表示数组:

[100, "萧萧弈寒", true]

JSON数组没有变量和分号。

把数组和对象结合,可以构造复杂的数据集合:

[
    {
        "title":"Java从入门到放弃",
        "authors":[
            "张三"
        ],
        edition: 2
    },
    {
        "title":"MySQL从删库到跑路",
        "authors":[
            "李四"
        ],
        edition: 3
    },
    {
        "title":"Dreamweaver从安装到卸载",
        "authors":[
            "王五"
        ],
        edition: 4
    }
]

解析与序列化

JSON对象

JSON对象有两个方法:

  • stringfy():把JavaScript对象序列化为JSON字符串
  • parse():。把JSON字符串解析为原生JavaScript值

示例:

var book = {
    "title":"Java从入门到放弃",
    "authors":[
        "张三"
    ],
    edition: 2
};
var jsonText = JSON.stringify(book);
alert(jsonText);    // {"title":"Java从入门到放弃","authors":["张三"],"edition":2}

上面的例子将一个JavaScript对象序列化为一个JSON字符串。默认情况下,JSON.stringify()不包含任何字符或缩进。

将JSON字符串直接传递个JSON.parse()可以得到相应的JavaScript值。

var newBook = JSON.parse(jsonText);    

book与newBook具有相同的属性,但是彼此是相互独立的。

序列化选项

JSON.stringify()还可以接收两个参数:

  • 第一个参数:过滤器,一个数组或一个函数。
  • 第二个参数:一个选项,表示是否在JSON字符串中保留缩进
过滤结果

如果参数是数组,JSON.stringify()的结果只包含数组中列出的属性。

var book = {
    "title":"Java从入门到放弃",
    "authors":[
        "张三"
    ],
    edition: 2
};
var jsonText = JSON.stringify(book, ["title", "authors"]);
console.log(jsonText);  // {"title":"Java从入门到放弃","authors":["张三"]}

如果参数是函数,传入的函数接收两个参数,属性名和属性值,根据属性名可以知道如何处理属性。属性名是字符串,属性值并非键值对的值,键名可以是空字符串。

返回的值是相应键的值,如果函数返回undefined,那么该属性就会被忽略。

var book = {
    "title":"Java programming",
    "authors":[
        "张三",
        "李四"
    ],
    edition: 2
};
var jsonText = JSON.stringify(book, function (key, value) {
    switch (key) {
        case "authors":
            return value.join("-");        // 用“-”分割数组
        case "edition":
            return undefined;
        default :
            return value;
    }
});
console.log(jsonText);    // {"title":"Java programming","authors":"张三-李四"}
字符串缩进

JSON.stringify()的第三个参数用于控制结果中的缩进和空白符。如果是数值,表示每格缩进的空格数。

var book = {
    "title":"Java programming",
    "authors":[
        "萧萧弈寒",
    ],
    edition: 2
};
var jsonText = JSON.stringify(book, null, 4);
console.log(jsonText);

如果是字符串,这个字符串将用作JSON字符串的缩进符

var book = {
    "title":"Java programming",
    "authors":[
        "萧萧弈寒",
    ],
    edition: 2
};
var jsonText = JSON.stringify(book, null, "__");
console.log(jsonText);

【显示结果】:

{
__"title": "Java programming",
__"authors": [
____"萧萧弈寒"
__],
__"edition": 2
}

缩进字符串最多只能包含10个字符。

toJSON()方法

JSON.stringify()并不能满足所有对象进行序列化的需求。可以给对象定义toJSON()方法,返回其自身的JSON数据格式。

示例:

var book = {
    "title":"Java programming",
    "authors":[
        "萧萧弈寒",
    ],
    edition: 2,
    toJSON: function () {
        return "==="+this.title+"===";
    }
};
var jsonText = JSON.stringify(book);
console.log(jsonText);    // "===Java programming==="

解析选项

JSON.parse()方法也可以接收另一个参数,该参数是一个函数,将在每个键值对上调用。如果返回undefined,表示从结果中删除相应的键,如果返回其他值,则将该值插入到结果中。

var book = {
    "title":"Java programming",
    "authors":[
        "萧萧弈寒",
    ],
    edition: 2,
    year: 2017,
    releaseDate: new Date(2017, 11, 1)
};
var jsonText = JSON.stringify(book);

var newBook = JSON.parse(jsonText, function (key, value) {
    if (key == "releaseDate") {
        return new Date(value);
    } else {
        return value;
    }
});
console.log(newBook.releaseDate.getFullYear());    // 2017

上面新增了releaseDate属性,该属性保存着一个Date对象。经过序列化后,变成了一个有效的JSON字符串,然后又在newBook中还原成一个Date对象。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值