初次接触JS,对一些基本概念进行探究。
this与优先级
this是一个完全根据调用点(函数是如何被调用的)而为每次函数调用建立的绑定。
从优先级最低的默认绑定开始说起。
-
默认绑定
function foo()
{
console.log(this.a);
}
var a = 2;
foo(); //2
默认的绑定规则,当且仅当其他任何绑定方式都不起作用时才适用。
-
隐含绑定
function foo()
{
console.log(this.a);
}
var obj =
{
a: 2,
foo: foo
};
obj.foo(); //2
调用点使用obj环境来引用函数,此时this.a为obj.a的同义词。需要注意的是,只有对象属性引用链的最后一层是影响调用点的。
-
明确绑定
function foo()
{
console.log(this.a);
}
var obj =
{
a: 2,
};
foo.call(obj); //2
通过call函数(或apply、bind函数)制定this的作用点。
一个更加强硬的手段为:将该绑定进行包装,成为硬绑定。
function foo()
{
console.log(this.a);
}
var obj =
{
a: 2,
};
var bar = function()
{
foo.call(obj);
};
bar(); //2
bar.call(window); //2
-
new绑定
在任何函数作为new表达式的一部分被调用时,它是一个构造器:它初始化这个新创建的对象。
在函数钱加入new调用时,一下事项会自动完成:
1.一个全新的对象会被构建
2.这个新构建的对象会被接入原型链
3.这个新构建的对向被设置为函数调用的this绑定
4.除非这个函数返回一个它自己的其他对象,这个new调用的函数将自动返回这个新构建的对象*
用这个方法可以用new进行this绑定。
function foo()
{
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); //2
至此,一般函数遵守的this规则介绍完毕,接下来考察词法this的使用。
-
箭头函数绑定
在ES6中引用了一种特殊规则函数:箭头函数。
function foo()
{
return(a) =>
{
console.log(this.a);
};
}
var obj1 =
{
a: 2
};
var obj2 =
{
a: 3
};
var bar = foo.call(obj1);
bar.call(obj2); //2,不是3!
箭头函数从封闭它的作用域采用this绑定。箭头函数的词法绑定是无法被覆盖的。
You Don't Know JS 02 Chapter2
closure(闭包)
JS里的function能访问它们的:
- 参数
- 局部变量或函数
- 外部变量(环境变量),包括:全局变量、 外部函数的变量或函数
如果一个函数访问了它的外部变量,那么它就是一个闭包。
注意,外部函数不是必需的。通过访问外部变量,一个闭包可以维持(keep alive)这些变量。在内部函数和外部函数的例子中,外部函数可以创建局部变量,并且最终退出;但是,如果任何一个或多个内部函数在它退出后却没有退出,那么内部函数就维持了外部函数的局部数据。
从技术上来讲,在JS中,每个function都是闭包,因为它总是能访问在它外部定义的数据。
闭包经常用于创建含有隐藏数据的函数(但并不总是这样)。
var db = (function()
{
// 创建一个隐藏的object, 这个object持有一些数据
// 从外部是不能访问这个object的
var data = {};
// 创建一个函数, 这个函数提供一些访问data的数据的方法
return function(key, val)
{
if (val === undefined) { return data[key] } // get
else { return data[key] = val } // set
}
// 我们可以调用这个匿名方法
// 返回这个内部函数,它是一个闭包
})();
db('x'); // 返回 undefined
db('x', 1); // 设置data['x']为1
db('x'); // 返回 1
// 我们不可能访问data这个object本身
// 但是我们可以设置它的成员
博客园:JavaScript闭包
You Don't Know JS 03 Chapter5
scope(作用域)与hoisting(声明提升)
scope(作用域)
作用域为定义如何在某些位置存储变量,以及如何在稍后找到这些变量的规则。
在实际运用过程中,作用域往往是嵌套的(值得庆幸的是,一个作用域不能同时嵌套在两个作用域中)。以下图展示函数嵌套产生的多层作用域。
图中:
- 气泡1为全局作用域,其中只有一个标识符:foo
- 气泡2为作用域foo,其中有三个标识符:a,b,bar
- 气泡3为作用域bar,其中只有一个标识符:c
JavaScript中的作用域规则很简单:引擎从当前执行的作用域开始查找变量,如果没有找到,就向上走一级继续查找,以此类推。如果到了最外层的作用域,那么查找就会停止,无论它是否找到了变量。
在以上代码中,console.log从bar中找a和b,但是并未找到,因此它继续在foo中找到了a和b并输出了。对于c,console.log在bar中就找到了它,因此无需继续在foo中寻找它了(即使可能存在全局变量c)。
hoisting(声明提升)
为了介绍声明提升,这里给出一个奇怪的例子。
foo();
function foo()
{
console.log(a); //undefined
var a = 2;
}
这段令人浑身难受的代码并不会报错。相反,它会输出undefined。
产生这样的结果正是由于JS在解析过程中的声明提升特性:它会将var等定义语句(包括函数)提升至所在作用域最上方,以免出现以上代码中的先使用后定义的情况。
以上代码的实际运行过程如下。
function foo()
{
var a;
console.log(a); //undefined
a = 2;
}
foo();
然而以下代码会报错。
foo(); //TypeError
var foo = function bar()
{
console.log(a);
var a = 2;
}
这是由于只有bar函数的声明被提升了,其对于foo的引用赋值并未提升,导致报错。
另外需要注意的一点是,函数提升优先于变量提升。考虑以下例子。
foo();
var foo;
function foo(){}
这段代码不会报错,是因为它在提升操作后变为如下形式。
function foo(){}
var foo;
foo();
第二行对foo的重复定义被无视了,因此对于foo函数的运行不会报错。
You Don't Know JS 02 Chapter4
对于一些量的TF判别
使用代码探究JS的正误判别。
console.log(!!0); //false
console.log(!!1); //true
console.log(!!0.1); //true
console.log(!!100); //true
console.log(!!-1); //true
console.log(!!""); //false
console.log(!!"0"); //true
console.log(!!"ab"); //true
console.log(!!null); //false
console.log(!!undefined); //false
console.log(!!NaN); //false
console.log(!!new Number()); //true
可以看到,值为false的量仅有0、“”、null、undefined、NaN。
接下来介绍两个常用的比较运算符。
相等运算符(==)
如果操作数具有相同的类型,则判断其等同性,如果两个操作数的值相等,则返回true,否则返回false。
如果操作数的类型不同,则按照这样的情况来判断:
- null和undefined相等
- 其中一个是数字,另一个是字符串,则将字符串转换为数字,再做比较
- 其中一个是true,先转换成1(false转换成0)在做比较
- 如果一个值是对象,另一个是数字/字符串,则将对象转换为原始值(通过toString()或者valueOf()方法)
- 其他情况,直接返回false
等同运算符(===)
如果操作数的类型不同,则不进行值得判断,直接返回false。
如果操作数的类型相同,分下列情况来判断:
- 都是数字的情况,如果值相同,则两者等同(有一个例外,NaN与其本身也不等同),否则不等同
- 都是字符串的情况,与其他程序设计语言一样,如果串的值不等,则不等同,否则等同
- 都是布尔值,且值均为true/false,则等同,否则不等同
- 如果两个操作数引用同一个对象(数组,函数),则两者完全等同,否则不等同
- 如果两个操作数均为null/undefined,则等同,否则不等同
JavaScript核心概念及实践 第二章
类型的判断
JS有基本类型:Number、String、Boolean、Undefined、Null,以及对象类型Object。
对于基本类型,除了null,都可以用typeof进行类型判断。
var a = 1;;
var b = "1";
var c = true;
var d = null;
var e = undefined;
console.log(typeof a); //number
console.log(typeof b); //string
console.log(typeof c); //boolean
console.log(typeof d); //object
console.log(typeof e); //undefined
对于null,通常用上一部分种的等同符号判定。
对于Object类型,可以使用instanceof来进行判断。
var a = [];
console.log(a instanceof Array); //true
console.log(a instanceof Object); //true
var b = Function()
console.log(b instanceof Function); //true
console.log(b instanceof Object); //true
console.log(b instanceof Array); //false
尽管有些偏题,还是附上一些typeof的判断结果,以此感受JS内部的new原理。
var a = "123";
console.log(typeof a) //string
var b = new String();
b = 123
console.log(typeof b) //number
console.log(typeof new String()) //object
console.log(typeof new Array()) //object
console.log(typeof new Number()) //object
console.log(typeof new Function()) //function
对于类的模拟
用JavaScript去模拟类是一个比较头疼的问题。
通常而言,一个标准的类应有public、private和protected成员。一般思维下,我们通过闭包来阻止对于某变量的访问。然而如果为了继承而使用了prototype,那么闭包这条路就走不通了。在这种情况下,只能使用Object.defineProperty了。该函数可以设定获取属性时返回的值,也可以设定更改属性时设置的值。有了这个函数,我们可以随时跟踪到某个属性是不是在被获取,或者是否在被更改。我们还需要一个开关,我们在类内部的方法调用时,把这个开关打开,表明是在内部运行,方法调用结束后将开关关闭,表明回到外部运行状态。有了这两个状态,我们就可以跟踪private和protected属性和方法了,一旦他们在开关关闭的时候被使用,就终止这个属性或方法的获取或设置。
详细源代码及说明请参照原作者文章:
JavaScript实现类的private、protected、public、static以及继承
开源库件jpp.js