JavaScript 中的所有事物都是对象:字符串、数字、数组、日期,等等。
在 JavaScript 中,对象是拥有属性和方法的数据。
内置对象:JS提供的
宿主对象:根据JS不同的执行环境来划分
如:浏览器:DOM、BOM
服务器:nodeJS、mysql
自定义对象:自己创建的对象
1、对象字面(直接)量
2、内置构造函数 new
3、自定义构造函数(高级里才有)
属性是与对象相关的值。{ key: value }
方法是能够在对象上执行的动作。{ funcName: function(){…} }
字面量表示如何表达这个值,一般除去表达式,给变量赋值时,等号右边都可以认为是字面量。
字面量分为字符串字面量(string literal )、数组字面量(array literal)和对象字面量(object literal),另外还有函数字面量(function literal)。
使用大括号{}创建空对象,属性名和属性值之间用冒号隔开,多组属性之间用逗号隔开,属性名引号可加可不加,如果含有特殊字符,必须加
var myObj = {};
myObj.name = "Bob";
myObj.age = 20;
myObj.say = function() {
console.log("你好,我是" + this.name); //this.name直接调用对象中的属性
};
// 或者var myObj = { myObj.name = "Bob", myObj.age = 20, say:function(){console.log("你好,我是" + this.name);} };
myObj.say() //调用方法,输出:你好,我是Bob
myObj.say /*查看属性值,输出(下同):
ƒ () {
console.log("你好,我是" + this.name); //this.name直接调用对象中的属性
}
*/
使用new操作符和内置构造函数Object(),new Object() >>创建一个空对象
var myObj = new Object();
myObj.name = "Bob";
myObj.age = 20;
myObj.say = function() {
console.log("你好,我是" + this.name); //this.name直接调用对象中的属性
};
myObj.say() //你好,我是Bob
一、二两种写法优点:代码简洁明了
一、二两种写法缺点:代码复用性差,如果要创建多个类似的对象,会产生大量的重复代码
创建一个对象,需要实例化的时候进行初始化赋值
//创建
function createPerson(name, age){
var myObj = new Object();
myObj.name = name;
myObj.age = age;
myObj.say = function() {
console.log("你好,我是" + this.name); //this.name直接调用对象中的属性
}
return myObj;
}
//实例化
var Bob = createPerson("Bob",20);
var Bob1 = createPerson("Bob1",20);
Bob.say(); //你好,我是Bob
Bob1.say(); //你好,我是Bob1
工厂模式优点:避免创建多个类似对象造成代码冗余
工厂模式缺点:工厂模式解决了重复实例化的问题,但无法区分他们到底是哪个对象的实例。所有的对象实例都是Object
类型
instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
var Bob1 = new createPerson("Bob1",20);
var Bob2 = new createPerson("Bob2",21);
console.log(Bob1 instanceof Object); //true
console.log(Bob2 instanceof Object); //true
构造函数模式,这个函数只管创建某个类型的对象实例,其他的一概不管
构造函数的执行过程:
//Person1对象实例
function Person1(name, age){
this.name = name;
this.age = age;
this.say = function() {
console.log("你好,我是" + this.name); //this.name直接调用对象中的属性
}
}
//Person2对象实例
function Person2(name, age){
this.name = name;
this.age = age;
this.say = function() {
console.log("你好,我是" + this.name); //this.name直接调用对象中的属性
}
}
var Bob1 = new Person1("Bob1",20);
Bob1.say() //你好,我是Bob1
var Bob2 = new Person2("Bob2",21);
Bob1.say() //你好,我是Bob2
console.log(Bob1 instanceof Person1); //true
console.log(Bob2 instanceof Person2); //true
console.log(Bob1 instanceof Person2); //false
console.log(Bob2 instanceof Person1); //false
构造函数式创建的优点:即解决了重复实例化的问题,又解决了对象识别的问题
构造函数式创建的缺点:因为对象不相同,所以遇到方法需要复用的时候,每次创建对象时,都需要重新创建该方法,造成了资源浪费
构造函数中的属性和方法在每个对象实例之间都不是共享的;而要想实现共享,就要将属性和方法存到构造函数的原型中。
当建立一个构造函数时(普通函数亦然),会自动生成一个prototype
(原型),构造函数与prototype
是一对一的关系,并且此时prototype
中只有一个constructor
属性
__proto__
这条链去构造函数的prototype
上寻找prototype
这就是原型模式的核心所在,结论:在原型上声明属性或方法,可以让对象实例之间共用它们
// 第一段代码
function Person(name){
this.name = name //如果这个name先不放进来就相当于字面量方式创建对象
}
//需要有一个概念:构造函数的`prototype`也是一个对象
//手动设置prototype,不指定构造函数,此时构造函数可以找到`prototype`,但`prototype`找不到构造函数了
//当我们这样设置`prototype`时,其实已经将原先`Person.prototype`给切断了,然后又重新引用了另外一个对象
Person.prototype = {
//constructor: Person, // 因为constructor属性没赋值,prototype本该利用它来找到构造函数的
age:20
}
console.log(Person.prototype); //Object {age: 20}
console.log(Person.prototype.constructor === Person); //false
var Bob = new Person("Bob");
console.log(Bob); //Person {name: "Bob"}
// 看下控制台输出
/**
{age: 20}
age: 20
__proto__: Object
false
Person {name: "Bob"}
name: "Bob"
__proto__:
age: 20
__proto__: Object
**/
// 第二段代码
function Person(name){
this.name = name
}
//手动设置prototype,指定构造函数,此时构造函数可以找到`prototype`,`prototype`也可以找到构造函数
Person.prototype = {
constructor: Person, // 构造函数为上面的Person
age:20
}
console.log(Person.prototype); //Object {age: 20, constructor: ƒ}
console.log(Person.prototype.constructor === Person); //true
var Bob = new Person("Bob");
console.log(Bob.__proto__);
console.log(Bob);
// 看下控制台输出
/**
{age: 20, constructor: ƒ}
age: 20
constructor: ƒ Person(name)
__proto__: Object
true
{age: 20, constructor: ƒ}
age: 20
constructor: ƒ Person(name)
__proto__: Object
Person {name: "Bob"}
name: "Bob"
__proto__:
age: 20
constructor: ƒ Person(name)
__proto__: Object
**/
原型模型创建的问题:
问题一、如果对象实例有与原型上重名的属性或方法,当访问该属性或方法时,实例上的属性值会将原型上的屏蔽
function Person(name){
this.name = name
}
Person.prototype = {
constructor: Person,
name:"原型Bob"
}
var Bob = new Person("实例化Bob");
console.log(Bob.name); //实例化Bob
问题二、由于实例间是共享原型上的属性和方法的,所以当其中一个对象实例修改原型上的属性(基本值,非引用类型值或方法时不影响,原因其实就在于js中值类型和引用类型的区别),其他实例也会受到影响
function Person(name){
this.name = name
}
Person.prototype = {
constructor: Person,
name:"原型Bob",
age:"20",
prolan:["C","C++"]
}
var Bob1 = new Person("实例化Bob1");
var Bob2 = new Person("实例化Bob2");
console.log("Bob1",Bob1.age,"Bob2",Bob2.age);
Bob1.age = 25;
console.log("修改Bob1",Bob1.age,"Bob2",Bob2.age);
console.log("Bob1",Bob1.prolan,"Bob2",Bob2.prolan);
Bob2.prolan.push("JavaScript");
console.log("Bob1",Bob1.prolan,"修改Bob2",Bob2.prolan);
//控制台输出
/*
Bob1 20 Bob2 20
修改Bob1 25 Bob2 20
Bob1 (2) ["C", "C++"] Bob2 (2) ["C", "C++"]
Bob1 (3) ["C", "C++", "JavaScript"] 修改Bob2 (3) ["C", "C++", "JavaScript"]
*/
将不共享的属性封装在构造函数,将共享的方法封装在原型中
function Person(name){
this.name = name //实例的属性,在构造函数中声明
}
Person.prototype = {
constructor: Person,
say: function() { //共享的方法存在原型中
console.log("你好,我是" + this.name); //this.name直接调用对象中的属性(哪个对象实例用就指向哪个)
}
}
var Bob1 = new Person("实例化Bob1");
var Bob2 = new Person("实例化Bob2");
Bob.say(); //你好,我是实例化Bob1
Bob1.say(); //你好,我是实例化Bob2
组合使用构造函数模式和原型模式创建的优点:解决了传参和对象共享数据的问题
组合使用构造函数模式和原型模式创建的缺点:不管是否调用了原型中的共享方法,它都会初始化原型中的方法
共享的方法是在构造函数中检测并声明的,原型并没有被显示创建
function Person(name, age){
this.name = name;
this.age = age;
//检查方法是否存在
if(typeof this.say !== 'function') {
console.log("say 方法不存在");
Person.prototype.say = function() { //共享的方法存在原型中
console.log("你好,我是" + this.name);
}
} else {
console.log("say 方法已存在");
}
}
var Bob1 = new Person("实例化Bob1",20);
var Bob2 = new Person("实例化Bob2",30);
Bob1.say();
Bob2.say();
console.log(Bob1.age)
console.log(Bob2.age)
//输出,注意输出的顺序,第一次对对象实例化的时候方法被创建
/*
say 方法不存在
say 方法已存在
你好,我是实例化Bob
你好,我是实例化Bob1
*/
当Person
构造函数第一次被调用时,Person.prototype
上就会被添加`say方法
注意:使用动态原型模式时,不能使用对象字面量重写原型,测试
function Person(name){
this.name = name;
//检查方法是否存在
if(typeof this.say !== 'function') {
Person.prototype.say = function() { //共享的方法存在原型中
console.log("你好,我是" + this.name);
}
}
}
var Bob1 = new Person("实例化Bob1");
Person.prototype = {
constructor: Person,
say: function(){
console.log("你好,我是" + this.name);
}
}
var Bob2 = new Person("实例化Bob2");
var Bob3 = new Person("实例化Bob3");
console.log(Bob1.__proto__ === Bob2.__proto__) //false
console.log(Bob2.__proto__ === Bob3.__proto__) //true
console.log(Bob1.__proto__.constructor === Bob2.__proto__.constructor) //true
console.log(Bob2.__proto__.constructor === Bob3.__proto__.constructor) //true
console.log(Bob1.say === Bob2.say) //false
console.log(Bob2.say === Bob3.say) //true
1.Bob1
实例创建,此时原型没有say
方法,则在原型中添加
2.随后,我们以字面量的形式重写了原型,这时旧的原型并没有被销毁,而且它和Bob1
还保持着联系
3.之后的实例,也就是这里的Bob2,Bob3
,都是与新原型保持联系;所以Bob1
、Bob2,Bob3
有各自的构造器原型,即使它们的构造器是同一个
寄生模式相当于工厂模式的一个子集,但是它需要遵守使用new操作符来定义新的对象(工厂模式无需考虑)
//创建
function createPerson(name, age){
var myObj = new Object();
myObj.name = name;
myObj.age = age;
myObj.say = function() {
console.log("你好,我是" + this.name); //this.name直接调用对象中的属性
}
return myObj;
}
//实例化
var Bob = new createPerson("Bob",20);
类似Object,Array,Date等等的拥有原生构造函数的引用类型并不能直接修改其原生构造函数,那么此时寄生构造函数就派上用场了,该模式和构造函数和原型无缘,也就是不能区分实例类型,因为该模式生成的实例,它的构造函数都是对应类型原生构造,原型都是类型.prototype
使用网上最常见数组例子验证:创建一个与数组原生构造不同的特殊数组
function SpecialArray () {
var values = new Array();
Array.prototype.push.apply(values,arguments);
values.toPipedString = function () {
return this.join('|')
}
return values
}
var Bob = new SpecialArray('Bob','20','say');
console.log(Bob.toPipedString()); //Bob|20|say
console.log(Bob[1]); //20
console.log(Bob.__proto__ === Array.prototype); //true
console.log(Bob.__proto__.constructor === Array); //true
在一些安全的环境中使用
//创建
function createPerson(name, age){
var myObj = new Object();
myObj.name = name;
myObj.age = age;
myObj.say = function() {
console.log("你好,我是" + name); //this.name直接调用对象中的属性
}
return myObj;
}
//实例化
var Bob1 = createPerson("Bob1",20);
var Bob2 = createPerson("Bob2",21);
console.log(Bob1.say()); //Bob|20|say
console.log(Bob2.say()); //20