JS构造函数原理与原型
1.创建对象有以下几种方式:
①.var obj = {};
②.var obj = new Object();
③.自定义构造函数,然后使用构造函数创建对象
【构造函数和普通函数的区别:函数名遵循大驼峰式命名规则,通常我们使用构造函数创建对象】
2.构造函数内部原理
①.在函数体内部隐式的加上 var this = {}【当然这个var this={}实际上是在AO对象中创建的!】
②.执行this.xxx = xxx;
③.隐式的返回this
代码如下所示:
1 <script type="text/javascript"> 2 function Student(name,age,sex){ 3 /* var this = { 4 name:"", 5 age: 6 }; */ 7 this.name = name; 8 this.age = age; 9 this.sex = sex; 10 this.grade = 2017; 11 12 //return this; 13 } 14 15 var student = new Student('zhangsan',13,'male'); 16 </script>
【需要特别注意:构造函数需要发现new关键字之后才会有构造函数的上面的三步走原理,否则构造函数就和一个普通的函数一样,没啥区别!】
1 <script type="text/javascript"> 2 function Student(name,age,sex){ 3 //var this = {} 4 this.name = name; 5 this.age = age; 6 this.sex = sex; 7 this.say = function(){ 8 console.log(this.name); 9 } 10 //return this 11 } 12 13 var student = new Student('zhangsan',13,'male'); 14 student.say(); 15 16 </script>
而且,一旦我们new一个构造函数来创建对象,那么这个函数不可以返回一个原始值【如果我们返回一个原始值,那么构造函数是会忽略掉这个原始值的,直接返回隐式的this对象】,但是可以返回一个对象、数组等非原始值!
如下代码所示:
1 <script type="text/javascript"> 2 function Student(name,age,sex){ 3 //var this = {} 4 this.name = name; 5 this.age = age; 6 this.sex = sex; 7 this.say = function(){ 8 console.log(this.name); 9 } 10 //return this 11 } 12 13 var student = new Student('zhangsan',13,'male'); 14 </script>
在控制台上我们输入student,就可以查看该student对象,如下所示:
当然,即使是我们显示的返回this,或者{}、或者一个数组[]等非原始值都是没有问题的,如下所示:
但是如果我们在构造函数里面返回的是一个原始值,如:123结果就会看到我们在控制台上找student对象的时候并不是显示123,而是显示的隐式this对象的内容,这是因为构造函数是不允许使用原始值作为函数的返回值的,既是是构造函数返回了一个原始值,那么这个原始值也会被忽略,而返回this对象,所以这里有问题,如下所示:
3.包装类
①.new String();
②.new Boolean()
③.new Number()
注意:在javascript中有两种数字【123,new Number(123)也即:原始值123、对象123】和两种字符串【原始值""字符串、对象字符串new String()】、以及两种boolean值【原始布尔值true或者false、对象布尔值true或者false】
之所以讲有两种数字和两种字符串是因为原始值字符串和原始值数字是不可以有属性和方法的,而对象数字和对象字符串才是可以有属性和方法的!而且对象数字也是可以直接参与运算的,但是运算完成之后就成了原始值了,就不是对象了!上述对于字符串和布尔值也是适用的!
注意:原始值是不能加属性的,但是如下代码是怎么回事呢?
不是说原始字符串没有属性和方法么,为啥这里可以打印出str.length的属性值呢?
再来一个:
不是原始数值不可以加属性么,为什么console.log(num.len)没有错误呢?
以上这两种情况都是由于包装类的原因【切记:原始值是没有属性和方法的!】
实际上上述代码当我们使用num.len=3的时候,实际上js代码会将原始数值转换为:new Number(4).len = 3,并且将这个对象Number删除,即:delete new Number(4),不做其他修改! 然后当我们console.log(num.len)的时候,js非常友善,它又创建了new Number(4)对象,然后在这个对象上面加上len属性,即:new Number(4).len,所以这时候的len属性是undefined!
所以常常有人利用这个来出题:我们都知道数组是可以截串的!例如:
然后面试官就会问:
然后下面的console.log(str)会输出啥呢?很多人就会答错了,实际上这个还是会输出Hello World,,这是要因为当我们在为str赋值属性值的时候,原始字符串会转换成对象字符串new String("Hello World"),然后赋值length属性值为2,就会产生一个截串,之后就将这个字符串对象给删除,当我们再次访问console.log(str)的时候,就会输出原来的"Hello World"字符串!而且我们也可以再次输出str.length属性验证,值应该为11,而不是2,当然这个str.length实际上是new String("Hello World").length值为11;
再来一道测试题,如下所示:
显然test.sign在if语句里面是能赋值的,但是test.sign赋值是new String(test变量代表的值).sign赋的值,赋值完之后又删除了new String(test变量代表的值)这个对象,所以到下面if语句之外我们再打印console.log(test.sign)的时候,打印的结果是undefined;
5.原型
①.定义:原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
②.利用原型特点和概念,可以提取共有属性。
③.对象如何查看原型------>隐式属性 __proto__
④.对象如何查看对象的构造函数----->Constructor
注意:原型实际上就是继承的关系;代码如下所示:
输出的结果如下所示:
再例如:
当然,函数的对象也可以有自己的属性,当自己的属性和继承的原型的属性重叠时,以自己的属性为主,如下所示:
结果如下所示:
注意:一般情况下,我们可以将子类对象的公共的属性提取到其对应的父类中,那么这里原型就是最好的父类,所以我们可以将子类公共属性都提取到原型中,但是我们在原型中如果设置了某个属性,那么函数的每个对象都会继承原型的属性,但是如果我们修改了某个对象的属性【从原型中继承过来的】,那么这仅仅是修改的某个对象本身的属性而不是修改的原型中的属性,代码如下所示:
你会看到我们修改了xuming的lastName值,但是输出的时候lisi的lastName值并没有修改,也就是原型的lastName值并没有修改!
注意:Person的prototype属性也是一个对象,如下代码:
在控制台上打印输出:
注意:我们上面是通过Person.prototype输出的函数的原型,而且我们这里绝对不能使用函数对象的prototype获取原型,如:我们不能
通过 var person = new Person('张三');person.prototype这样的属性来获取Person函数【类】的原型!
注意:我们可以使用delete删除一个对象的属性,如下所示:
结果为:
这样是没问题的,但是如果我们使用的是delete person.lastName,然后再打印输出,你看看是否能删除,
结果为:
你会发现,这是删除不了的,因为js中对象只能删除自己的属性,不是自己的属性,js对象是删除不了的!
而且如果js代码如下所示:
在控制台上我们先删除对象的name属性值,然后在控制台上打印person.name的值的时候,发现:
对象的name属性值是可以被删除的,而且删除的时候返回了true;
如果我们在控制台上使用的是:
就会发现删除person.lastName的时候返回的是true,但是我们在下面却可以直接通过person.lastName再次查看person对象从原型继承过来的属性,也就是实际上person对象只能删除自身的属性,不能删除原型的属性,而且当我们删除对象的一个不存在的属性的时候,也是会返回true的,如下所示:
如果我们的代码如下所示:
那么我们可以使用car.constructor来查看构造car对象的构造函数,如下所示:
这个不是car对象,所以肯定是继承过来的,那是继承的谁的呢,答案是:肯定是继承的Car的原型对象的,我们可以在控制台上打印看一下,如下所示:
这就更加证实了,这是Car类的原型的自带的构造函数!