对象
JavaScript中对象的操作包括对象的创建,对象的调用,对象的属性和方法,对象的废除以及对象的绑定
在JavaScript中有两种对象,一种是系统内置的对象,另一种是用户自己创建的对象,两种不同的方式有着不同的方法。
- 创建自定义对象(最简单——创建一个Object类然后为他添加属性和方法)
<script>
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function () {
console.log(this.name);
};
person.sayName();
</script>
上面也可以简单写成:
var person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function () {
alert(this.name);
}
};
<script>
var person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function () {
alert(this.name);
}
};
</script>
属性可以重新配置,可以更新,可以遍历
属性类型
有4个描述特性的值
- configurable:表示能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
- enumerable:表示能否通过for-in 循环返回属性
- writable:表示能否修改属性的值
- value:包含这个属性的数据值
<script>
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Json",
configurable:false,
enumerable:true
});
alert(person.name); //“Json"
person.name = "Greg";
delete person.name;
alert(person.name); //“json"
</script>
访问器属性
定义多个属性:
由于为对象定义多个属性的可能性很大,ECMAScript 5 又定义了一个Object.defineProperties()方法。利用这个方法可以通过描述符一次定义多个属性。这个方法接收两个对象参数:
第一个对象是要添加和修改其属性的对象
第二个对象的属性与第一个对象中要添加或修改的属性一一对应。
有4个特征
- configurable:表示能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为true
- enumerable:表示能否通过for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为true
- get:在读取属性时调用的函数。默认值为undefined
- set:在写入属性时调用的函数。默认值为undefined
- 访问器属性不能直接定义,必须使用Object.defineProperty()来定义
<script>
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition);//2
</script>
属性读取
第一种:Object.getOwnPropertyDescriptor()方法:这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象,如果是访问器属性,这个对象的属性有configurable、enumerable、get 和set;如果是数据属性,这个对象的属性有configurable、enumerable、writable 和value
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition);//2
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
console.log(descriptor.value); //2005
console.log(descriptor.configurable); //true
console.log(descriptor.enumerable);//true
console.log(typeof descriptor.get); //"undefined“
var descriptor = Object.getOwnPropertyDescriptor(book, "year");
console.log(descriptor.configurable);//false
console.log(descriptor.value); //undefined
console.log(descriptor.enumerable); //false
console.log(typeof descriptor.get); //"function"
</script>
</body>
</html>
第二种
Object. getOwnPropertyDescriptors方法获取对象所有属性的描述信息
<script>
var o = Object.create({id:'123',name:"Jason"}, {
// foo会成为所创建对象的数据属性
foo: {
writable:true,
configurable:true,
value: "hello"
},
// bar会成为所创建对象的访问器属性
bar: {
configurable: false,
get: function() { return 10 },
set: function(value) {
console.log("Setting `o.bar` to", value);
}
}
});
console.log(Object.getOwnPropertyDescriptors(o));
</script>
第三种
Object.propertyIsEnumerable()方法判断对象的某个属性是否可以枚举。即判断给定的属性是否可以用 for…in 语句进行枚举
var obj={name:'Jason'}
console.log(obj.propertyIsEnumerable('name'));//true
属性探测
Object.hasOwnProperty(property)
用于检查给定的属性在当前对象实例中(而不是在实例 的原型中)是否存在。其中,作为参数的属性名( propertyName )必须以字符串形式指定
使用in操作符判断属性是否存在当前实例及原型链中
有两种方式使用 in 操作符:单独使用和在 for-in 循环中使用。在单独使用时, in 操作符会在通过对象能够访问给定属性时返回 true ,无论该属性存在于实例中还是原型中。
<script>
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
person1.name = "Greg";
alert(person1.name); //"Greg" ——来自实例
alert(person1.hasOwnProperty("name")); //true
</script>
属性枚举
Object.keys() 方法:接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组
<script>
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"
</script>
**Object.values()**方法返回一个给定对象自己的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。
Object.values()返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同
var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]
**Object.entries()**方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for…in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)
const object1 = { foo: 'bar', baz: 42 };
console.log(Object.entries(object1)[1]);
// Array ["baz", 42]
Object.getOwnPropertyNames()
方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
Object.getOwnPropertyNames() 返回一个数组,该数组对元素是 obj自身拥有的枚举或不可枚举属性名称字符串。 数组中枚举属性的顺序与通过 for…in 循环(或 Object.keys)迭代该对象属性时一致。数组中不可枚举属性的顺序未定义
<script>
var obj = {
0: "a",
1: "b",
2: "c"
};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
// 使用Array.forEach输出属性名和属性值
Object.getOwnPropertyNames(obj).forEach(function (val, idx, array) {
console.log(val + " -> " + obj[val]);
});
</script>
Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。
与Object.getOwnPropertyNames()类似,您可以将给定对象的所有符号属性作为 Symbol 数组获取。 请注意,
Object.getOwnPropertyNames()
本身不包含对象的 Symbol 属性,只包含字符串属性
<script>
var obj = {
name: 'Jason'
};
var a = Symbol("a");
var b = Symbol.for("b");
obj[a] = "localSymbol";
obj[b] = "globalSymbol";
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols.length); // 2
console.log(objectSymbols) // [Symbol(a), Symbol(b)]
console.log(objectSymbols[0]) // Symbol(a)
</script>
阻止向对象中增加新属性
如果一个对象可以添加新的属性,则这个对象是可扩展的。
Object.preventExtensions()
将对象标记为不再可扩展,即不能增加新属性,但仍可以将属性添加到对象的原型对象中。不可扩展对象的属性可能仍然可被删除
<script>
const object1 = {
property1: 42
};
let object2=Object.preventExtensions(object1);
object2.name='jason';
console.log(object2);// {property1: 42}
</script>
封闭对象
Object.seal()
方法米密封一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变
<script>
const object1 = {
property1: 42
};
Object.seal(object1);
object1.property1 = 33;
console.log(object1.property1);// 输出: 33
delete object1.property1; // 无法删除封闭对象的属性
console.log(object1.property1); // 仍然输出:33
</script>
对象冻结
Object.freeze()
方法可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性
<script>
const object1 = {
property1: 42
};
const object2 = Object.freeze(object1);
object2.property1 = 33;
// Throws an error in strict mode
console.log(object2.property1);
// expected output: 42
</script>
Object.isExtensible() ,Object.isFrozen() ,Object.isSealed()方法判断对象是否可扩展、被冻结和被密封
冻结对象的是指它不可扩展,所有属性都是不可配置的,且所有数据属性(即没有getter或setter组件的访问器的属性)都是不可写的。
密封对象是指那些不可 扩展的,且所有自身属性都不可配置且因此不可删除的对象
构造函数和原型对象
创建对象——工厂模式
对象通过原型链继承!!设计模式——创建对象:工厂模式,抽象模式,工厂类(创建对象没有类型都是object所有构造函数派上用场(首字母大写))
创建对象——工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程考虑到在ECMAScript 中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节
<script>
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"
</script>
使用自定义构造函数创建对象
虽然直接创建自定义对象很方便也很直观,但是如果要创建多个相同的对象,使用这种方法就显的很繁琐了,而且创建的对象没有类型信息。
在JavaScript中也可以通过调用自定义构造函数创建对象。调用自定义构造函数的方法与调用函数内置的构造函数的方法一样。使用new运算符。
创建自定义的构造函数,从而定义自定义对象类型的属性和方法
创建对象-构造函数模式
<script>
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.sayName();
person2.sayName();
</script>
静态方法直接在类里依赖这个方法(直接绑定到函数Circle.PI=),实例属性(绑定到this。this.r=r),私有属性(局部变量)
构造函数声明对象有类型如Person而不是Object
函数声明之后。(Person本身有name,age,job,sayName)Person对象里还有个特定属性prototype,prototype是对象有个属性constructor属性。不管创建多少个person对象 他们都指向一个原型对象(原型链)
instanceof运算
检测左边的实例是否是右边类的实例
在Javascript中,instanceof判断右边类型的原型是否存在于左边对象的原型链上
创建对象——原型模式
每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法
<script>
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas“
console.log(person1.age);
var person2 = new Person();
person2.sayName(); //"Nicholas"
</script>
<script>
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例(Person1的prototype)
alert(person1.hasOwnProperty("name")); //true
alert(person2.name); //"Nicholas"——来自原型
alert(person2.hasOwnProperty("name")); //false
delete person1.name;
alert(person1.name); //"Nicholas"——来自原型
alert(person1.hasOwnProperty("name")); //false
</script>
动态原型模式
动态原型模式正是致力于解决这个问题的一个方案,它把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点
<script>
function Person(name, age, job) {
//属性
this.name = name;
this.age = age;
this.job = job;
// 方法
if (typeof this.sayName != "function") {
Person.prototype.sayName = function () {
console.log(this.name);//Nicholas
};
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();
</script>
创建对象-寄生构造函数模式
寄生(parasitic)构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数
<script>
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
</script>
类的静态方法
- 可以通过为构造方法直接赋值的方式创建静态方法。
function Human(name){
this.name=name;
}
Human.showMore=function(){
console.log("it's works")
}
Human.showMore();
- 这样就可以不创建类的实例就可以调用类的方法,但其实质是:
var Human=function(name){
this.name=name;
}
Human.showMore=function(){
console.log("it's works")
}
Human.showMore();
ES6中类的定义
ES6中提出一个类特性——类声明
<script>
class Person{
constructor(name){
this.name=name;
}
show(){
console.log(this.name);
}
static showMore(){
console.log("it is working");
}
}
let p1=new Person('Jason');
p1.show();
Person.showMore();
</script>
ES6中注意事项
class关键字只是提供了语法糖,使得类的定义更加简单,但并没有改变类的原型链的本质。
通过ES6语法定义的类,所有的方法都将定义在原型链中,所欲的属性均为实例属性。
ES6中创建的类只能使用new关键字进行实例化,不能像函数作为普通函数执行。
继承
创建对象-原型模式(更一般的原型)
为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象
<script>
function Person(){
}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
console.log(this.name);
}
};
var friend = new Person();
</script>
创建对象-组合使用原型和构造方法
可以将构造函数的原型指定为特定兑现,但如此一来却打破构造函数的原型的constructor属性指向构造方法本身的约定。
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
<script>
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor: Person,
sayName : function () {
console.log(this.name);
}
};
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
console.log(person1.friends); //"Shelby,Court,Van"
console.log(person2.friends); //"Shelby,Court"
</script>
原型相关方法
Object.setPrototypeOf() 方法为对象指定原型对象。
Object.setPrototypeOf(obj, prototype)
Object.getPrototypeOf() 方法返回指定对象的原型
isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。
Object.create与原型继承
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
<script>
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = "Matthew";
me.isHuman = true;
me.printIntroduction();
</script>
ES5继承-指定父类型
<script>
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;
};
var instance = new SubType();
console.log(instance.getSuperValue());//true
</script>
原型链的问题
原型链虽然很强大,可以用它来实现继承,但它也存在一些问题。其中,最主要的问题来自包含引用类型值的原型。这会导致包含引用类型值的原型属性会被所有实例共享
原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数
<script>
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {}
//继承了SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors); //"red,blue,green,black"
</script>
借用构造函数
开发人员开始使用一种叫做借用构造函数(constructor stealing)的技术(有时候也叫做伪造对象或经典继承)。这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数
<script>
function Person(name,sex){
this.name=name;
this.sex=sex;
this.intro=function(){
console.log("Hello,I'm "+this.name);//Hello,I'm liuzhuang
}
}
function Student(score,name,sex){
Person.call(this,name,sex);
this.score=score;
this.getScore=function(){
return this.score;
}
}
var s1=new Student(70,"liuzhuang","male");
s1.intro();
console.log(s1 instanceof Person);//false
</script>
借用构造方法不足
如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
借用构造方法无法实现类型的兼容
组合继承
组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性
<script>
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType(name, age) {
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
</script>
继承-ES6
<script>
class SuperClass{
constructor(p){
this.property=p;
}
showSup(){
console.log("super:"+this.property)
}
}
class SubClass extends SuperClass{
constructor(sub,sup){
super(sup);
this.subproperty=sub;
}
showSub(){
console.log("suber:"+this.subproperty)
}
}
var sup=new SuperClass("Here");
console.log(sup.property);
var sub=new SubClass("sub","sup");
console.log(sub.subproperty);
console.log(sub.property);
sub.showSub();
sub.showSup();
</script>