求知若饥,虚心若愚。
当自己能力越来越高的时候,承担的东西就会越来越多。
今天告别了华为Telkomsel项目组,告别了无休止加班导致自己没精力学习和总结的机会。
JavaScript高级在我看来就是自己做项目时的一些实战用法+高级原理。
我将从下面几点开始讲解:
- 作用域
- 闭包
- 关键词this
- 原型链
- 继承
- 异步
- 高级用法
请看如下代码:
1 | var a=1; |
输出结果为:
1 | 1 |
请再看如下代码:
1 | function test(){ |
输出结果为:
1 | ReferenceError: a is not defined |
总结:
a. 变量的作用域分成两种:局部变量和全局变量;
b. 函数内部可以读取全局变量,函数外部不能读取函数内部的局部变量;
首先看下我之前遇到的一道面试题,简洁而经典,代码如下:
1 | var a=1; |
输出结果依次为:
1 | 2,1,2 |
为什么会是这样的结果呢?请听我一一到来。
原因分析:
先分析第一行:看总结a、b、c,可以将上述代码重写如成:
1 | var a=1; |
答案一下子就懂了吧。
再分析第二行:看总结d,可以将上述代码重写如成:
1 | var a=1; |
当使用new关键字时,会生成test的实例化对象t,如果直接是new test(),生成的是匿名对象。this指该对象,由于没有a这个属性,因此是undefined。
总结:
a. 在JavaScript中没有块级作用域,而是函数作用域;
b. 函数作用域:变量在声明它的函数以及这个函数里面的函数内都是有定义的。
c. 对于直接定义的函数,this指向全局window对象;
d. 对于对象的方法和属性,this指向实例化对象;
简单一句话:在JavaScript中,变量提前声明,局部变量的优先级高于全局变量。
请看如下代码:
1 | var name="a"; |
输出结果为:
1 | c |
如果把第三行干掉,代码如下:
1 | var name="a"; |
输出结果为:
1 | c |
当执行c1时,将创建函数c1的执行环境(调用对象),并将该对象置于链表开头,然后将函数b的调用对象链接在之后,最后是全局对象。然后从链表开头寻找变量name,很明显name是”c”。
但执行c2时,作用域链是:c2->b->window,所以name是”a”。
总结:
在JavasScript中,函数也是对象,每个函数都有自己的执行上下文环境,当代码在这个环境中执行时,会创建变量对象的作用域链,作用域链是一个对象列表或对象链,它保证了变量对象的有序访问。如:在控制台可以用window.b访问到刚刚写的b函数。
作用域链的前端是当前代码执行环境的变量对象,常被称之为“活跃对象”,变量的查找会从第一个链的对象开始,如果对象中包含变量属性,那么就停止查找,如果没有就会继续向上级作用域链查找,直到找到全局对象中:作用域链的逐级查找,也会影响到程序的性能,变量作用域链越长对性能影响越大,这也是我们尽量避免使用全局变量的一个主要原因。如:在使用document等内置对象时,先声明出来。
注意:如果在函数内声明变量时一定要加var,否则你声明的是全局变量。
什么是闭包呢?在阮一峰的文章中提到过:
闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
一个典型的闭包代码:
1 | function createPeople(){ |
请看如下代码:
1 | function a(){ |
总结:
a.闭包可以从函数外部读取函数内部的变量。
b.闭包可以将变量的值存放在内存中。
再看如下代码:
1 | function test(){ |
为什么全是5呢,因为每个函数的作用域链中都保存着对外部函数的活跃对象,他们都引用着同一变量i,当外部函数返回时,此时的i值为5,所以内部的每个函数i的值也为5。
怎样解决该问题呢?
请看如下代码:
1 | function test(){ |
总结:
a. 在使用闭包时,由于作用域链机制的影响,闭包只能取得外部函数的最后一个值。
b. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。
this的用法可以分为以下三种:
1)函数的调用
请看如下代码:
1 | function test(){ |
总结:
对于直接定义的函数,this指向全局window对象。
2)对象方法的调用
请看如下代码:
1 | var people={ |
总结:
对于对象的方法和属性,this指向实例化对象。
3)call/apply的调用
call/apply第一个参数表示改变后的调用这个函数的对象。
如果call/apply的参数为空时,默认调用全局window对象。
请看如下代码:
1 | var name='global'; |
总结:
用call/apply方法能改变this指向
请看如下代码:
1 | function People(){ |
控制台显示结果如下:
总结:
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。
请再看如下代码:
1 | function People(name){ |
总结:
a. prototype属性包含一个对象,所有实例对象需要共享的属性和方法都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
b. new关键字实质是用构造函数生成实例对象,实例化对象有三步:
- 创建对象:ww=newPeople(‘ww’);
- 将ww的内部__proto__指向构造它的函数People的prototype,使得ww.constructor===People.prototype.constructo永远成立;
- 将ww作为this去调用构造函数People,从而设置其对象属性和方法并初始化。
请再看如下代码:
1 | function People(name){ |
总结:
a. instanceof可以用来检测某个对象是不是另一个对象的实例,也可以用来检测多重继承。
b. isPrototypeOf()方法用来判断某个proptotype对象和某个实例之间的关系。
c. hasOwnProperty()方法用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。
d. in可以用来判断某个实例是否含有某个属性,不管是不是本地属性。
e. Object.getPrototypeOf()方法用来获取对象的原型。
这篇终于写完了,可是才写到第四点,在下篇中从第五点继承继续给大家讲解哈。