JavaScript中的面向对象--对象创建

JavaScript高级程序设计第3版总结p156

1.JavaScript中的对象

首先,ECMAScript 中函数实际上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。如此,根据ECMA-262 中对象的定义:“无序属性的集合,其属性可以包含基本值、对象或者函数。”,我们可以把 ECMAScript 的对象想象成散列表,一组名值对

创建自定义对象实例的方法有两种:一种是var person = new Object()再为其添加属性和方法person.name="Yann LeCun";另一种是通过对象字面量的方法var person = {name:"Yann LeCun",age:56},这里name就是对象实例person中的一个属性。

定义了对象的属性后,有时候还需要设置对象属性的属性,譬如前述对象实例personname属性是否可修改,若可修改,修改name属性时是否需要同时更新及如何更新age属性。

ECMAScript 中有两种属性描述了对象属性(property)的各种特征:数据属性访问器属性。(特性是内部值,ECMA-262规范把它们放在了两对儿方括号中)

  • 数据属性:配置对象属性属性的一些特征

    • [[Configurable]] :表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为 true。
    • [[Enumerable]] :表示能否通过 for-in 循环返回属性,默认值为 true。
    • [[Writable]] :表示能否修改属性的值,默认值为 true。
    • [[Value]] :包含这个属性的数据值,默认值为 undefined。

    要修改属性默认的特性,必须使用 ECMAScript 5 的 **Object.defineProperty() **方法,三个参数:属性所在的对象、属性的名字和一个描述符对象(configurable 、 enumerable 、 writable 和 value)

    Object.defineProperty(person, "name", {
    	writable: false,
    	value: "Nicholas"
    });
    

    注意:一旦configurable 设置为 false,再调用Object.defineProperty() 方法修改除 writable 之外的特性,都会导致错误。

  • 访问器属性:包含一对儿 getter 和 setter 函数。访问器属性有如下 4 个特性:

    • [[Configurable]] :同数据属性。
    • [[Enumerable]]:同数据属性。
    • [[Get]] :在读取属性时调用的函数,返回属性值。默认值为 undefined 。
    • [[Set]] :在写入属性时调用的函数,传入新值。默认值为 undefined 。
      访问器属性不能直接定义,必须使用 Object.defineProperty() 来定义。
    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;
    console.log(book.edition);//2
    
  • Object.defineProperties(obj,{})同时定义多个属性,包括数据属性和访问器属性。

var book = {};
Object.defineProperties(book, {
	_year: {
		value: 2004
	},
	edition: {
		value: 1
	},
	year: {
		get: function(){
			return this._year;
			},
		set: function(newValue){
			if (newValue > 2004) {
				this._year = newValue;
				this.edition += newValue - 2004;
			}
		}
	}
});
  • Object.getOwnPropertyDescriptor(book, “_year”)取得给定属性的描述符
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
console.log(descriptor.value);//2004
alert(descriptor.configurable); //false

2.如何创建对象

可以通过var obj = {key:value}对象字面量的方式来创建对象,这种方式简便直接,但若需要重复创建多个对象时就会很冗余(每个都要写一遍)。JS中创建对象有多种不同的模式,最基础的应该是工厂模式、构造函数模式和原型模式,基于这三种模式还衍生出了其他的如,原型构造函数组合模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式,涉及到模式,更像是去理解一种设计思想,具有一定的抽象性。

  • 工厂模式
function createBook(name,year,edition){
	var obj = new Object();
	obj.name = name;
	obj.year = year; 
	obj.edition = edition;
	obj.sayName = function(){
		console.log(this.name)
		}
	return obj;
}
var book1 = createBook('Master Javascript',2016,2);
var book2 = createBook('Master CSS',2016,2);

传入不同的参数返回不同的实例;单纯的工厂模式创建对象存在的主要问题是,无法进行对象识别,也就是不能自己创建类型。像上例中,book1book2是同一个类型,但是instanceof只能确定他们是object,而没有创建它们具体属于的类。这种感觉就像是说二哈和泰迪是动物,其实更具体来说他们是狗,但若没有狗这个类,那只能说他们是动物了。

  • 构造函数模式
function Book(name,year,edition){
	this.name = name;
	this.year = year;
	this.edition  = edition;
	this.sayName = function(){
		console.log(this.name)
	}
}
var book1 = new Book('Master Javascript',2016,2);
var book2 = new Book('Master CSS',2016,2);
console.log(book1 instanceof Book);//true
console.log(book1 instanceof Object);//true

1.首先看下thisjavascript中的this引用的是函数据以执行的环境对象.通过new来调用构造函数,将构造函数的作用域赋给新对象,所以构造函数的this就是指new出来的新对象,也就将传入的参数绑定到了新创建的对象上。
2.构造函数,函数名以大写字母开头(惯例)。
3.通过构造函数创建的对象可以确定具体类型book1就是Book类型。
4.构造函数与普通函数的唯一不同,就是调用方式不同,调用方式决定的是函数的作用域,任何函数通过new来调用也就都变成了构造函数。
5.上面的构造函数中定义了一个sayName方法,在JavaScript中,functionFunction类型的对象,所以上面的sayName方法还可写为this.sayName = new Function("alert(this.name)");,这样,每调用一次构造函数创建一个Book类型的实例,都会给每个实例开辟一片独立的内存空间创建一个sayName方法对象,而sayName完全可以定义成Book类型所有实例共享的方法,为每个实例创建一个同样的方法是奢侈浪费低性能的,这也正是构造函数模式存在问题,就是每个方法都要在每个
实例上重新创建一遍。
属于同一个类型的不同实例,不就应该既有一部分共享的,又有一部分个性化的属性和方法吗?

  • 原型模式
    每一个函数都有一个prototype的属性,这个属性是一个指向函数原型对象的指针。这个原型对象中包含了某个特定类型的所有实例共享的属性和方法

    function Book(){
    }
    Book.prototype.name = 'Master JavaScript';
    Book.prototype.year = 2019;
    Book.prototype.edition = 2;
    Book.prototype.sayName = function(){
    	console.log(this.name);
    	}
    var book1 = new Book();
    var book2 = new Book();
    book2.name = 'Master CSS';
    book2.year = 2019;
    book2.edition = 2;
    book2.sayName();//'Master CSS';
    book2.author = 'CSS Author';
    Book.prototype.isPrototypeOf(book1);//true
    Object.getPrototypeOf(book1) == Book.prototype
    delete book2.name;
    book2.name;//Master JavaScript
    book1.hasOwnProperty('name');//false;
    book2.hasOwnProperty('name');//true;
    ’name' in book1;//true
    //判断属性是存在于原型还是实例中
    function hasPrototypeProperty(object, name){
    	return !object.hasOwnProperty(name) && (name in object);
    }
    Object.keys(book1);//[]
    Object.keys(book2);//["name", "year", "eidtion", "author"]
    Object.getOwnPropertyNames(Book.prototype);// ["constructor", "name", "year", "edition", "sayName"]
    

    1.JavaScript中,创建一个函数就会根据一组特定的规则为该函数创建一个prototype属性,指向函数的原型对象,语言如此设计,基于此实现对象创建继承等。刚创建函数时,函数的原型对象中都会自动包含prototype属性所在函数的指针Book.prototype.constructor.
    2.调用自定义的构造函数创建一个实例时,该实例内部包含一个指针[[prototype]],chrome等中每个实例都有一个__proto__属性,指向生成该实例的构造函数的原型对象,与构造函数已没有关系。
    3.isPrototypeOf(),判断某个构造函数的原型是不是某个实例的原型。Object.getPrototypeOf(),获取某个实例的__prototype所指的对象。
    4.访问实例属性时,先访问定义在实例上的属性,没有再去原型上搜索。在实例上新增的与原型中同名的属性,会屏蔽原型中定义的属性。使用 delete 操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性
    5. hasOwnProperty(),从Object中继承来的方法,当属性是在实例中定义的属性时,才会返回true
    6. in,in 操作符会在通过对象能够访问给定属性时返回 true
    7. 要取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 的 Object.keys()方法。
    8. Object.getOwnPropertyNames(),得到所有实例属性,无论它是否可枚举。
    9. 通过对象字面量方法创建原型对象会覆盖掉原型中constructo属性。手动创建constructor时,会在会导致它的 [[Enumerable]] 特性被设置为 true,可借助前述defineProperty设置其特性。
    10.先创建实例后通过对象字面量定义原型对象时,会出错,因为会重写原型对象,导致实例中的[[prototype]]属性无法指向创建的原型对象。
    11.原型对象存在问题,是由其共享的属性导致的,当原型属性中包含引用类型时,实例之间会相互影响

  • 构造函数和原型组合使用
    既然构造函数过于’个性‘,原型过于‘共享’,如果把同一类型实例共享的属性用原型定义,个性化的属性用构造函数来定义,问题不就解决了吗?构造函数与原型混成的模式,是目前在 ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。

    function Book(name,year,edition){
    	this.name = name;
    	this.year = year;
    	this.edition = edition;
    	this.category = ['computer','software'];
    }
    Book.prototype = {
    	consturctor:Book,
    	sayName:function(){
    		console.log(this.name);
    	}
    }
    var book1 = new Book('Master JavaScript',2019,2);
    var book2 = new Book('Master CSS',2020,2);
    book1.category.push('JavaScript');//["computer", "software", "JavaScript"]
    book1.sayName == book2.sayName;//true
    book2.category;//["computer", "software"]
    
  • 动态原型模式
    根据需要,在构造函数中根据条件动态的初始化原型。

    function Book(name,year,edition){
    	this.name = name;
    	this.year = year;
    	this.edition = edition;
    	if(typeof this.sayName != "function"){
    		Book.prototype.sayName = function(){
    			console.log(this.name);
    			}
    		}
    	}
    

    动态原型模式只通过构造函数来创建实例,更接近其他OO(面向对象)的语言,便于理解。

  • 寄生构造函数模式
    实现方式是,创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;

    function Book(name,year,edition){
    	var obj =  new Object();
    	obj.name = name;
    	obj.year = year;
    	obj.edition = edition;
    	obj.sayName = function(){
    		console.log(this.name);
    		}
    	return obj;
    }
    book1 = new Book('Master JavaScript',2019,2);
    book1 instanceof Book;//false
    

    寄生构造函数和工厂函数一样,区别在与调用方式,这里加了调用new操作符,Book函数的作用域赋值给了new出来的对象,通过返回值重写了这个new出来的对象,所以无法通过instanceof来识别对象。这个模式可以在特殊的情况下用来为对象创建构造函数。如特殊的数组。寄生可以理解为寄生在内部创建对象的类型上。

  • 稳妥构造函数
    稳妥对象,没有公共属性,不使用thisnew

    function Book(name,year,edition){
    	var obj = {};
    	obj.sayName:function(){
    		console.log(name);
    		}
    	return obj;
    }
    var book = Book('Master CSS',2019,2);
    book.sayName();//'Master CSS'
    

    实现数据成员的私有,只能通过对象开放的方法访问其中的数据属性,安全性高。

其实,这两部分概括来说是,一部分是JavaScript对象属性相关的特性,一部分是创建对象的工厂函数、构造函数和原型方法,有时间补上几张图,条分缕析方便理解,加深印象。

你可能感兴趣的:(前端开发,javascript,面向对象)