Blog Logo

JavaScript高级(一)

写于2015-10-19 02:06 阅读耗时12分钟 阅读量


当自己能力越来越高的时候,承担的东西就会越来越多。 今天告别了华为Telkomsel项目组,告别了无休止加班导致自己没精力学习和总结的机会。 JavaScript高级在我看来就是自己做项目时的一些实战用法+高级原理。 我将从下面几点开始讲解:

  • 作用域
  • 闭包
  • 关键词this
  • 原型链
  • 继承
  • 异步
  • 高级用法

1.作用域

1)变量作用域

请看如下代码:

var a=1;
function test(){
    console.log(a);
}
test();

输出结果为:

1

请再看如下代码:

function test(){
    var a=1;
}
test();
console.log(a);

输出结果为:

ReferenceError: a is not defined

总结:

a. 变量的作用域分成两种:局部变量和全局变量; b. 函数内部可以读取全局变量,函数外部不能读取函数内部的局部变量;


2)函数作用域

首先看下我之前遇到的一道面试题,简洁而经典,代码如下:

var a=1;
function test(){
    a=2;
    console.log(a);
    console.log(this.a);
    var a;
    console.log(a);
}
test();
new test();

输出结果依次为:

2,1,2
2,undefined,2

为什么会是这样的结果呢?请听我一一到来。 原因分析: 先分析第一行:看总结a、b、c,可以将上述代码重写如成:

var a=1;
function test(){
    var a=2;
    console.log(a);
    console.log(this.a);
    console.log(a);
}
test();

答案一下子就懂了吧。

再分析第二行:看总结d,可以将上述代码重写如成:

var a=1;
function test(){
    var a=2;
    console.log(a);//2
    //console.log(this.a);
    console.log(a);//2
}
var t=new test();
t.a //undefined

当使用new关键字时,会生成test的实例化对象t,如果直接是new test(),生成的是匿名对象。this指该对象,由于没有a这个属性,因此是undefined。 总结:

a. 在JavaScript中没有块级作用域,而是函数作用域; b. 函数作用域:变量在声明它的函数以及这个函数里面的函数内都是有定义的。 c. 对于直接定义的函数,this指向全局window对象; d. 对于对象的方法和属性,this指向实例化对象; 简单一句话:在JavaScript中,变量提前声明,局部变量的优先级高于全局变量。


3)作用域链

请看如下代码:

var name="a";
function b(){
    var name="b";
    function c1(){
        var name="c";
        console.log(name);
    }
    function c2(){
        console.log(name);
    }
    c1();
    c2();
}
b();

输出结果为:

c 
b

如果把第三行干掉,代码如下:

var name="a";
function b(){
    function c1(){
        var name="c";
        console.log(name);
    }
    function c2(){
        console.log(name);
    }
    c1();
    c2();
}
b();

输出结果为:

c 
a

当执行c1时,将创建函数c1的执行环境(调用对象),并将该对象置于链表开头,然后将函数b的调用对象链接在之后,最后是全局对象。然后从链表开头寻找变量name,很明显name是"c"。 但执行c2时,作用域链是:c2->b->window,所以name是”a"。

总结:

在JavasScript中,函数也是对象,每个函数都有自己的执行上下文环境,当代码在这个环境中执行时,会创建变量对象的作用域链,作用域链是一个对象列表或对象链,它保证了变量对象的有序访问。如:在控制台可以用window.b访问到刚刚写的b函数。 作用域链的前端是当前代码执行环境的变量对象,常被称之为“活跃对象”,变量的查找会从第一个链的对象开始,如果对象中包含变量属性,那么就停止查找,如果没有就会继续向上级作用域链查找,直到找到全局对象中:作用域链的逐级查找,也会影响到程序的性能,变量作用域链越长对性能影响越大,这也是我们尽量避免使用全局变量的一个主要原因。如:在使用document等内置对象时,先声明出来。

注意:如果在函数内声明变量时一定要加var,否则你声明的是全局变量。


2.闭包

什么是闭包呢?在阮一峰的文章中提到过:

闭包就是能够读取其他函数内部变量的函数。 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

一个典型的闭包代码:

function createPeople(){
  var name='';
  return {
    set:function(setName){
      name=setName||'ww';
    },
    get:function(){
      return 'name='+name;
    }
  }
}
var p1=new createPeople();
p1.set();
p1.get();//'name=ww'
var p2=new createPeople();
p2.set('zl');
p2.get();//'name=zl'

请看如下代码:

function a(){
   var num=999;
   add=function(){
      num+=1;
   }
   function b(){
     console.log(num);
   }
   return b;
}
var result=a();
result();//999
add();
result();//1000

总结:

a.闭包可以从函数外部读取函数内部的变量。 b.闭包可以将变量的值存放在内存中。

再看如下代码:

function test(){
  var result=[];
  for(var i=0;i<5;i++){
    result[i]=function(){
      return i;
    };
  }
  return result;
}
test()[0](); //5
test()[1](); //5

为什么全是5呢,因为每个函数的作用域链中都保存着对外部函数的活跃对象,他们都引用着同一变量i,当外部函数返回时,此时的i值为5,所以内部的每个函数i的值也为5。 怎样解决该问题呢? 请看如下代码:

function test(){
  for(var i=0;i<5;i++){
    (function(){
       console.log(i);
    })(i);
  }
}
test(); 

总结:

a. 在使用闭包时,由于作用域链机制的影响,闭包只能取得外部函数的最后一个值。 b. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。


3.关键词this

this的用法可以分为以下三种: 1)函数的调用 请看如下代码:

function test(){
  this.a=1;
  console.log(this.a);
}
test(); //1

总结:

对于直接定义的函数,this指向全局window对象。

2)对象方法的调用 请看如下代码:

var people={
   name:'ww',
   say:function(){
      console.log('My name is '+this.name);
   }
}
people.say();//'My name is ww'

function People(){
  this.name='ww';
  this.say=function(){
    console.log('My name is '+this.name);
  }
}
var p=new People();
p.say();//'My name is ww'

总结:

对于对象的方法和属性,this指向实例化对象。

3)call/apply的调用 call/apply第一个参数表示改变后的调用这个函数的对象。 如果call/apply的参数为空时,默认调用全局window对象。 请看如下代码:

var name='global';
function People(){
  this.name='ww';
  this.say=function(){
    console.log('My name is '+this.name);
  }
}
var p=new People();
p.say.apply(p);//'My name is ww'
p.say.apply(); //'My name is global'

p.say.call(p);//'My name is ww'
p.say.call(); //'My name is global'

总结:

用call/apply方法能改变this指向


4.原型链

请看如下代码:

function People(){
  this.name='ww';
  this.say=function(){
    console.log('My name is '+this.name);
  }
}
var p=new People();
p.__proto__ === People.prototype //true
People.prototype.__proto__ === Object.prototype //true
Object.prototype.__proto__ //null


People.__proto__ === Function.prototype //true
Function.prototype.__proto__ === Object.prototype //true
Object.prototype.__proto__ //null

Function.__proto__ === Function.prototype //true
Function.prototype.__proto__  == Object.prototype //true
Object.prototype.__proto__ //null

控制台显示结果如下: 此处输入图片的描述

总结:

a. 使用__proto__串起来的链叫做原型链。如上面代码得到的原型链如下:

  1. p -> _proto_ -> People.prototype -> _proto_ -> Object.prototype -> _proto_ -> null;
  1. People -> _proto_ -> Function.prototype -> _proto_ -> Object.prototype -> _proto_ -> null;
  2. Function -> _proto_ -> Function.prototype -> _proto_ -> Object.prototype -> _proto_ -> null;

b. JS在创建对象的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的prototype属性。 c. 原型链__proto__属性才是真正的原型链的实际指针。 d. 原型链的顶端是Object.prototype.__proto__且它的值为null。

请再看如下代码:

function People(name){
  this.name=name;
  this.say=function(){
    console.log('My name is '+this.name);
  }
}
People.prototype = {category:'animals'}
var ww=new People('ww');
var zl=new People('zl');
ww.category;//'animals'
zl.category;//'animals'
ww.constructor === People.prototype.constructor;//true

总结:

a. prototype属性包含一个对象,所有实例对象需要共享的属性和方法都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。 b. new关键字实质是用构造函数生成实例对象,实例化对象有三步:

  1. 创建对象:ww=newPeople('ww'); 2.将ww的内部__proto__指向构造它的函数People的prototype,使得ww.constructor===People.prototype.constructo永远成立;
  1. 将ww作为this去调用构造函数People,从而设置其对象属性和方法并初始化。

请再看如下代码:

function People(name){
  this.name=name;
  this.say=function(){
    console.log('My name is '+this.name);
  }
}
People.prototype = {category:'animals'}
function Dog(name){
  this.name=name;
  this.say=function(){
    console.log("My dog's name is "+this.name);
  } 
}
var ww=new People('ww');
var wc=new Dog('wc');

ww instanceof People;//true
ww instanceof Function;//false
ww instanceof Object;//true
People instanceof Object;//true

People.prototype.isPrototypeOf(ww);//true
People.prototype.isPrototypeOf(wc);//false

ww.hasOwnProperty('name');//true
ww.hasOwnProperty('category');//false

'name' in ww;//true
'category' in ww;//true

var wwProto=Object.getPrototypeOf(ww);
var wcProto=Object.getPrototypeOf(wc);
wwProto.category; //'animals'
wwProto.category; //undefined

总结:

a. instanceof可以用来检测某个对象是不是另一个对象的实例,也可以用来检测多重继承。 b. isPrototypeOf()方法用来判断某个proptotype对象和某个实例之间的关系。 c. hasOwnProperty()方法用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。 d. in可以用来判断某个实例是否含有某个属性,不管是不是本地属性。 e. Object.getPrototypeOf()方法用来获取对象的原型。


这篇终于写完了,可是才写到第四点,在下篇中从第五点继承继续给大家讲解哈。

Headshot of Maxi Ferreira

怀着敬畏之心,做好每一件事。