当自己能力越来越高的时候,承担的东西就会越来越多。 今天告别了华为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__串起来的链叫做原型链。如上面代码得到的原型链如下:
- p -> __proto__ -> People.prototype -> __proto__ -> Object.prototype -> __proto__ -> null;
- People -> __proto__ -> Function.prototype -> __proto__ -> Object.prototype -> __proto__ -> null;
- 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关键字实质是用构造函数生成实例对象,实例化对象有三步:
- 创建对象:ww=newPeople('ww'); 2.将ww的内部__proto__指向构造它的函数People的prototype,使得ww.constructor===People.prototype.constructo永远成立;
- 将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()方法用来获取对象的原型。
这篇终于写完了,可是才写到第四点,在下篇中从第五点继承继续给大家讲解哈。