当自己能力越来越高的时候,承担的东西就会越来越多。 今天告别了华为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;
  2. People -> __proto__ -> Function.prototype -> __proto__ -> Object.prototype -> __proto__ -> null;
  3. 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永远成立;
  2. 将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()方法用来获取对象的原型。


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