1.作用域
参考 https://juejin.im/post/5abb99e9f265da2392366824#heading-0,https://www.jianshu.com/p/e2c72357ed12
- 1.1全局作用域
生命周期将存在于整个程序之内;能被程序中任何函数或者方法访问;在 JavaScript 内默认是可以被修改的。
分为显示声明:<script type="text/javascript">
var testValue = 123;
var testFunc = function () { console.log('just test') };
/**---------全局变量会挂载到 window 对象上------------**/
console.log(window.testFunc) //
ƒ () { console.log('just test') }
console.log(window.testValue) // 123 </script> - 隐式声明:不带有声明关键字的变量,JS 会默认帮你声明一个全局变量!!!
<script type="text/javascript">
function foo(value) {
result = value + 1; // 没有用 var 修饰
return result;
};foo(123); // 124
console.log(window.result); // 124 <= 挂在了 window全局对象上
</script>
其实,我们写的函数如果不经过封装,也会是全局变量,他的生命周期也就是全局作用域; - 1.2 函数作用域
函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域!!!function bar() {
var testValue = 'inner';
}console.log(testValue); // 报错:ReferenceError: testValue is not defined
通过 return 访问函数内部变量:
通过 闭包 访问函数内部变量:
立即执行函数:这是个很实用的函数,很多库都用它分离全局作用域,形成一个单独的函数作用域;
<script type="text/javascript">
(function() {
var testValue = 123;
var testFunc = function () { console.log('just test'); };
})();
console.log(window.testValue); // undefined
console.log(window.testFunc); // undefined
</script>
- 词法作用域:
词法作用域是指一个变量的可见性,及其文本表述的模拟值(《JavaScript函数式编程》);testValue = 'outer';
function afun() {
var testValue = 'middle';
console.log(testValue); // "middle"
function innerFun() {
var testValue = 'inner';
console.log(testValue); // "inner"
}
return innerFun();
}afun();
console.log(testValue); // "outer"
当我们要使用声明的变量时:JS引擎总会从最近的一个域,向外层域查找;var testValue = 'outer';
function foo() {
console.log(testValue); // "outer"
}function bar() {
var testValue = 'inner';
foo();
}bar();
显然,当 JS 引擎查找这个变量时,发现全局的 testValue 离得更近一些,这恰好和 动态作用域 相反;
-
动态作用域:
在 JavaScript 中的仅存的应用动态作用域的地方:
this
引用,所以这是一个大坑!!!!!动态作用域,作用域是基于调用栈的,而不是代码中的作用域嵌套;
作用域嵌套,有词法作用域一样的特性,查找变量时,总是寻找最近的作用域;
参考https://www.jianshu.com/p/d7fbf97a0316
闭包是指那些能够访问独立(自由)变量的函数 (变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以“记忆”它被创建时候的环境。要理解函数闭包,就要先知道这两条特性:
- 函数外部的代码无法访问函数体内部的变量,而函数体内部的代码可以访问函数外部的变量。
- 即使函数已经执行完毕,在执行期间创建的变量也不会被销毁,因此每运行一次函数就会在内存中留下一组变量。(js当然会有垃圾回收机制,不过如果它发现你正在使用闭包,则不会清理可能会用到的变量)
先看个例子:
我初学的时候犯了一个错误,就是认为outter是闭包函数(因为我以为将整个闭包结构“包”起来的函数就是闭包函数),但其实根据定义,被返回的show才是闭包函数,也就是那个可以在外部访问“私有成员”的函数。function outter() { var private= "I am private"; function show() { console.log(private); } return show; } var ref = outter(); // console.log(private); // 尝试直接访问private会报错:private is not defined ref(); // 打印I am private
让我们再看一个例子:function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); console.log(add5(2)); // 7 console.log(add10(2)); // 12
这个例子有意思的地方在于:makeAdder调用了两次!每运行一次makeAdder就会在内存中产生一组变量(也就是一个“环境”),每一个“环境”虽然结构相同,都有私有成员x和公有成员函数,但是这两个“环境”是互不干涉的。在这个例子中,第一个环境中x=5,第二个环境中x=10。
利用闭包的特性,可以实现模块模式。用一个闭包函数包裹模块的代码,将不需要暴露的变量隐藏起来(好处是不会污染全局变量空间),将别人要调用的方法return出去,就可以实现模块化了。实际上Node.js就是这么做的
还有几个例子看参考网页的详情
不要随便在函数中创建函数
除非明确你知道你自己需要使用闭包,否则,不要在函数中创建另一个函数,这样会造成速度和性能的浪费。
3. 变量提升
参考https://www.jianshu.com/p/e92c9938e22ajs的变量可见性与c语言不太一样。
在c中,变量的作用域是块级的,从声明的地方开始,到与它最近的块的结尾,就是这个变量的作用域:
#include <stdio.h> void main() { int i=2; while(i > 0) { int j=3; i--; } printf("%d/n", i); // 0 printf("%d/n", j); // 报错 }
而js则不一样:
'use strict';
function test() {
var i=2;
while(i > 0) {
var j=3;
i--;
}
console.log(i); // 0
console.log(j); // 3,在这里也可以访问到j!
}
test();
js变量(不包括函数)的作用域是函数级的,也就是说定义变量的整个函数区域都可以访问到它,即使这个变量是否定义在while、if代码块中也可以。就像我们上面的例子,j是定义在代码块中的,然而在代码块外也可以访问到。
不要忘记使用'use strict'模式!'use strict'能避免犯一些很坑的错误,也不会限制你大展身手。我们只讨论严格模式下的情况。
变量提升:变量(包括函数)的声明会被提升到本函数代码块的顶部。
4. this的理解
首先明确最重要的一点:当函数被调用的时候,调用函数的那个对象会被传递到执行上下文中,成为this的值。
函数 this 的指向与这个函数定义在哪个对象中无关,只与调用这个函数的对象有关,this是在调用时确定的,而不是定义时。
'use strict';
var obj = {
a:111,
fn: function fn() {
console.log(this);
}
}
var fn_ref = obj.fn;
obj.fn(); // Object {a: 111, fn: [Function: fn]}
fn_ref(); // undefined
console.log(this); // Window {…}
console.log(this.obj); // Object {a: 111, fn: [Function: fn]}
一个干扰性比较强的例子
'use strict';
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this);
}
}
}
o.b.fn(); // Object {a: 12, fn: [Function: fn] }
var j = o.b.fn;
j(); //undefined
不要被fn定义的位置给迷惑了,其实就是一个独立调用,相当于j.invoke(undefined, [])
5.apply, call与bind方法
参考https://juejin.im/post/582bcd36d203090067edb8a0
在JavaScript中,call
、apply
和bind
是Function
对象自带的三个方法,这三个方法的主要作用是改变函数中的this
指向。
- call
定义:调用一个对象的一个方法,以另一个对象替换当前对象。语法:
call([thisObj[,arg1[, arg2[, [,.argN]]]]])
说明:
call
方法可以用来代替另一个对象调用一个方法。call
方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。thisObj
的取值有以下4种情况:
(1) 不传,或者传null,undefined, 函数中的this指向window对象
(2) 传递另一个函数的函数名,函数中的this指向这个函数的引用
(3) 传递字符串、数值或布尔类型等基础类型,函数中的this指向其对应的包装对象,如 String、Number、Boolean
(4) 传递一个对象,函数中的this指向这个对象 -
apply()
语法:
apply([thisObj[,argArray]])
定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明:
如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。call 和 apply的区别
对于 apply、call 二者而言,作用完全一样,只是接受参数的方式不太一样。 -
bind
bind
是在EcmaScript5中扩展的方法(IE6,7,8不支持)bind()
方法与 apply 和 call 很相似,也是可以改变函数体内this
的指向。MDN的解释是:
bind()
方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入bind()
方法的第一个参数作为this
,传入bind()
方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。注意:
bind
方法的返回值是函数
6.模块化
参考地址https://www.cnblogs.com/lvdabao/p/js-modules-develop.html
https://www.imooc.com/article/20057 -
模块萌芽时代
用自执行函数来包装代码:
modA = function(){
var a,b; //变量a、b外部不可见
return {
add : function(c){
a + b + c;
},
format: function(){
//......
}
}
}()这样function内部的变量就对全局隐藏了,达到是封装的目的。但是这样还是有缺陷的,modA这个变量还是暴漏到全局了,随着模块的增多,全局变量还是会越来,为了避免全局变量造成的冲突,人们想到或许可以用多级命名空间来进行管理,于是,代码就变成了这个风格:
app.util.modA = xxx; app.tools.modA = xxx; app.tools.modA.format = xxx;
Yahoo的YUI早期就是这么做的
jQuery风格的匿名自执行函数,代码如下:
(function(window){
//代码window.jQuery = window.$ = jQuery;//通过给window添加属性而暴漏到全局
})(window);jQuery的封装风格曾经被很多框架模仿,通过匿名函数包装代码,所依赖的外部变量传给这个函数,在函数内部可以使用这些依赖,然后在函数的最后把模块自身暴漏给window。
如果需要添加扩展,则可以作为jQuery的插件,把它挂载到$上。
这种风格虽然灵活了些,但并未解决根本问题:所需依赖还是得外部提前提供、还是增加了全局变量。
-
CommonJS规范
该规范最初是用在服务器端的
node
的,前端的webpack
也是对CommonJS原生支持的。
具体来说,Modules/1.0规范包含以下内容:1). 模块的标识应遵循的规则(书写规范)
2). 定义全局函数require,通过传入模块标识来引入其他模块,执行的结果即为别的模块暴漏出来的API
3). 如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖
4). 如果引入模块失败,那么require函数应该报一个异常
5). 模块通过变量exports来向往暴漏API,exports只能是一个对象,暴漏的API须作为此对象的属性。
例子如下://math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};//increment.js
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};//program.js var inc = require('increment').increment; var a = 1; inc(a); // 2
优点:
CommonJS规范在服务器端率先完成了JavaScript的模块化,解决了依赖、全局变量污染的问题,这也是js运行在服务器端的必要条件。
缺点:此文主要是浏览器端js的模块化, 由于 CommonJS 是同步加载模块的,在服务器端,文件都是保存在硬盘上,所以同步加载没有问题,但是对于浏览器端,需要将文件从服务器端请求过来,那么同步加载就不适用了,所以,CommonJS是不适用于浏览器端的。
-
AMD规范
CommonJS规范加载模块是
同步
的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步
加载模块,允许指定回调函数
。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。而AMD规范的实现,就是大名鼎鼎的require.js
了。AMD标准中,定义了下面两个API:
1.require([module], callback) 2. define(id, [depends], callback)
即通过
define
来定义一个模块,然后使用require
来加载一个模块。 并且,require还支持CommonJS的模块导出方式。定义alert模块:
define(function () { var alertName = function (str) { alert("I am " + str); } var alertAge = function (num) { alert("I am " + num + " years old"); } return { alertName: alertName, alertAge: alertAge }; });
引入模块:
require(['alert'], function (alert) { alert.alertName('JohnZhu'); alert.alertAge(21); });
但是,在使用require.js的时候,我们必须要提前加载所有的依赖,然后才可以使用,而不是需要使用时再加载。
优点:适合在浏览器环境中异步加载模块。可以并行加载多个模块。
缺点:提高了开发成本,并且不能按需加载,而是必须提前加载所有的依赖。
- CMD规范
CMD规范是阿里的玉伯提出来的,实现js库为
sea.js
。 它和requirejs非常类似,即一个js文件就是一个模块,但是CMD的加载方式更加优秀,是通过按需加载
的方式,而不是必须在模块开始就加载所有的依赖。如下:define(function(require, exports, module) { var $ = require('jquery'); var Spinning = require('./spinning'); exports.doSomething = ... module.exports = ... })
优点:
同样实现了浏览器端的模块化加载。
可以按需加载,依赖就近。缺点:
依赖SPM打包,模块的加载逻辑偏重。其实,这时我们就可以看出AMD和CMD的区别了,前者是对于依赖的模块提前执行,而后者是延迟执行。 前者推崇依赖前置,而后者推崇依赖就近,即只在需要用到某个模块的时候再require。 如下:
7 .模块化之ESM6// AMD define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.doSomething() // 此处略去 100 行 b.doSomething() ... }); // CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此处略去 100 行 var b = require('./b') // 依赖可以就近书写 b.doSomething() // ... });
之前的几种模块化方案都是前端社区自己实现的,只是得到了大家的认可和广泛使用,而ES6的模块化方案是真正的规范。 在ES6中,我们可以使用import
关键字引入
模块,通过export
关键字导出
模块,功能较之于前几个方案更为强大,也是我们所推崇的
代码如下:import store from '../store/index' import {mapState, mapMutations, mapActions} from 'vuex' import axios from '../assets/js/request' import util from '../utils/js/util.js' export default { created () { this.getClassify(); this.RESET_VALUE(); console.log('created' ,new Date().getTime()); } }
函数重载与继承