this
提供了一种更优雅的方式“传递”一个对象引用,所以能够将API设计得更加简洁而且易于复用。javascript
var obj = {
name: 'Reader',
speak: function() {
console.log(this.name);
}
};
obj.speak(); // Reader
复制代码
有两种对this
常见的误解:前端
this
的做用域// 第一个误解: this 指向自身
function foo(){
console.log(this.count);
}
foo.count = 4;
var count = 3;
foo();
复制代码
this
并不指向foo
函数,而是查找外层做用域,最终找到全局做用域的count
。java
// 第二个误解:this 指向函数的做用域
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo(); // undefined
复制代码
首先,foo
函数向外层做用域找到bar
函数,而后逐层向外找a
,到全局做用域找到window
对象,而后window
上没有a
属性,因此是undfined
。算法
注意:this
在任何状况都不指向函数的词法做用域。在 JavaScript 内部,做用域和对象很类似,可是做用域没法经过 JavaScript 代码访问,它存在于 JavaScript 引擎内部。数组
this
是在运行时绑定的,不是在编写时绑定,它取决于函数的调用方式。
当一个函数被调用时,会建立一个活动记录(也称执行上下文)。这个记录包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数信息。this
就是这个记录的一个属性,会在函数执行过程当中用到。promise
分析调用位置最重要的是分析调用栈。浏览器
当没有其余绑定时,使用默认绑定,非严格模式下,this
绑定到全局做用域下的全局对象window
;严格模式下,不能使用全局对象,所以this
会绑定到undefined
。安全
function foo() {
console.log(this.a);
}
var a = 1;
foo(); // 1
复制代码
查看调用位置是否有上下文对象,或者说是否被某个拥有或者包含,若是是,this
“至关于”那个对象的引用。app
function foo() {
console.log(this.a)
}
var a = 1;
var obj = {
foo: foo,
a: 2
}
obj.foo(); // 2
foo(); // 1
复制代码
var foo1 = obj.foo;
foo1(); // 1
复制代码
1
undefined
复制代码
此时将obj
的foo
方法赋给foo1
, 此时调用foo1
至关于直接调用foo
。异步
若是咱们想使用一个对象上的方法,并在某个对象上使用,这个时候就须要显式绑定,用到call
方法和apply
方法。
具体用法:Function.prototype.apply、Function.prototype.call
硬绑定是一种很是经常使用的模式,因此ES5提供了内置的方法Function.prototype.bind
。
// TODO: 理解更为复杂的bind的实现
// Function.prototype.bind 的 Polyfill(来自MDN)
// Does not work with `new funcA.bind(thisArg, args)`
if (!Function.prototype.bind) (function(){
var slice = Array.prototype.slice;
Function.prototype.bind = function() {
var thatFunc = this, thatArg = arguments[0];
var args = slice.call(arguments, 1);
if (typeof thatFunc !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - ' +
'what is trying to be bound is not callable');
}
return function(){
var funcArgs = args.concat(slice.call(arguments))
return thatFunc.apply(thatArg, funcArgs);
};
};
})();
复制代码
第三方库的许多函数,以及JavaScript语言和宿主环境中许多新的内置函数,都提供了一个可选参数,一般被称为“上下文”,其做用和bind
同样。
function foo(el) {
console.log(el, this.id);
}
var obj = {
id: 'awesome'
};
[1, 2, 3].forEach(foo, obj);
// 1 'awesome'
// 2 'awesome'
// 3 'awesome'
复制代码
使用new
来调用函数,或者说发生构造函数调用时,会自动执行下面的操做。
this
new
表达式中的函数调用会自动返回这个对象new
> 显式绑定 > 隐式绑定 > 默认绑定
显然,隐式绑定优先级大于默认绑定,由于若是默认绑定优先级大于隐式绑定,则经过对象调用方法时会绑定全局对象而不是绑定该对象。
function foo() {
console.log(this.a)
}
var obj = {
foo: foo,
a: 2
}
var obj1 = {
a: 3
}
obj.foo.call(obj1); // 3
复制代码
上面的例子说明,显式调用优先级大于隐式调用。最后咱们须要断定new
与显式绑定和隐式绑定的优先级。
function foo(something){
this.a = something;
}
var obj1 = {
foo: foo
};
obj1.foo(2);
var obj2 = new obj1.foo(4);
console.log(obj1.a); // 2
console.log(obj2.a); // 4
复制代码
能够看到new
绑定比隐式绑定优先级要高。
注意:new
和call/apply
没法一块儿使用,所以没法直接测试,可是能够经过硬绑定测试。
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
复制代码
bar
硬绑定在obj1
上,可是new bar(e)
并无修改obj1
,而是绑定到一个新的对象。
若是把null
或者undefined
做为绑定对象传给call/apply
或者bind
,这些值在调用是会被忽略,实际应用的是默认绑定规则。
一种常见作法是使用apply
来“展开”一个数组,看成参数传入一个函数。相似的,bind
能够对参数柯里化,这种方法有时很是有用。
function foo(a, b) {
console.log('a:', a, 'b:', b);
}
foo.apply(null, [2, 3]); // a: 2 b: 3
复制代码
var bar = foo.bind(null, 2);
bar(3); // a: 2 b: 3
复制代码
老是使用null
做为绑定对象,会有一些潜在的反作用,若是某个函数使用了this
,那么默认绑定规则会把this
绑定到全局对象,这会致使不可预测的结果。
更安全的办法是传入一个特殊的对象,把this
绑定到这个对象不会有任何反作用。
咱们能够在忽略this
绑定时传入一个空对象,不会对全局对象产生影响。在 JavaScript 中建立一个空对象最简单的方法是Object.create(null)
,Object.create(null)
和null
很像,可是它并不会建立Object.prototype
这个委托,因此它更“空”。
function foo(a, b) {
console.log('a:', a, 'b:', b);
}
var empty = Object.create(null);
foo.apply(empty, [2, 3]); // a: 2 b: 3
复制代码
function foo() {
console.log(this.a);
}
var a = 2;
var o = { a: 3, foo: foo}
var p = { a: 4 }
o.foo(); // 3
(p.foo = o.foo)(); // 2
复制代码
(p.foo...)
返回的是foo
函数,因此会使用全局对象的a
。
// TODO:软绑定的实现
复制代码
箭头函数不使用this
的四种标准,而是根据外层做用域来肯定this
。
function foo() {
return (a) => {
conosle.log(this.a)
}
};
var obj1 = {
a: 2
};
var obj2 = {
a: 3
}
var bar = foo.call(obj1);
bar.call(obj2); // 2 而不是3
复制代码
在foo.call(obj1);
运行完毕时,箭头函数的this
已经绑定在obj1
上,已经没法经过硬绑定从新绑定。
var myObject = {
key: 'value'
}
// 或
var myObj = new Object();
myObject.key = 'value';
复制代码
在JavaScript中有六种主要类型:
(ES6中,新加了symbol
)
简单基本类型(string、boolean、number、null 和 undefined)自己并非对象。null有时会被当成一种对象类型,可是这是语言自己的一个bug,实际上,null自己是基本类型。
JavaScript中还有一些对象子类型,一般被称为内置对象。有些内置对象的名字看起来和简单基础类型同样,不过实际上它们的关系更复杂。
在JavaScript中,他们其实是一些内置函数,这些函数能够看成构造函数来使用,从而能够构造一个对应子类型的新对象。
var strPrimitive = "I'm a string";
typeof strPrimitive; // string
strPrimitive instanceof String; // false
var strObject = new String("I'm a string");
typeof strObject; // 'object'
strObject instanceof String; // true
复制代码
**注意:原始值I'm a string
不是一个对象,他只是一个字面量,而且不是一个不可变的值。若是要在这个字面量上执行一些操做,好比获取长度、访问其中某个字符等,那须要转换为String对象,幸亏在必要时语言会自动把字符串字面量转换成一个String对象。**number存在相似行为。
null
和undefined
没有对应的构造式,只有文字形式。相反Date
只有构造,没有文字形式。
对于Object
、Array
、Function
和RegExp
来讲,不管使用文字形式仍是构造形式,它们都是对象。
Error
对象不多在代码中显式建立,通常是在抛出异常时被自动建立。
键访问obj['key']
,属性访问obj.key
。
经过表达式来计算属性名,可使用obj[perfix+name]
使用。
数组也是对象,虽然每一个下标都是整数,仍然能够给数组添加属性。
对象拷贝分为深拷贝和浅拷贝,浅拷贝其实只是对原有对象的引用,原对象发生改变则浅拷贝的对象也会发生变化。
var obj = {
a: 1,
b: 2,
c: 3
}
var obj1 = obj;
obj1.a; // 1
obj.a = 2;
obj1.a; // 2
复制代码
深拷贝比浅拷贝麻烦得多,JavaScript有一种办法实现深拷贝。
var obj = {a:3};
var newObj = JSON.parse(JSON.stringify(obj));
newObj.a; // 3
obj.a = 2;
newObj.a; // 3
复制代码
可是这种方法的前提是保证对象是JSON安全的,因此只适用部分状况。
尽管,JavaScript的Object上有assign方法,他能够进行对象间的复制,可是仍然不知足深拷贝的要求。
Object.assign的详细信息:Object.assign() - JavaScript | MDN
var obj = {
a: 1,
b: {
c: 3
}
}
var newObj = Object.assign({}, obj);
obj.a = 2
newObj.a // 1
obj.b.c = 4;
newObj.b.c; // 4
复制代码
尽管,复制出了一个newObj
,可是它内部的b
属性仍是引用的obj
内部的b
属性,仍是浅拷贝。
Object.getOwnPropertyDescriptor(obj, prop)获取属性描述符。
Object.defineProperty(obj, prop, descriptor)添加一个新属性或者修改一个已有属性。
1.对象常量
结合writeable: false
和configurable:false
就能够建立一个真正的常量属性(不可被修改、重定义或者删除)
2.禁止扩展
Object.preventExtensions(obj)禁止一个对象添加新属性而且保留已有属性。
3.密封
Object.seal在禁止扩展基础上,把现有属性标记为configurable:false
4.冻结
Object.freeze在密封的基础上,把全部属性标记为writable:false
**注意:**这些功能只能做用在一个对象的键上,可是若是某一个键的值是一个对象,该对象不会受到影响,即嵌套对象内部的对象的可变性不受影响。若是像深度修改,逐级遍历内部的对象。
var myObject = {
a: 2
};
myObject.a;
复制代码
在语言规范中,myObject.a
在myOjbect
上实际上实现了[[Get]]
操做,首先在对象中查找是否有名称相同的属性,若是找到就会返回这个属性,不然就会在原型链上寻找。
TODO:原型链见后文
有获取属性的操做,天然有对应的[[Put]]
操做,[[Put]]
算法大体会检查下面这些内容:
setter
就调用setter
writable
是不是false
?若是是,在非严格模式下静默失败,在严格模式下抛出TypeError
异常若是对象中不存在这个属性,[[Put]]
操做会更加复杂,TODO:在后文讨论。
在ES5中可使用getter
和setter
部分改写默认操做,可是只能应用在单个属性上,没法应用在整个对象上。getter
是一个隐藏函数,会在获取属性值时调用。setter
也是一个隐藏函数,会在设置属性值时调用。
var myObject = {
get a() {
console.log('this is getter');
return 2;
}
}
myObject.a;
// this is getter
// 2
复制代码
Object.defineProperty(
myObject,
"b",
{
get: function(){ return this.a * 2 },
enumerable: true,
configurable: true
}
);
myObject.b;
// this is getter
// 4
复制代码
为了让属性更合理,还应当定义setter
,和你指望的同样,setter
会覆盖单个属性默认的[[Put]]
操做。一般来讲getter
和setter
是成对出现的(只定义一个的话一般会产生意料以外的行为):
var myObject = {
get a() {
return this._a_;
},
set a(val) {
this._a_ = val * 2;
}
}
myObject.a = 2;
myObject.a; // 4
复制代码
访问一个对象个属性返回为undefined
,可是这个属性是不存在,仍是存在可是值是undefined
,如何区分这两种状况?
咱们能够在不访问属性值状况下判断对象中是否存在这个属性:
var myObject = {
a: 2
};
"a" in myObject; // true
"b" in myObject; // false
myObject.hasOwnProperty('a'); // true
myObject.hasOwnProperty('b'); // false
复制代码
in
操做符会检查属性是否咋爱对象及其[[Prototype]]
原型链中。TODO:参见第5章。相比之下,hasOwnProperty
只会检查属性是否在myObject
中,不会检查原型链。
Object.keys()
返回对象直接包含的全部可枚举属性,Object.getOwnPropertyNames()
返回对象直接包含的全部属性,不管是否可枚举。
for...in
循环能够用来遍历对象的可枚举属性列表(包括原型链)。
for(var i = 0; i< len; i++){...}
的方式其实不是在遍历值,而是用下标来指向值而后访问。
forEach
会遍历数组全部值,并返回回调函数的返回值。
every
一直运行回调函数返回fasle
some
一直运行到回调函数返回真值
这部分省略for..of
的内容,这部分直接看ES6的教程更好些,后面会省略部分ES6的内容。
类/继承描述了一种代码组织结构形式——一种对真实世界中问题领域的建模方法。
举个例子,以汽车为例:
类 就是图纸,图纸上包含了它的各类零件以及它具有什么样的功能。
实例化就是按照图纸造一辆车出来。
// 伪代码
calss Vehicle{
engines = 1
run() {
console.log('run')
}
toot() {
console.log('toot')
}
}
复制代码
类的实例化是由一个特殊的方法来构造的,这个方法被称为构造函数,经过new
来调用。别的语言中,这个方法名和类名相同。JavaScript比较特殊,后面会说明。
类一个很重要的特性就是继承,假设有一个父类,一个子类,子类继承父类,父类的特性会复制给子类。
用车辆举例,交通工具是父类,小轿车是子类,它继承了交通工具的全部特性。
类另外一个重要的特性是多态,用交通工具举例,交通工具和小轿车均可以驾驶,可是小轿车是四轮驱动的行驶,小轿车的类定义了本身行驶方法,行驶时会使用自身的行驶方法,而不是父类的。
calss Car inherits Vehicle{
run() {
console.log('car run')
}
}
复制代码
多重继承就是继承多个父类,可是JavaScript自己不提供多重继承。
在JavaScript中,只存在对象,不存在类,在其余语言中类表现出来的都是复制行为,所以JavaScript开发者也想出一个方法来模拟类的复制行为,这个方法就是混入。
JavaScript 中又一个特殊的[[Prototype]]
内置属性,其实就是对其余对象的饮用。几乎全部的对象在建立时[[Prototype]]
的属性都会被赋予一个非空的值。
然而虽说[[Prototype]]
是一个隐藏属性,但不少浏览器都给每个对象提供__proto__
这一属性,这个属性就是上文反复提到的该对象的[[prototype]]
。因为这个属性不标准,所以通常不提倡使用。
在第3章中说过,当试图引用对象的属性时会触发[[Get]]
操做,好比myObject.a
,对于默认的[[Get]]
操做来讲,第一步是检查对象自己是否有这个属性,若是有的话就使用它。
可是若是不存在,就须要使用对象的[[Prototype]]
链了。for...in
同理。
var obj = {
a: '1'
};
var source = {
b: 'source: 2'
};
obj.__proto__ = source; // 不推荐这么使用
obj.b; // source: 2
source.b = 'source: 22';
obj.b; // source: 22
复制代码
'source: 22'
复制代码
var source = {
b: 'source: 2'
};
var obj = Object.create(source);
obj.b; // source: 2
复制代码
哪里是[[Prototype]]
链的“尽头”?
全部普通的[[Prototype]]
最终都会指向内置的Object.prototype
。
属性访问时,若是不直接存在对象上时,会遍历原型链。若是,在原型链上遍历时,先发现的对象上有该属性,就取出该属性,再也不查找后面的对象,就屏蔽了原型链后面的同名属性。
可是在设置属性时,会有出人意料的行为发生:
setter
,那就会调用这个setter
,不会被添加到该对象,也不会在对象上从新定义一个setter
。在JavaScript中,只有对象。