最近项目,一直在研究使用新的框架,新的工具,结果发现学习的速度还没有轮子出现的速度快,就拿webpack来说,1.0都还没有掌握,2.0就出来了,无力感突现,一直在跟随着别人的脚步在前进,都没有自己的东西,所以内功才是一切,内功强大,自己写一个框架,也不是不可。不然你连人家的源码都不懂,只会用又有什么用呢,明天换一个框架,或者换一个公司,你又要从头开始,你的竞争力又在何方。
-
作用域
1,LHS(赋值操作)和RHS(取值操作)查询都会在当前执行作用域开始,如果没有找到,就会向上级作用域继续查找目标标识符,这样每次上升一级,最后抵达全局作用域。
2,不成功的RHS引用会抛出ReferenceError异常,不成功的LHS引用会导致自动隐式的创建全局变量(非严格模式下),或者抛出ReferenceError异常(严格模式下)
-
词法作用域##
词法作用域完全由写代码期间函数声明的位置来定义
function foo(a) {
var b = a * 2;
function bar(c) {
console.log( a, b, c );
}
bar(b * 3);
}
foo( 2 ); // 2 4 12
对于console而言,会先查找最内部的作用域,引擎在这里无法找到a,因此就会去上一层作用域中查找。作用域查找会在找到第一个匹配的标识符时停止,所以在多层嵌套的作用域中可以定义同名的标识符,这叫做遮蔽效应(内部的标识符遮蔽了外部的标识符)
function foo(str, a) {
eval( str ); // 欺骗!,在运行时就相当于var b= 3,遮蔽了外部的b
// 但是在严格模式下,eval运行时,有自己的作用域,无法修改所在的作用域
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1 3
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = {
a: 3
};
var o2 = {
b: 3
};
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2 -- 不好,a被泄露到全局作用域上了!
//当我们传递o1给with时,with所声明的作用域是o1,而这个作用域中含有一个相符的标识符,但是我们将o2作为作用域时,其中并没有a标识符,因此进行了正常的LHS查找
使用eval和with可以欺骗词法作用域,但是会导致性能的下降,所以不要使用,同时因为js引擎会在编译阶段进行数项的优化,但是对于未知的代码时优化不了的
-
函数作用域和块作用域
1,在软件设计中,应该最小限度的暴露必要内容,而将其他的内容都隐藏起来,比如某个模块或对象的API,
2,(function(){})()和(function(){}())是一样的东西,都叫做立即执行函数表达式(IIFE),看个人习惯,
3,在try/catch的catch分局中会创建一个作用域
4,
-
提升
1,js存在预编译阶段,此时会将函数声明和变量提升,并且函数会在变量之前,同事若是重复的声明将会忽略
-
闭包
1,无论通过何种手段将内部函数传递到所在的词法作用域以外,他都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包,内部的变量都不会销毁,因为闭包需要使用,最常见的就是回调函数,和setTimeout,
2,延迟函数的回调会在循环结束后才执行,会先把当前的同步执行完,
3,现在大多数的模块依赖加载/管理器的本质上都是将这种模块定义封装进一个友好的API中,一下是核心的原理
var MyModules = (function Manager(){
var modules = []
// name: 模块名称
// deps:模块依赖
// impl: 模块实体
function define(name,deps,impl){
// 得到所有依赖,当前依赖的东西必须在之前已经定义好的
for(var i=0;i<deps.length;i++){
deps[i] = modules[deps[i]]
}
// 定义模块,将所有依赖传入当前的模块实体中,这样模块便可以使用依赖
// apply会直接执行函数,然后得到模块暴露出来的API
modules[name] = impl.apply(impl,deps)
}
function get(name){
return modules[name]
}
// 暴露API
return {
define: define,
get: get
}
})()
4,模块文件中的内容会被当做好像包含在作用域闭包中一样来处理。
-
this
使用new来调用函数,会自动执行下面的操作
1,创建一个全新的对象
2,这个对象会被执行[[Prototype]]连接
3,这个新对象会绑定到函数调用的this
4,如果函数内部没有返回,那么自动返回这个新对象
1,字符串,数值,布尔字面量在需要的时候会自动转换为对应的对象
2,object.assign使用=复制,所以引用的东西复制过来还是引用
3,var myobject = Object.create(anotherobject)//以这个对象为原型创建一个对象
var obj = Object.create(null)得到的对象没有_proto_,存储数据最好了。
-
原型,委托
这里的原型概念确实拓展了以前的理解
常见的类的实现
function Foo(who){
this.me = who
}
Foo.prototype.identify = function(){
return 'I am '+ this.me
}
function Bar(who){
Foo.call(this,who)
}
// 惭愧,以前直接=,因为是引用,所以不合逻辑
Bar.prototype = Object.create(Foo.prototype)
Bar.prototype.speak = function(){
alert('Hello,'+ this.identify()+'.')
}
var b1 = new Bar('b1')
var b2 = new Bar('b2')
b1.speak()
b2.speak()
这种实现情况下,他的关系图如下(良心出品):
1,因为bar的原型对象中没有constructor所以去原型链中找到了Foo,b1,b2也是一样
2,Foo,Bar都是Function 的一个实例(函数也是一个对象),包括function Object()也是,所以他们的constructor都指向Function 对象,而proto都指向Function.prototype,
使用委托风格实现的代码:
Foo = {
init: function(who){
this.me = who
},
identify: function(){
return 'I am '+this.me
}
}
Bar = Object.create(Foo)
Bar.speak = function(){
alert('Hello,'+this.identify()+'.')
}
var b1 = Object.create(Bar)
b1.init('b1')
var b2 = Object.create(Bar)
b2.init('b2')
b1.speak()
b2.speak()
关系图如下:
实现方式比较简单,就是一路的使用原型链去委托,找不到的话,就去上一级寻找。
还是习惯于使用传统的方式,但是委托的方式还是很大拓展的思维
-
严格模式的限制
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with语句
不能对只读属性赋值,否则报错
不能使用前缀0表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop,会报错,只能删除属性delete global[prop]
eval不会在它的外层作用域引入变量
eval和arguments不能被重新赋值
arguments不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller和fn.arguments获取函数调用的堆栈
增加了保留字(比如protected、static和interface)
-
es6模块
1,代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
2,模块脚本自动采用严格模式,不管有没有声明use strict。
3,模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口。
4,模块之中,顶层的this关键字返回undefined,而不是指向window。也就是说,在模块顶层使用this关键字,是无意义的。
5,同一个模块如果加载多次,将只执行一次。