你不知道的JavaScript(上卷)(读书笔记之一)
第二部分 this 和对象原型
任何足够先进的技术都与魔法无异。
—Arthur.C Clarke
第1章 关于this
1.this到底是什么
this是**在运行时**进行绑定的,并不是在编写时绑定的,它的上下文取决于行数调用时的各种条件。
this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
第2章 this全面解析
2.如何判断this
可以从以下方面进行判断:
- 函数是否在
new
中调用(new
绑定)?如果是的话this
绑定的是新创建的对象。var bar = new foo()
- 函数是否通过
call、apply
(显示绑定)或者硬绑定调用?如果是的话,this
绑定的是指定的对象。var bar = foo.call(obj2)
- 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,
this
绑定的是哪个上下文对象。var bar = obj1.foo()
- 如果都不是的话,使用默认绑定。如果再严格模式下,就绑定到
undefined
,否则绑定到全局对象。var bar = foo()
3. 被忽略的this
如果你把null
或者undefined
作为this
的绑定对象传入call、apply
或者bind
,这些值在调用时会被忽略,实际应用的是默认绑定规则:
function foo() {
console.log(this.a)
}
var a = 2
foo.call(null) // 2
那么什么情况下会传入null呢?
一种是非常常见的做法是使用apply(..)
来“展开”一个数组,并当作参数传入一个函数。另一种是bind(..)
可以对参数 进行柯里化(预先设置一些参数):
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
这两种方法都需要传入一个参数当作this
的绑定对象。如果不关心this
的话,你仍然需要传入一个占位符,这时null
可能是一个不错的选择,如上。
4. 更安全的this
在 JavaScript
中创建一个空对象最简单的方法是Object.create(null)
。Object.create(null)
和{}
很像,但是并不会创建Object.prototype
这个委托,所以它比{}
“更空”。
赋值表达式 p.foo = o.foo
的返回值是目标函数的引用,因此调用对象的位置是foo()
而不是p.foo()
或者o.foo()
。使用的是默认绑定规则。
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
第3章 对象
3.1 语法
对象定义:声明(文字)形式和构造形式。
- 文字形式
var myObj = {
key: value
//...
};
- 构造形式
var myObj = new Object();
myObj.key = value;
区别:
二者产生的对象是一样的。唯一区别是文字形式可以一次性添加多个属性,而构造形式只能逐个添加属性。
3.2 类型
- 基本类型:
String
、Number
、Boolean
、Undefined
、Null
、Symbol
- 引用数据类型:
Object
注:Symbol 是 ES6 引入了一种新的原始数据类型,表示独一无二的值。
注:有一种错误的说法。“JavaScript 中万物皆是对象”,这显然是不对的,因为基本类型本身并不是对象。对于typeof null
会返回object
,那是语言本事的一个bug,二进制前三位都是0
的话会被判为object
类型,而null
的二进制是32个0
,所以类型是object
.
内置对象
JavaScript中有一些对象子类型,通常被称为内置对象。
String
Number
Boolean
Object
Function
Array
Date
RegExp
Error
在JavaScript中它们实际上是一些内置函数。可以被当做构造函数来使用(由new
产生的函数调用)。
var str = 'I am a string';
var strObject = new String(str);
typeof strObject; // "Object"
strObject instanceof String; // true
通过以下方法可以检查对象子类型
Object.prototype.toString.call(new Date()); // [object, Date]
Object.prototype.toString.call({}); // [object, Object]
Object.prototype.toString.call(3); // [object, Number]
3.3 内容
- 在对象中,属性名永远都是字符串。
- 若不是字符串,则首先会先被转换成字符串。
var myObject = {};
myObject[true] = 'foo';
myObject[3] = 'bar';
myObject[myObject] = 'baz';
myObject['true']; // 'foo'
myObject['3']; // 'bar'
// 对象转成字符串是 '[object, Object]'
myObject['[object Object]']; // 'baz'
3.3.3 数组
数组也是对象,可以给数组添加属性。添加属性length
的值不会变。
var myArr = ['foo', 'bar'];
myArr.zzz = 'zzz';
myArr.length; // 2
myArr.zzz; // 'zzz'
注意:如属性名“看起来”想一个数字,那它会变成一个下标,此时就不是添加一个属性了。
var myArr = ['foo', 'bar'];
myArr[1]; // 'bar'
myArr['1'] = 'change';
myArr['2'] = 'add';
myArr[1]; // 'change'
myArr.length; // 3
3.3.5 属性描述符
从ES5
开始,所有属性都具备了属性描述符。
var myObj = { a: 1 };
Object.getOwnPropertyDescriptor(myObj, 'a');
// {
// value: 1,
// configurable: true,
// enumerable: true,
// writable: true
// }
- 通过
Object.getOwnPropertyDescriptor
函数获取属性描述符。configurable
(可配置)、enumerate
(可枚举)、writable
(可写)。 - 可通过
Object.defineProperty
函数去配置这些属性
-
writable
决定是否可以修改属性的值。
可以把writable:false
看成敌营了一个空的操作setter
,setter
被调用时抛出一个TypeError
错误。 -
configurable
决定是否可以用defineProperty(..)
方法修改属性描述符。- 把
configurable
设置成false
,是单向操作,无法撤销的。 configurable为flase
时,还是可以把writable
从true
设置为false
,但是无法从false
改为true
。- 除了禁止修改属性,也禁止
delete
删除属性。
- 把
-
enumerate
,设置false
,则不会出现再for..in
循环里。
3.3.6 不变性
- 对象常量
结合writable:false
和configurable:false
创建一个常量(不可修改、重定义和删除)。 - 禁止扩展
禁止添加新的属性并且保留已有属性,可以使用Object.preventExtensions( myObj )
- 密封
禁止添加新的属性、不能重新配置、不能删除任何现有属性,但是能修改属性。可以使用Object.seal
,实际上它对现有的对象调用Object.preventExtensions( myObj )
,并且对每个属性设置configurable:false
。 - 冻结
禁止添加新的属性、不能重新配置、不能删除任何现有属性、不能修改属性。可以使用Object.freeze
,实际上它对现有的对象调用Object.seal( myObj )
,并且对每个属性设置writable:false
。
3.3.7 [[Get]] 操作
var myObj = {
w: 1
};
myObj.w; // 1
myObj
对象访问属性w
,实际上是实现了[[Get]]
操作,首先在对象中找相同的属性,没找到的话回去[[Prototype]]
链上找,找不到返回undefined
。- 跟普通的访问变量不同,访问变量的话找不到没有定义的是会抛出一个
ReferenceError
异常。
3.3.10 存在性
var myObj = {
w: 2
};
Object.prototype.test = 1;
'w' in myObj; // true
myObj.hasOwnProperty('w'); // true
'test' in myObj; // true
myObj.hasOwnProperty('test'); // false
in
操作符会检查属性是否在对象及其[[Prototype]]
原型链中,- 而
hasOwnProperty
方法只会检查属性是否在对象中。
var myObj = {};
Object.defineProperty(myObj, 'a', {
enumerable: true, // a 可枚举的
value: 2 // 值
})
Object.defineProperty(myObj, 'b', {
enumerable: false, // b 不可枚举的
value: 1 // 值
})
myObj.hasOwnProperty('a'); // true
myObj.hasOwnProperty('b'); // true
'a' in myObj; // true
'b' in myObj; // true
for(var key in myObj) {
console.log(key, myObj[key])
}
// a 2
myObj.propertyIsEnumerable('a'); // true
myObj.propertyIsEnumerable('b'); // false
Object.keys(myObj); // ['a']
Object.getOwnPrepertyNames(myObj); // ['a', 'b']
- 可以看到
enumerate:false
,还是可以用hasOwnProperty
和in
操作符判断是否存在,但是在for..in
遍历中显示不出来,可枚举
相当于可以出现在对象属性的遍历中
。 propertyIsEnumerable
会检查属性名是否直接在对象上(不会检查[[Prototype]]
原型链)并且满足enumerate:true
。Object.keys(myObj)
,返回所有可枚举的属性(不查找[[Property]]
原型链)。Object.getOwnPrepertyNames(myObj)
,返回所有属性,不管是否可枚举(不查找[[Property]]
原型链)。