你不知道的JavaScript(三)|this和对象原型

对象语法
对象的文字语法大概是这样:

var myObj = {
    key: value
    // ...
};

构造形式大概是这样:

var myObj = new Object();
myObj.key = value;

类型
对象是JavaScript的基础。在JavaScript中一共有六种主要类型(术语是“语言类型”)

  • string
  • number
  • boolean
  • null
  • undefined
  • object
    简单基本类型(string、boolean、number、null和undefined)本身并不是对象。
    null有时候会被当做一种对象类型,但是这其实只是语言本身的一个bug,即对null执行typeof null时会返回字符串“object”。实际上,null本身是基本类型。
    JavaScript中有许多特殊的对象子类型,我们可以称之为复杂基本类型。
    函数就是对象的一个子类型(从技术角度来说就是“可调用的对象”)。JavaScript中的函数是“一等公民”,因为它们本质上和普通的对象一样(只是可以调用),所以可以像操作其他对象一样操作函数(比如当做另一个函数的参数)。
    数组也是对象的一种类型,具备一些额外的行为。数组中内容的组织方式比一般的对象要稍微复杂一些。

内置对象
JavaScript中还有一些对象子类型,通常被称为内置对象:

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error
    在JavaScript中,它们实际上只是一些内置函数。这些内置函数可以当做构造函数(由new产生的函数调用)来使用,从而可以构造一个对应子类型的新对象。
var strPrimitive = "I am a string";
typeof strPrimitive; // "string"
strPrimitive instanceof String; // false
var strObject = new String( "I am a string" );
typeof strObject; // "object"
strObject instanceof String; // true
// 检查sub-type 对象
Object.prototype.toString.call( strObject ); // [object String]

原始值“I am a string”并不是一个对象,它只是一个字面量,并且是一个不可变得值。如果要在这个字面量上执行一些操作,比如获取长度、访问其中某个字符等,那需要将其转换为String对象。在必要时语言会自动把字符串字面量转换成一个String对象,也就是说你并不需要显式创建一个对象。

可计算属性名
ES6增加了可计算属性名,可以在文字形式中使用[]包裹一个表达式来当作属性名:

var prefix = "foo";
var myObject = {
[prefix + "bar"]:"hello",
[prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world

数组
数组也是对象,所以虽然每个下标都是整数,你仍然可以给数组添加属性:

var myArray = [ "foo", 42, "bar" ];
myArray.baz = "baz";
myArray.length; // 3
myArray.baz; // "baz"

可以看到虽然添加了命名属性(无论是通过.语法还是[]语法),数组的length值并未发生变化。
如果你试图向数组添加一个属性,但是属性名“看起来”像一个数字,那它会变成一个数值下标(因此会修改数组的内容而不是添加一个属性):

var myArray = [ "foo", 42, "bar" ];
myArray["3"] = "baz";
myArray.length; // 4
myArray[3]; // "baz"

对象复制
对于JSON安全(也就是说可以被序列化为一个JSON字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一个巧妙的复制方法:

var newObj = JSON.parse( JSON.stringify( someObj ) );

当然,这种方法需要保证对象是JSON安全的,所以只适用于部分情况。如下:

var o = {
    a: 1,
    b: 2,
    sum: function () { return a + b; }
};
var o2 = JSON.parse(JSON.stringify(o));
console.log(o2);//{a: 1, b: 2}

相比深复制,浅复制非常易懂并且问题要少得多,所以ES6定义了Object.assign(..)方法来实现浅复制。该方法的第一个参数是目标对象,之后还可以跟一个或多个源对象。它会遍历一个或多个源对象的所有可枚举的自有键并把它们复制到目标对象,最后返回目标对象,就像这样:

function anotherFunction() { /*..*/ }
var anotherObject = {
    c: true
};
var anotherArray = [];
var myObject = {
    a: 2,
    b: anotherObject, // 引用,不是复本!
    c: anotherArray, // 另一个引用!
    d: anotherFunction
};
var newObj = Object.assign({}, myObject);
newObj.a; // 2
newObj.b === anotherObject; // true
newObj.c === anotherArray; // true
newObj.d === anotherFunction; // true

需要注意的一点是,由于Object.assign(..)就是使用=操作符来赋值,所以源对象属性的一些特性(比如writable)不会被复制到目标对象。

属性描述符
从ES5开始,所有的属性都具备了属性描述符:

var myObject = {
    a: 2
};
console.log(Object.getOwnPropertyDescriptor(myObject, "a"));
// {
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
// }

在创建普通属性时属性描述符会使用默认值,我们也可以使用Object.defineProperty(..)来添加一个新属性或者修改一个已有属性(如果它是configurable)并对特性进行设置。

var myObject = {};
Object.defineProperty(myObject, "a", {
    value: 2,
    writable: true,
    configurable: true,
    enumerable: true
});
console.log(myObject.a); // 2

Getter和Setter
对象默认的[[Put]]和[[Get]]操作分别可以控制属性值的设置和获取。
在ES5中可以使用getter和setter部分改写默认操作,但是只能应用在单个属性上,无法应用在整个对象上。getter是一个隐藏函数,会在获取属性值时调用。setter也是一个隐藏函数,会在设置属性值时调用。

var myObject = {
    // 给 a 定义一个getter
    get a() {
        return 2;
    }
};
myObject.a = 3;
myObject.a; // 2

由于我们只定义了a的getter,所以对a的值进行设置时set操作会忽略赋值操作,并不会抛出错误。而且即便有合法的setter,由于我们自定义的getter只会返回2,所以set操作是没有意义的。
为了让属性更合理,还应当定义setter,和你期望的一样,setter会覆盖单个属性默认的[[Put]](也被称为赋值)操作。通常来说getter和setter是成对出现的(只定义一个的话通常会产生意料之外的行为):

var myObject = {
    // 给 a 定义一个getter
    get a() {
        return this._a_;
    },
    // 给 a 定义一个setter
    set a(val) {
        this._a_ = val * 2;
    }
};
myObject.a = 2;
myObject.a; // 4

存在性
前面介绍过,如myObject.a的属性访问返回值可能是undefined,但是这个值有可能是属性中存储的undefined,也可能是因为属性不存在所以返回undefined。那么如何区分这两种情况呢?
可以在不访问属性值的情况下判断对象中是否存在这个属性:

var myObject = {
    a: 2
};
console.log("a" in myObject); // true
console.log("b" in myObject); // false
console.log(myObject.hasOwnProperty("a")); // true
console.log(myObject.hasOwnProperty("b")); // false

in操作符会检查属性是否在对象及其[[Prototype]]原型链中。相比之下hasOwnProperty(..)只会检查属性是否在myObject对象中,不会检查[[Prototype]]链。
所有的普通对象都可以通过对于Object.prototype的委托来访问hasOwnProperty(..),但是有的对象可能没有连接到Object.prototype(通过Object.create(null)来创建)。在这种情况下,形如myObject.hasOwnProperty(..)就会失败。
这时可以使用一种更加强硬的方法来进行判断:Object.prototype.hasOwnProperty.call(myObject,"a"),它借用基础的hasOwnProperty(..)方法并把它显式绑定到myObject上。
注:看起来in操作符可以检查容器内是否有某个值,但是它实际上检查的是某个属性名是否存在。对于数组来说这个区别非常重要,4 in [2,4,6]的结果并不是你期待的True,因为[2,4,6]这个数组中包含的属性名是0,1,2,没有4。

你不知道的JavaScript(三)|this和对象原型_第1张图片
慈母手中线,游子身上衣

你可能感兴趣的:(你不知道的JavaScript(三)|this和对象原型)