第一部分 作用域与闭包
(一)词法作用域
欺骗作用域:eval 和 with 不建议使用
var obj = {
a:1,
b:2,
c:3
}
//重复obj修改
obj.a = 3;
obj.b = 4;
obj.c = 5;
//快速修改
with(obj){
a=3,
b=4,
c=5
}
(二)函数作用域和块作用域
函数表达式:
代码中foo 被绑定在函数表达式自身的函数中而不是所在作用域中。换句话说,(function foo(){ .. }) 作为函数表达式意味着foo 只能在.. 所代表的位置中被访问,外部作用域则不行。foo 变量名被隐藏在自身中意味着不会非必要地污染外部作用域。
块作用域:
1.with
2.try/catch
catch分句会创建一个块作用域,其中生命的变量仅在catch内部有效。
3.let
使用let进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不存在。
- 垃圾回收 在块中声明的变量,在使用后可以被回收。
4.const
作用域闭包
闭包:将内部函数传递到所在的词法作用域外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。
现代模块机制
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i=0; i<deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply( impl, deps );
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
};
})();
MyModules.define( "bar", [], function() {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
} );
MyModules.define( "foo", ["bar"], function(bar) {
var hungry = "hippo";
function awesome() {
console.log( bar.hello( hungry ).toUpperCase() );
}
return {
awesome: awesome
};
} );
var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );
console.log(
bar.hello( "hippo" )
); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO
二 this和对象原型
- 每个函数的this是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。
绑定规则
默认绑定
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
在本例中,函数调用时应用了this 的默认绑定,因此this 指向全局对象。。在代码中,foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。严格模式下与foo()不影响默认绑定。
隐式绑定
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
当foo() 被调用时,它的落脚点确实指向obj 对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this 绑定到这个上下文对象.
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"
虽然bar 是obj.foo 的一个引用,但是实际上,它引用的是foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn 其实引用的是foo
fn(); // <-- 调用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样。
显式绑定
call、apply 1 硬绑定
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
// 简单的辅助绑定函数
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
var obj = {
a:2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
bind(..) 会返回一个硬编码的新函数,它会把参数设置为this 的上下文并调用原始函数。
new绑定
在传统的面向类的语言中,“构造函数”是类中的一些特殊方法,使用new 初始化类时会调用类中的构造函数。 在JavaScript 中,构造函数只是一些使用new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。 实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new 操作符调用的普通函数而已。
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行[[ 原型]] 连接。
- 这个新对象会绑定到函数调用的this。
- 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
使用new 来调用foo(..) 时,我们会构造一个新对象并把它绑定到foo(..) 调用中的this上。new 是最后一种可以影响函数调用时this 绑定行为的方法,我们称之为new 绑定。
优先级
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
显式绑定优先级更高
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2//new不改变被new函数的值,只是改变新创建函数的
console.log( bar.a ); // 4//************
new绑定优先级高于隐式绑定
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); // 3
硬绑定优先级高于new ????是否和顺序有关
之所以要在 new 中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用 new 进行初始化时就可以只传入其余的参数。bind(..) 的功能之一就是可以把除了第一个 参数(第一个参数用于绑定 this)之外的其他参数都传给下层的函数(这种技术称为“部 分应用”,是“柯里化”的一种)。
function foo(p1,p2) { this.val = p1 + p2;
}
// 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么 // 反正使用 new 时 this 会被修改
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" ); baz.val; // p1p2
判断this顺序: 1 是否用new 2 是否硬绑定 3 是否隐式绑定 4 以上都不是
忽略this的情况
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 把数组“展开”成参数
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 ); bar( 3 ); // a:2, b:3
总是使用 null 来忽略 this 绑定可能产生一些副作用。如果某个函数确实使用了 this(比如第三方库中的一个函数),那默认绑定规则会把 this 绑定到全局对象(在浏览 器中这个对象是 window),这将导致不可预计的后果(比如修改全局对象)。
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 我们的 DMZ 空对象
var ø = Object.create( null );
// 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3
Object.create(null)和{}类似,但是不会创建object.proptotype这个委托,所以比{}更空。?????
软绑定
硬绑定会大大降低函数的灵活性,使 用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this。 ????????下方例子中没有发现这个情况。。。
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
}; }
var obj = { a:2};
var obj1 = {a:5}
var bar = bind( foo, obj );
bar(2)//4
bar = bind(foo,obj1);
bar(3)//8
软绑定
if (!Function.prototype.softBind) { Function.prototype.softBind = function(obj) {
var fn = this;
// 捕获所有 curried 参数
var curried = [].slice.call( arguments, 1 );
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ?
obj : this
curried.concat.apply( curried, arguments )
); };
bound.prototype = Object.create( fn.prototype);
return bound;
};
}
它会对指定的函 数进行封装,首先检查调用时的 this,如果 this 绑定到全局对象或者 undefined,那就把 指定的默认对象 obj 绑定到 this,否则不会修改 this。
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看!
setTimeout( obj2.foo, 10 );// name: obj <---- 应用了软绑定
Nodejs环境里面setTimeout的this非window或global,会指向TimeOut函数,所以最后会输出undefined。
箭头函数
箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决 定 this。
function foo() {
// 返回一个箭头函数 return (a) => {
//this 继承自 foo()
console.log( this.a );
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !
foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不 行!)
class的this问题
类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。
一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
另一种解决方法是使用箭头函数。
class Obj {
constructor() {
this.getThis = () => this;
}
}
const myObj = new Obj();
myObj.getThis() === myObj // true
箭头函数内部的this总是指向定义时所在的对象。上面代码中,箭头函数位于构造函数内部,它的定义生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以this会总是指向实例对象。