广州网站排名推广,网站经营与建设,会员注册网站怎么做,网站添加微博对象迭代
在 JavaScript 有史以来的大部分时间内#xff0c;迭代对象属性都是一个难题。ECMAScript 2017 新增了两个静态方法#xff0c;用于将对象内容转换为序列化的——更重要的是可迭代的——格式。这两个静态方法Object.values()和 Object.entries()接收一个对象#…对象迭代
在 JavaScript 有史以来的大部分时间内迭代对象属性都是一个难题。ECMAScript 2017 新增了两个静态方法用于将对象内容转换为序列化的——更重要的是可迭代的——格式。这两个静态方法Object.values()和 Object.entries()接收一个对象返回它们内容的数组。Object.values()返回对象值的数组Object.entries()返回键/值对的数组。 看代码
const o { foo: bar, baz: 1, qux: {}
};
console.log(Object.values(o));
// [bar, 1, {}]
console.log(Object.entries((o)));
// [[foo, bar], [baz, 1], [qux, {}]]注意非字符串属性会被转换为字符串输出。另外这两个方法执行对象的浅复制
const o { qux: {}
};
console.log(Object.values(o)[0] o.qux);
// true
console.log(Object.entries(o)[0][1] o.qux);
// true符号属性会被忽略
const sym Symbol();
const o { [sym]: foo
};
console.log(Object.values(o));
// []
console.log(Object.entries((o)));
// []其他原型语法 每次定义一个属性或方法都会把 Person.prototype 重写一遍。为了减少代码冗余也为了从视觉上更好地封装原型功能直接通过一个包含所有属性和方法的对象字面量来重写原型成为了一种常见的做法如下面的例子所示
function Person() {}
Person.prototype {name: Nicholas, age: 29, job: Software Engineer, sayName() { console.log(this.name); }
};Person.prototype 被设置为等于一个通过对象字面量创建的新对象。最终结果是一样的只有一个问题这样重写之后Person.prototype 的 constructor 属性就不指向 Person了。在创建函数时也会创建它的 prototype 对象同时会自动给这个原型的 constructor 属性赋值。而上面的写法完全重写了默认的 prototype 对象因此其 constructor 属性也指向了完全不同的新对象Object 构造函数不再指向原来的构造函数。虽然 instanceof 操作符还能可靠地返回值但我们不能再依靠 constructor 属性来识别类型了
let friend new Person();
console.log(friend instanceof Object); // true
console.log(friend instanceof Person); // true
console.log(friend.constructor Person); // false
console.log(friend.constructor Object); // trueinstanceof仍然对Object和Person都返回true。但constructor属性现在等于Object而不是 Person 了。如果 constructor 的值很重要则可以像下面这样在重写原型对象时专门设置一下它的值
function Person() {
}
Person.prototype { constructor: Person, name: Nicholas, age: 29, job: Software Engineer, sayName() { console.log(this.name); }
};代码中特意包含了 constructor 属性并将它设置为 Person保证了这个属性仍然包含恰当的值 注意以这种方式恢复 constructor 属性会创建一个[[Enumerable]]为 true 的属性。而原生 constructor 属性默认是不可枚举的。因此如果你使用的是兼容 ECMAScript 的 JavaScript 引擎那可能会改为使用 Object.defineProperty()方法来定义 constructor 属性
function Person() {}
Person.prototype { name: Nicholas, age: 29, job: Software Engineer, sayName() { console.log(this.name); }
};
// 恢复 constructor 属性
Object.defineProperty(Person.prototype, constructor, { enumerable: false, value: Person
});原型的动态性
因为从原型上搜索值的过程是动态的所以即使实例在修改原型之前已经存在任何时候对原型对象所做的修改也会在实例上反映出来。下面是一个例子
let friend new Person();
Person.prototype.sayHi function() { console.log(hi);
};
friend.sayHi(); // hi以上代码先创建一个Person实例并保存在friend中。然后一条语句在Person.prototype上添加了一个名为sayHi()的方法。虽然friend实例是在添加方法之前创建的但它仍然可以访问这个方法。之所以会这样主要原因是实例与原型之间松散的联系。在调用friend.sayHi()时首先会从这个实例中搜索名为sayHi的属性。在没有找到的情况下运行时会继续搜索原型对象。因为实例和原型之间的链接就是简单的指针而不是保存的副本所以会在原型上找到sayHi属性并返回这个属性保存的函数。 虽然随时能给原型添加属性和方法并能够立即反映在所有对象实例上但这跟重写整个原型是两回事。实例的[[Prototype]]指针是在调用构造函数时自动赋值的这个指针即使把原型修改为不同的对象也不会变。重写整个原型会切断最初原型与构造函数的联系但实例引用的仍然是最初的原型。记住实例只有指向原型的指针没有指向构造函数的指针。来看下面的例子
function Person() {}
let friend new Person();
Person.prototype { constructor: Person, name: Nicholas, age: 29, job: Software Engineer, sayName() { console.log(this.name); }
};
friend.sayName(); // 错误在这个例子中Person的新实例是在重写原型对象之前创建的。在调用friend.sayName()的时候会导致错误。这是因为firend指向的原型还是最初的原型而这个原型上并没有sayName属性。 3. 原生对象原型 原型模式之所以重要不仅体现在自定义类型上而且还因为它也是实现所有原生引用类型的模式。所有原生引用类型的构造函数包括Object、Array、String等都在原型上定义了实例方法。比如数组实例的sort()方法就是Array.prototype上定义的而字符串包装对象的substring()方法也是在String.prototype上定义的如下所示
console.log(typeof Array.prototype.sort); // function
console.log(typeof String.prototype.substring); // function 通过原生对象的原型可以取得所有默认方法的引用也可以给原生类型的实例定义新的方法。可以像修改自定义对象原型一样修改原生对象原型因此随时可以添加方法。比如下面的代码就给String 原始值包装类型的实例添加了一个startsWith()方法
String.prototype.startsWith function (text) { return this.indexOf(text) 0;
};
let msg Hello world!;
console.log(msg.startsWith(Hello)); // true 如果给定字符串的开头出现了调用startsWith()方法的文本那么该方法会返回true。因为这个方法是被定义在String.prototype上所以当前环境下所有的字符串都可以使用这个方法。msg 是个字符串在读取它的属性时后台会自动创建String的包装实例从而找到并调用startsWith()方法。 注意尽管可以这么做但并不推荐在产品环境中修改原生对象原型。这样做很可能造成误会而且可能引发命名冲突比如一个名称在某个浏览器实现中不存在在另一个实现中却存在。另外还有可能意外重写原生的方法。推荐的做法是创建一个自定义的类继承原生类型。 4. 原型的问题 原型模式也不是没有问题。首先它弱化了向构造函数传递初始化参数的能力会导致所有实例默认都取得相同的属性值。虽然这会带来不便但还不是原型的最大问题。原型的最主要问题源自它的共享特性。 我们知道原型上的所有属性是在实例间共享的这对函数来说比较合适。另外包含原始值的属性也还好如前面例子中所示可以通过在实例上添加同名属性来简单地遮蔽原型上的属性。真正的问题来自包含引用值的属性。来看下面的例子
function Person() {}
Person.prototype { constructor: Person, name: Nicholas, age: 29, job: Software Engineer, friends: [Shelby, Court], sayName() { console.log(this.name); };
}
let person1 new Person();
let person2 new Person();
person1.friends.push(Van);
console.log(person1.friends); // Shelby,Court,Van
console.log(person2.friends); // Shelby,Court,Van
console.log(person1.friends person2.friends); // true
这里Person.prototype有一个名为friends的属性它包含一个字符串数组。然后这里创建了两个Person的实例。person1.friends通过push方法向数组中添加了一个字符串。由于这个friends属性存在于Person.prototype而非person1上新加的这个字符串也会在指向同一个数组的person2.friends上反映出来。如果这是有意在多个实例间共享数组那没什么问题。但一般来说不同的实例应该有属于自己的属性副本。这就是实际开发中通常不单独使用原型模式的原因。
继承
继承是面向对象编程中讨论最多的话题。很多面向对象语言都支持两种继承接口继承和实现继承。前者只继承方法签名后者继承实际的方法。接口继承在ECMAScript中是不可能的因为函数没有签名。实现继承是ECMAScript唯一支持的继承方式而这主要是通过原型链实现的。
原型链 ECMA-262把原型链定义为ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。重温一下构造函数、原型和实例的关系每个构造函数都有一个原型对象原型有一个属性指回构造函数而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢那就意味着这个原型本身有一个内部指针指向另一个原型相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。 实现原型链涉及如下代码模式
function SuperType() { this.property true;
}
SuperType.prototype.getSuperValue function() { return this.property;
};
function SubType() { this.subproperty false;
}
// 继承SuperType
SubType.prototype new SuperType();
SubType.prototype.getSubValue function () { return this.subproperty;
};
let instance new SubType();
console.log(instance.getSuperValue()); // true 以上代码定义了两个类型SuperType和SubType。这两个类型分别定义了一个属性和一个方法。这两个类型的主要区别是SubType通过创建SuperType的实例并将其赋值给自己的原型SubTtype. prototype实现了对SuperType的继承。这个赋值重写了SubType最初的原型将其替换为 SuperType的实例。这意味着SuperType实例可以访问的所有属性和方法也会存在于SubType. prototype。这样实现继承之后代码紧接着又给SubType.prototype也就是这个SuperType的实例添加了一个新方法。最后又创建了SubType的实例并调用了它继承的getSuperValue()方法。 原型链扩展了前面描述的原型搜索机制。我们知道在读取实例上的属性时首先会在实例上搜索这个属性。如果没找到则会继承搜索实例的原型。在通过原型链实现继承之后搜索就可以继承向上搜索原型的原型。对前面的例子而言调用instance.getSuperValue()经过了3步搜索instance、SubType.prototype和SuperType.prototype最后一步才找到这个方法。对属性和方法的搜索会一直持续到原型链的末端。
默认原型 实际上原型链中还有一环。默认情况下所有引用类型都继承自Object这也是通过原型链实现的。任何函数的默认原型都是一个Object的实例这意味着这个实例有一个内部指针指向Object.prototype。这也是为什么自定义类型能够继承包括toString()、valueOf()在内的所有默认方法的原因。因此前面的例子还有额外一层继承关系。图8-5展示了完整的原型链。 SubType继承SuperType而SuperType继承Object。在调用instance.toString()时实际上调用的是保存在Object.prototype上的方法。原型与继承关系 原型与实例的关系可以通过两种方式来确定。第一种方式是使用instanceof操作符如果一个实例的原型链中出现过相应的构造函数则instanceof返回true。如下例所示
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true
从技术上讲instance是Object、SuperType和SubType的实例因为instance的原型链中包含这些构造函数的原型。结果就是instanceof对所有这些构造函数都返回true。 确定这种关系的第二种方式是使用isPrototypeOf()方法。原型链中的每个原型都可以调用这个方法如下例所示只要原型链中包含这个原型这个方法就返回true
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true 关于方法 子类有时候需要覆盖父类的方法或者增加父类没有的方法。为此这些方法必须在原型赋值之后再添加到原型上。来看下面的例子
function SuperType() { this.property true;
}
SuperType.prototype.getSuperValue function() { return this.property;
};
function SubType() { this.subproperty false;
}
// 继承SuperType
SubType.prototype new SuperType();
// 新方法
SubType.prototype.getSubValue function () { return this.subproperty;
};
// 覆盖已有的方法
SubType.prototype.getSuperValue function () { return false;
};
let instance new SubType();
console.log(instance.getSuperValue()); // false 在上面的代码中加粗的部分涉及两个方法。第一个方法getSubValue()是SubType的新方法而第二个方法getSuperValue()是原型链上已经存在但在这里被遮蔽的方法。后面在SubType实例上调用getSuperValue()时调用的是这个方法。而SuperType的实例仍然会调用最初的方法。重点在于上述两个方法都是在把原型赋值为SuperType的实例之后定义的。 4. 原型链的问题 原型链虽然是实现继承的强大工具但它也有问题。主要问题出现在原型中包含引用值的时候。前面在谈到原型的问题时也提到过原型中包含的引用值会在所有实例间共享这也是为什么属性通常会在构造函数中定义而不会定义在原型上的原因。在使用原型实现继承时原型实际上变成了另一个类型的实例。这意味着原先的实例属性摇身一变成为了原型属性。
function SuperType() { this.colors [red, blue, green];
}
function SubType() {}
// 继承SuperType
SubType.prototype new SuperType();
let instance1 new SubType();
instance1.colors.push(black);
console.log(instance1.colors); // red,blue,green,black
let instance2 new SubType();
console.log(instance2.colors); // red,blue,green,black在这个例子中SuperType构造函数定义了一个colors属性其中包含一个数组引用值。每个SuperType的实例都会有自己的colors属性包含自己的数组。但是当SubType通过原型继承SuperType后SubType.prototype变成了SuperType的一个实例因而也获得了自己的colors 属性。这类似于创建了SubType.prototype.colors属性。最终结果是SubType的所有实例都会共享这个colors属性。这一点通过instance1.colors上的修改也能反映到instance2.colors 上就可以看出来。 原型链的第二个问题是子类型在实例化时不能给父类型的构造函数传参。事实上我们无法在不影响所有对象实例的情况下把参数传进父类的构造函数。再加上之前提到的原型中包含引用值的问题就导致原型链基本不会被单独使用。
最近天气不错春天万物复苏周末晚上陪娃又看了央视关于苏轼的纪录片特意搜了一首苏轼写春天的词 蝶恋花·春景 花褪残红青杏小燕子飞时绿水人家绕。枝上柳绵吹又少。天涯何处无芳草。 墙里秋千墙外道墙外行人墙里佳人笑。笑渐不闻声渐悄。多情却被无情恼。