初识JavaScript对象与继承
JavaScript中的“对象”可谓是“王”一般的存在。对象是一个“个体”,其中的属性定义了这个“个体”的信息。
首先创建一个对象可以使用如下的方法:
//第一种,直接new出来一个 var test_one = new Object(); // 然后运用点方法为其增加属性 test_one.name = 'hello'; // 第二种为用对象字面量法 // 这种方法好用,但是如果要为其增加属性,不得使用字面量,因为会重新定义(或者说把新的东西赋值给了对象) var test_two = {name:'hello',age:100}; console.log(test_one.name,test_two.age);
接下来便是属性类型,属性由一些特征值去设置
(简单点理解就是你设置了一个属性,然后要设置这个属性的一些操作,比如可不可以修改,可不可以显示出来。。。)
属性类型分为数据属性与访问器属性,且都有四个特征值。
数据属性的特征值为:Configurable,Enumerable,Writable,Value。
访问器属性的特征值:Configurable,Enumerable,get,set。
configurable:即设置属性能否被删除,或者重新定义,修改属性。
writable:设置能否修改属性的值。
enumerable:属性是否可以被for循环返回。
value:属性的值。
想要修改某属性的特征值可以采用object.difineProtype()函数,其接受三个参数分别为:属性所在的的对象,属性,一个描述符对象(描述符对象里存放着以特征值为属性名的数据)
看了也不懂,我们直接看例子
var test = {age:12} // 为test设置一个数据类型的属性 // 在用object.defineProtype设置一个属性时,如果不指定,configuratable,enumerable,writable,都默认为false // 注意传的属性名字是字符串的 Object.defineProperty(test,'name',{value:'hello',configurable:true,enumerable:false}) test.name = 'baibai' // 在这里我们没有设置writable所以用object.defineProtype创建的属性默认为false console.log(test.name); // 由于enumerable为false,所以name属性不可以被迭代 for (var i in test){ console.log(i); } console.log('-----------分割线---------'); // 重新设置name属性,设置writable和writable Object.defineProperty(test,'name',{value:'hello',configurable:true,enumerable:true,writable:true}) test.name = 'baibai' // 发现可以修改了 console.log(test.name); //可以被迭代 for (var i in test){ console.log(i); } console.log('---------分割线--------'); //但当configurable设置为false,属性不可以像上面那样可以改进行重新定义 //而且也不可以重新设置configurable为ture。 Object.defineProperty(test,'name',{value:'hello',configurable:false,enumerable:false}) // 会报错 Object.defineProperty(test,'name',{value:'world',configurable:true,enumerable:false})
访问器属性与数据属性差不多,不同在于get函数与set函数,这里举个例子:
// 通过改变对象person在哪一年开始读书,相应的改变这个人读了几年书 var person = { inschool_age:2008, //2008年开始读书 readbook_age:12 //读了12年了 } Object.defineProperty(person,'inschool_age',{ get :function(){ return this.inschool_age }, set:function(inschool_age){ this.readbook_age = 2020 - inschool_age } }) person.inschool_age = 2010; console.log(person.readbook_age);
二、有了对象,我们可以设置一个个体的属性,达到操作的目的。
但如果一个一个操作,着实有点麻烦。为此,我们需要找到这些对象之间有的共性。比如学生有学号,我们可以将学生作为一个群体,对于每次构造学生这个对象的时候,都赋予学号这个属性。相当于通过这个类型可以创造出具有相同属性,值不同的对象;
同时学生拥有学号,工人拥有工号,其实本质上差别不大,那可以那学生的模板来用在工人身上,这就是需要将学生的属性“继承”到工人身上。
1、在这里我们先了解一个东西----指针(C语言之灵魂)。
指针(个人理解):即存放一个变量,也占据内存,但其存放的值,是另外一个变量的地址,指向了那个变量。算了,看图。
指针存放了变量的地址,然后指向了变量。相当于定义了一个变量(指针)存放的不是值,而是另一个变量所存放的地址。(个人见解)
2、构造函数:以下均为个人见解
第一种
function people(name,year){ var test = {}; test.name = name; test.year = year; test.sayhello = function (){console.log('hello');} return test } var one = people('hello',10); var two = people('one',23) console.log(one.name); //在这里我们发现one不是基于people的实例 console.log(one instanceof people); //false console.log(one.sayhello == two.sayhello); //false console.log(test.name); //报错
个人的猜测:对象可以算一个指针,但又可以存放更多的数据,不只存放地址,(也可以说对象其实是一块和程序声明属于自己的内存区域)。在将people('hello',10)赋给one时,应该传的是数据即 var one = {name:'helllo',year:10};而不是直接把一个对象给one,而且test对象在函数执行完就不存在了,所以访问不了。
function people(name,year){ this.name = name; this.year = year; } var one = new people('hello',10); console.log(one.name); console.log(one instanceof people); //true
个人的猜测:new一下,就会有一个对象,那这个对象指向哪?上面例子new 指向了函数people,然后function的this又回指了new出来的对象实例。(因为函数也是对象)
前面两个例子第一个因为无论怎么创建,对象都是基于object创建的,而不是我们自己定义的类型。还有每一次定义下来的方法是一个实例的方法,不是共有的。
第二个例子解决了自定义类型的问题而已。所以
原型模式:
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象(原型对象),而这个对象的用途是包含可以由特定类型的所有实例共享的方法和属性。在默认情况下,所有原型对象都会自动获得一个constructor属性,这是一个指向prototype属性所在函数的指针。如图(图片来源:红宝书):
function people(){ } people.prototype.name ='D'; people.prototype.age = 12; people.prototype.sayhello = function(){ console.log('hello'); } var person1 = new people(); var person2 = new people(); person1.sayhello(); person2.sayhello(); console.log(person1.sayhello == person2.sayhello); //true
同时,也可以修改对象的已有属性,或者添加属性。注意:修改并不是修改,而是为实例添加,从而覆盖掉原型的同名属性。如例子:
function people(){ } people.prototype.name ='D'; people.prototype.age = 12; people.prototype.sayhello = function(){ console.log('hello'); } var person1 = new people(); var person2 = new people(); person1.sayhello(); person2.sayhello(); console.log(person1.sayhello == person2.sayhello); console.log(person1.name == person2.name); console.log(person1.name); //当为对象添加原型已有的属性,则会覆盖原型的属性,而用实例的属性 person1.name = '马云'; console.log(person1.name); console.log(person1.name == person2.name); //可见更改之后,用的不是原型的属性name了 //也可以使用hasOwnprotype()检测属性是实例的还是原型的。
原型模式也可以采用字面量的方式赋值简化,但是需要为constructor设置为其prototype所在函数的函数名。因为字面量方式相当于重写了原型模式。并没有默认的带一个指针constructor。
function people(){ } people.prototype = { construtor:people, name:'D', }
以这种方式设置的constructor默认是可枚举的,而原本的是不可以被枚举的。也可以使用前面的访问器属性重新设置该属性。这里不举例了。
同时字面量方式修改原型对象,也会影响原型对象的动态性(即实例后的对象,如果我为原型对象增加了新的方法,会立马反应到实列中)。如:
function people(){ } people.prototype.name = 'D'; var one = new people(); people.prototype.age = 12; //age属性在实例化one后面,却马上反应到了one实例中。 console.log(one.age); people.prototype = { name:'d' } var two = new people(); console.log(two.name,two.age);
原型对象在基本数据的共享是可以的,但是如果运用引用类型的数据,就不行。因为引用类型值的属性,是一个指针,指向了数据存放的地址。如果共享了的话,相当于每一个实例的引用类型属性,所存放的都是同一个地址。如:
function students(){ } students.prototype.name = ['马云','比尔盖茨']; var one = new students(); var two = new students(); one.name[2]='华为云'; console.log(one.name); //并没有设置two的属性,但却更改了 console.log(two.name);
前面我们由于不使用构造函数是因为每一次都会为实例添加一个相同的方法或属性,达不到共用的效果。后面我们说了原型可以实训共用,但要设置属于自己的,则只有通过创建相同属性覆盖掉原型。所以将两者都使用,构造函数用于特有的属性和方法设置,原型模式用于共享。如:
function student(name,age){ this.name = name; this.age = age; } student.prototype.sayhello = function (){ console.log('hello'); } var one = new student('D',2); var two = new student('d',3); console.log(two.age == one.age); //由于这个属性是每个对象特有的,使用false console.log(two.sayhello == one.sayhello); //sayhello在原型对象中,所以为ture one.sayhello(); two.sayhello();
原型链:前面我们说到,为实例添加一个原型对象拥有的属性,会覆盖掉原型对象的同名属性,其实是每一次,都进行一次搜索。即先从实例搜一下,没有,那去原型对象看看,原型对象也没有,那就去原型对象的原型对象看看。。。。。从上面那里,我们知道原型链就像‘你的祖宗’,如果每一辈有一个传家宝,那我想要知道这个传家宝是那一辈留下了的,就要往上找!!!
继承:我们知道构造函数都有一个prototype属性指向了它的原型对象,然后原型对象有一个constructor属性回指了构造函数,实例有一个[[prototype]]属性指向了原型对象。这样实例是不是就拥有了原型对象的所有属性和方法。那我们将另一个原型对象(2)设置为该原型对象(1)的实例,是不是这个原型对象(2)就有了原型对象(1)的所有属性和方法。如:
function people(){ this.name = 'D'; } people.prototype.sayhello=function(){console.log('hello');} function student(){ } student.prototype = new people(); var test = new student(); test.sayhello(); console.log(test.name);
在这个例子中,我们发现student继承了people的方法,但连在构造函数中的属性name也继承了,是为什么?前面我们说到原型链,找不到属性,会向上级找,所以在执行student.prototype时,该原型对象已经被改写了,里面没有constructor属性,所以,他就向他的上级(即被继承的原型对象)找,找到了people。constructor的属性,而该属性指向了构造函数people。
起初认为这样子把构造函数继承下来也挺好的,但是后面问题就是:如何给子类型添加属性?去改父类型!那父类型也要更改?(其实可以将想加在子类的属性,在父类设置,传参的时候,父类就传自己有的实参,而运用子类的时候,就传比父类更多的参数,因为多余的形参会是undefined)- --->本人的胡思乱想,哈哈。
解决的办法就是将父类的构造函数调到子类的构造函数中使用
function student(name,age,year){ this.name = name; this.age = age; this.year = year; } student.prototype.sayhello = function(){ console.log('hello'); } function teacher(name,age,year,classbook){ student.call(this,name,age,year) //运用call方法把父类的构造函数调到teacher中使用 this.classbook = classbook; //为子类添加新的属性 } teacher.prototype = new student(); teacher.prototype.construtor = teacher; //让子类的构造函数指向他自己的构造函数 teacher.prototype.response = function (){ console.log('hello too!'); } var student_one = new student('D',24,1); var teacher_one = new teacher('d',20,2,'huaxue'); student_one.sayhello(); console.log(student_one.name,student_one.age,student_one.year); console.log(teacher_one.name,teacher_one.classbook); teacher_one.response(); teacher_one.sayhello();
将原型对象更改为另外一条原型链的某一结点的原型对象,就好比:这个传家宝找到了,在你爷爷那一辈传下来的,那么这个传家宝是自己做出来的,还是花钱从别人那里买下来(继承),又或是抢回来的(哈哈哈哈),后来作为传家宝的。
个人感觉JavaScript的类型,其实只有一种即object,所有的类型都是基于object prototype原型而写的。比如:
function people(){} //个人觉得在此步就已经运用了object的原型对象,然后people作为object的一个实例 function student(){} stuedent.prototype = new people(); //student从people那里买(抢)传家宝。
就吐槽到这了,如此文有错误,希望能为我指出,因为很多都是自己的看法。。。。。。。
- 点赞
- 收藏
- 关注作者
评论(0)