理解JS的原型对象,构造函数,对象之间的关系

时间:2022-02-25 14:42:42

第一次看原型的时候感觉没咋看懂,这周又重新把原型对象,构造函数,对象看了一遍,理解颇多。在理解这三者及这三者的关系之前必须先熟悉的掌握一些前提知识,不然容易混淆。

前提补充:

  1. 对象(键值对的集合)的动态特性:
    对象创建出来为对象添加新的属性和方法/访问对象的方法和属性:
  • 点语法 :使用点语法进行赋值的时候,如果对象中存在该属性,则是修改操作;如果对象中不存在该属性,则是给对象添加此属性并且赋值
  • [ ] 法 :对象名[属性名], 这里的属性名是字符串
  1. 创建对象的方式
  • 对象字面量:
var obj={key: value,key: value,key: value};
  • 使用内置构造函数
var obj=new Object();
  • 工厂函数
function createObj(name,age){
            var o =new Object();
            o.name = name;
            o.age = age;

            o.sayName = function () {
                console.log("我的名字是:"+name);
            }
            return o;
        }

        var obj = createObj("张三",18);
  1. this和new: new创建一个对象实例,这个对象是用户自定义的,也可以是一些带构造函数的系统自带的对象,new关键字让this不再指向window,而是指向new的那个对象(new改变this指向的对象)。
function fun() { alert(this);
}
fun();// 这个this 指向的是window(窗口)


function fn() {
    alert(this);
}
new fn();  
// 添加了new 的函数 this 指向的是 新的对象
  1. 构造函数:构造函数也是函数,只是通常用来初始化对象,并且和new关键字一起出现。构造函数首字母务必是大写。
  1. 构造函数的返回值:
  • 如果不写返回值,默认返回的是新创建出来的对象 (一般都不写return语句)
  • 如果写return语句 return的是空值(return;),或者是基本类型的值 或者null,都会默认返回新创建出来的对象
  • 如果返回的是object类型的值,将不会返回刚才新创建的对象,而是返回return后面的值
  • 一个简单构造函数的例子:
function Person(name,age) {  //记得首字母大写
    this.name = name;
    this.age = age;
    this.sayHello = function () {
        console.log("Hey");
    }
    //此处一般不写return
}

var p = new Person("张三",18);   //构造函数默认返回新创建的对象
//new 使this指向新创建出来的对象
console.log(p);
p.sayHello();
  • 也可以这样写:
//此处是方法
function sayNameMe(){
    console.log("我叫"+ this.name);
}

function Person(name,age) {
    this.name = name;
    this.age = age;
    this.sayName = sayNameMe;
    //把方法写在外面
}

var zs = new Person("张三",age);
zs.sayName();

这样写的原因:如果在构造函数中定义函数(添加方法),那么每次创建对象,都会重新创建该函数。但是函数内部代码完全相同,就造成了资源浪费。为了处理这个问题,我们要让所有的对象共用一个方法,在构造函数的外部定义好该函数,将该函数赋值给构造函数内的方法。但是使用这种方法也会导致全局变量增多(每一个方法对应的函数都是一个新的全局变量)。

  • 实例化和示例:实例化就是用构造函数new出来的新对象的过程,而一个个新对象就是一个个示例。


上面的补充主要是为了大家可以更好的掌握原型,那么这时候你肯定想说原型到底是什么?为什么要用原型?下来我们就开始介绍原型啦。 为什么要用原型?现在我们已经知道了,如果单纯的使用构造函数(把方法放到函数的外面)会使全局变量增多,造成污染,而解决这个问题的办法就是使用原型。

原型prototype


  1. 什么是原型:其实原型是一个对象。是在构造函数创建的时候,系统会自动的给这个构造函数关联一个对象,这个对象就是原型。
  2. 原型的作用:原型中的属性可以被它的构造函数创建的每一个对象使用。所以我们可以将构造函数中需要创建的函数,放到原型对象中存储,解决上面提到的问题。
function Person(name,age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        console.log("我叫"+this.name);
    }
}
var zs = new Person("张三",18);
  1. 访问原型对象的方法:

1.通过构造函数访问原型对象:构造函数.prototype

console.log(Person.prototype);

2.通过对象访问原型对象:对象.--proto--

--proto--是一个非标准属性,原型对象中的属性。可以使用 对象.--proto-- 去访问原型对象

  1. new对象 访问 原型里的属性或者方法:
  1. 点语法:对象 . 原型里的属性

注意:用点语法访问属性和方法时,会首先在对象自己内部查找,如果找到了就直接只用,如果没有找到才去原型中查找,如果找到了直接使用,如果原型中也没有的话属性会Undefined,方法会报错。

2.构造函数.prototype[属性] 3.对象.--prototype--.属性

  1. 给原型对象添加属性/方法:

1.点语法:构造函数 . prototype . 新的属性/方法

Person.prototype.sayAge = function () {
    console.log("我"+this.age+"岁");
}

2.[ ]法:构造函数 . prototype [新的属性/方法]

Person.prototype["sayAge"]=fuction () {
    console.log("我"+this.age+"岁");
}

3.直接替换原型对象:注意在替换原型之前构造函数 创造的对象的原型和替换后构造函数创建的对象的原型不同

function Person(name,age){
	this.name=name;
	this.age=age;
}
Person.prototype.sayName=function(){
	console.log("我叫"+this.name);
}
var p1=new Person("张三",18);
//直接替换原型对象
Person.prototype={
	askName: function(){
		console.log("我的名字是"+this.name);
	}
}
var p2= new Person("张三",18);
// p2.sayName(); 替换后和之前的原型不一样 不能再使用
p2.askName();

6.使用原型的注意事项:

  • 使用对象访问属性时,如果在本身内找不到就会在原型中找,但是使用点语法给赋值(注意是赋值)时,并不会去原型中查找。点语法赋值时,如果对象没有该属性,就会给对象新增该属性并赋值,如果有该属性则是修改属性值,而不会修改原型中的东西。意思就是点语法赋值的时候不会影响原型中的属性。
//如下:
function Person(){
}
Person.prototype.name = "张三";
Person.prototype.age = 18;

var p = new Person();
console.log(p.name);//张三

p.name = "李四";
console.log(p.name);//李四

var p1 = new Person();
console.log(p1.name);//张三
  • 当原型中的属性是引用类型的属性时,那么所有的对象共享该属性,并且一个对象修改了该引用类型属性的成员时,其他对象也会受到影响。(这里你需要知道引用类型和值类型的不同处)。
//如下:
function Person(){

}
var x = {
    name:"张三",
    age:18
};
Person.prototype.peo =x;//原型中的属性是引用类型
var p = new Person();
console.log(p.peo.name);//张三

Person.prototype.peo = {
    name:"李四"
};
var p1 =new Person();
console.log(p1.peo.name);//李四
console.log(p.peo.name);//李四
  • 一般情况下不会将属性放到原型对象中,一般情况下原型中只会放需要共享的方法。
  1. constructor属性:
    原型对象在构造出来时就会默认有一个constructor属性,指向对应的构造函数,当然该构造函数构造出来的对象也可以使用该属性(对象可以使用它的构造函数的原型对象中所有得属性和方法)
如下:
function Person(name, age){
    this.name = name;
    this.age = age;
}
var p = new Person("张三",18);
var p1 = new p.constructor("张三1",19);
//相当于直接使用 new Person()

console.log(p1);

注意:在使用新的对象替换默认的原型对象时,原型对象中的constructor属性会变为Object,所有当你需要为了保证 构造函数--原型对象--对象之间关系的合理性,应该在替换原型对象时,给新的对象添加constructor属性(经常:构造函数.prototype.constructor=构造函数)。

理解JS的原型对象,构造函数,对象之间的关系

  1. 将属性写在原型里与将属性写在构造函数里有什么不同:
  • 把方法写在原型中比写在构造函数中消耗的内存更小,因为在内存中一个类的原型只有一个,写在原型中的行为可以被所有实例共享,实例化的时候并不会在实例的内存中再复制一份 而写在类中的方法,实例化的时候会在每个实例中再复制一份,所以消耗的内存更高 所以没有特殊原因,我们一般把属性写到类中,而行为写到原型中
  • 构造函数中定义的属性和方法要比原型中定义的属性和方法的优先级高,如果定义了同名称的属性和方法,构造函数中的将会覆盖原型中的
  1. hasOwnProperty:
    用来判断一个对象是否有你给出名称的属性或对象,返回值为Boolean类型。不过需要注意的是,此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员。
function Person(){

}
var p = new Person();
p.name="李四";
console.log(p.hasOwnProperty("name"));//true
console.log(p.hasOwnProperty("__proto__"));//false
  1. isPrototypeOf:
    用来判断指定对象object1是否存在于另一个对象object2的原型链中,是则返回true,否则返回false。
// object1.isPrototypeOf(object2); 
// object1是一个对象的实例; 
// object2是另一个将要检查其原型链的对象。
  1. propertyIsEnumerable:
    用来检测属性是否属于某个对象的,如果检测到了,返回true,否则返回false.
  • 这个属性必须属于实例的,并且不属于原型.
  • 这个属性必须是可枚举的,也就是自定义的属性,可以通过for..in循环出来的.
  • ps:构造函数里面的属性也是实例的属性
  • 所以可以用来判断 :1.属性是否属于对象本身 2.判断属性是否可以被遍历
  1. Object.defineProperty():
  • 语法:Object.defineProperty(obj, prop, descriptor)
  • 参数说明: obj:必需,目标对象;
    prop:必需,需定义或修改的属性的名字;
    descriptor:必需,目标属性所拥有的特性;
  • 返回值:传入函数的对象。即第一个参数obj
  • 我们可以给这个属性设置一些特性,比如是否只读不可以写;是否可以被for..in或Object.keys()遍历。
  • 这个方法是参考来的,详细请看:https://segmentfault.com/a/1190000007434923
  1. toString 和 toLocaleString
ar o = {};
// console.log(o.toString());
// console.log(o.toLocaleString());
//
// var now = new Date();
// console.log(now.toString());
// console.log(now.toLocaleString());
  1. valuOf():用于返回指定对象的原始值。该方法属于Object对象,由于所有的对象都"继承"了Object的对象实例,因此几乎所有的实例对象都可以使用该方法。
  • 语法:object.valueOf()
  • 在对象参与运算的时候,默认的会先去调用对象的valueOf方法,如果valueOf获取到的值,无法进行运算 ,就去去调用toString方法最终做的就是字符串拼接的工作。
function Person(){
	this.valueOf=function(){
		return 1;
	}
}
var p = new Person();
console.log( 1 + p); //2 计算
function Person(){
	
}
var p = new Person();
console.log( 1 + p); //1[object Object] 拼接