一、构造函数
构造函数的函数名以大写字母为开头。
JavaScript规定,一个函数可以用new关键字来调用。那么此时将按顺序发生四件事情:
(1)隐秘的创建一个新的空对象
(2)将这个函数里面的this绑定到刚才创建隐秘新对象上
(3)执行函数体里面的语句
(4)返回这个新的对象
function People(){
this.name = "小明";
this.age = 18;
this.sex = "男";
console.log(11);//会打印因为第三步执行了函数体内的语句
}
var xiaoming = new People();//11
console.log(xiaoming);//People {name: "小明", age: 18, sex: "男"}
xiaoming.name;//小明
我们发现People不仅能执行还能通过new返回一个对象,我们就称呼像People这样的函数为构造函数,而xiaoming这样返回出来的对象就称呼为构造函数的实例,(这里就是People的实例)
1.1注意事项
1.1.1如果构造函数内没有this将返回一个空对象。
function People(){
for(let i = 0; i < 3; i++) {
console.log(i);
}
}
var xiaoming = new People();//0,1,2
console.log(xiaoming);//{}
1.1.2 构造函数构造函数中,不允许出现return语句
但出现return语句的时候,如果return的是一个对象,那么通过new放回出来的对象就是return出来的对象,如果是简单数据类型,返回正常情况下该返回的实例。
//正常情况
function People(){
this.name = "小明";
this.age = 18;
this.sex = "男";
}
var xiaoming = new People();
console.log(xiaoming);//People {name: "小明", age: 18, sex: "男"}
//return 一个对象
function People(){
this.name = "小明";
this.age = 18;
this.sex = "男";
return {a: 1, b: 2}
}
var xiaoming = new People();
console.log(xiaoming);// {a: 1, b: 2}
//return 简单数据类型
function People(){
this.name = "小明";
this.age = 18;
this.sex = "男";
return 1
}
var xiaoming = new People();
console.log(xiaoming);//People {name: "小明", age: 18, sex: "男"}
二原型
原型的特点:
1、原型也是对象,原型是函数对象的一个属性
2、原型自带constructor属性, constructor指定构造函数
3、构造函数创建出的对象会继承原型的属性和方法
每个函数都有原型。
function People(){
this.name = "小明";
this.age = 18;
this.sex = "男";
}
People.prototype.sayHello = function() {
console.log('你好');
}
var xiaoming = new People();
console.log(typeof People.prototype);//Object
console.log(People.prototype.constructor);//People
xiaoming.sayHello();//你好
function test(){};
console.log(test.prototype)//{}
2.1实例对象的原型
当一个对象被new出来时,这个实例对象会自带一个_proto_属性,这个属性指向生出这个实例的构造函数的原型。
function People(){
this.name = "小明";
this.age = 18;
this.sex = "男";
}
var xiaoming = new People();
console.log(xiaoming.__proto__ === People.prototype)//true
注意:__proto__前后各有两个英文状态下的下划线
我们把构造函数的原型叫做构造函数实例的原型对象。
__proto__属性,是Chrome自己的属性,别的浏览器不兼容,但是别的浏览器也有原型对象,只不过不能通过proto进行访问而已。
三应用
如果我们吧方法定义在构造函数内部时
function People(name,age){
this.name = name;
this.age = age;
this.sayHello = function() {
console.log('hello')
}
}
let xiaoming = new People('小明',14);
let xiaogang = new People('小刚', 14);
xiaoming .sayHello;//hello
xiaogang.sayHello;//hello
在new一个xiaoming的时候,构造函数中的代码顺序执行,绑定了name,age,sayHello,new一个xiaogang的时候也是如此,你有没有发现,不同的对象拥有相同他方法.因此可以将复用的方法放在原型对象上.
以避免消耗内存。
function People(name,age){
this.name = name;
this.age = age;
}
People.prototype.sayHello = function() {
console.log('hello')
}
let xiaoming = new People('小明',14);
let xiaogang = new People('小刚', 14);
xiaoming .sayHello();//hello
xiaogang.sayHello();//hello
console.log(xiaoming.sayHello === xiaogang.sayHello);//true
四常见的创建对象的模式
4.1工厂模式
function test(name,age) {
let o = new Object();
o.name = name;
o.age = age;
o.sayHello = function() {
console.log('helllo')
};
return o
};
let test1 = test('first',1);
let test2 = test('second', 2);
⼯⼚模式虽然解决了创建多个相似对象的问题,但没有解决对象标识的问题。
4.2构造函数模式
function Test(name,age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log('helllo')
};
};
let test1 = test('first',1);
let test2 = test('second', 2);
相对于工厂模式,构造函数模式的优点:
1、没有显式地创建对象
2、直接将属性和方法赋给了 this 对象
3、没有 return 语句
构造函数模式的缺点: 就是每个⽅法都要在每个实例上重新创建⼀遍。
4.3.原型函数模式
function People(){}
People.prototype = {
constructor: People,
name : 'xiaoming',
age : 18,
sayHello () {
console.log('hello')
}
}
let xiaoming = new People();
let xiaogang = new People();
原型模式的好处是所有对象实例共享它的属性和方法(即所谓的共有属性),
缺点:就是省略了为构造函数传递初始值参数,导致所有的实例对象都是相同的属性和方法,其主要问题在于,如果原型上存在引用类型的值的属性时:
function People(){}
People.prototype = {
constructor: People,
name : 'xiaoming',
age : 18,
sayHello () {
console.log('hello')
},
friend : ['a']
}
let xiaoming = new People();
let xiaogang = new People();
xiaoming.friend.push('b');
console.log(xiaoming.friend);//['a', 'b']
console.log(xiaogang.friend);//['a', 'b']
4.4、混合模式(构造函数模式+原型模式)
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性
function People(name,age){
this.name = name;
this.age = age
}
People.prototype = {
constructor: People,
sayHello () {
console.log('hello')
}
}
let xiaoming = new People('小明',1);
let xiaogang = new People('小刚',2);
五原型链
只要是对象,一定有原型对象(除了Object.prototype),而一个实例通过__proto__属性可以知道它的原型对象,也就是它的构造函数的原型。而它的原型对象也是对象,那么它的原型对象应该也有原型对象。
function People() {};
let people = new People;
console.log(people.__proto__ );
console.log(people.__proto__ .__proto__.constructor);//Object
console.log(people.__proto__ .__proto__.__proto__);//null
Object是一个函数,是系统内置的构造函数,用于创造对象的。Object.prototype是所有对象的原型链终点。
所以,当我们在一个对象上打点调用某个方法的时候,系统会沿着原型链去寻找它的定义,一直找到Object.prototype。
function People() {};
let people = new People;
Object.prototype.sayHello = function() {
console.log('hello')
};
people.sayHello();
Object.prototype是所有对象的原型链的终点,所以我们直接给Object.prototype增加一个方法,那么所有的对象都能调用这个方法.
5.1引用类型的构造函数
我们可以利用原型链的机制,给数组对象增加方法.
所有的引用类型值,都有内置构造函数。比如
new Object()
new Array()
new Function()
new RegExp()
new Date();
以数组为例子;
let arr = [1,2,3,45,6];
console.log(arr.__proto__ === Array.prototype)//true
console.log(arr.__proto__.__proto__.constructor === Object)//true
let arr = [1,2,3,45,6];
let max = -Infinity;
arr.__proto__.max = function() {
for(let i = 0; i < arr.length; i++) {
if(arr[i] >= max) {
max = arr[i]
}
}
return max
}
arr.max()//45
5.2基本数据类型的包装类
基本类型值,也有包装类型。所谓包装类型,就是它的构造函数。
1、new Number()
2、new String()
3、new Boolean()
let num = 10;
console.log(num.__proto__ === Number.prototype)//true
console.log(num.__proto__.__proto__.constructor === Object)//true
let str= '10';
console.log(str.__proto__ === String.prototype)//true
console.log(str.__proto__.__proto__.constructor === Object)//true
let bol= true;
console.log(bol.__proto__ === Boolean.prototype)//true
console.log(bol.__proto__.__proto__.constructor === Object)//true
let un= undefined;
console.log(un.__proto__.constructor)//报错
let nu= null;
console.log(nu.__proto__.constructor)//报错
六对象与属性
6.1打点调用
var obj = {
a : 1,
b : 2,
c : 3
}
obj.__proto__ = {
d : 4
}
console.log(obj.a); //1
console.log(obj.b); //2
console.log(obj.c); //3
console.log(obj.d); //4
6.2in
var obj = {
a : 1,
b : 2,
c : false
}
obj.__proto__ = {
d: 20
}
console.log("a" in obj); //true
console.log("b" in obj); //true
console.log("c" in obj); //true
console.log("d" in obj); //true
in和打点调用,如果自己或者只要在原型链上有这个方法就会被查找到并返回true。
通过for...in循环调用可枚举的属性。
var obj = {
a : 1,
b : 2,
c : false
}
obj.__proto__ = {
d: 20
}
for( let k in obj) {
console.log(k)//a,b,c,d.
}
6.2 hasOwnProperty
这个方法定义在了Object.prototype对象上面,所以任何一个Object都能够拥有这个方法。
var obj = {
a : 1,
b : 2,
c : 3
}
obj.__proto__ = {
d : 4
}
console.log(obj.hasOwnProperty("a")); //true
console.log(obj.hasOwnProperty("b")); //true
console.log(obj.hasOwnProperty("c")); //true
console.log(obj.hasOwnProperty("d")); //false,在原型链上不在实例上
把自己身上的属性输出:
for(let k in obj){
obj.hasOwnProperty(k) && console.log(k);
}
把不在自己身上的属性输出:
var obj = {
a : 1,
b : 2,
c : 3
}
obj.__proto__ = {
d : 4
}
for(let k in obj){
if(!obj.hasOwnProperty(k) && k in obj) {
console.log(k)//d
}
}
6.3 定义多个属性Object.defineProperties()
var test = {};
Object.defineProperties(test, {
_name: {
value: 'chen'
},
name: {
get: function() {
return this._name;
},
set: function(newvalue) {
if (typeof newvalue != 'string') {
throw new Error(TypeError);
} else {
this._name = newvalue;
}
}
}
})
console.log(test.name);//chen
test.name = 10;//typeerror;
test.name = 'aa';
console.log(test.name);//aa
读取属性的描述对象Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor(test, 'name')
//{enumerable: false, configurable: false, get: ƒ, set: ƒ}
6.4 instanceof
function Boy(){};
function Girl(){};
Girl.prototype = new Boy();
let xiaohong = new Girl();
console.log(xiaohong.constructor)//Boy;
console.log(xiaohong instanceof(Boy))//true
console.log(xiaohong instanceof(Girl))//true
首先,实例会继承原型对象的属性和方法(当然包括constructor属性);这里Girl.prototype上的constructor本来应该指向Girl,但由于
Girl.prototype = new Boy(),new Boy()实例继承了Boy.prototype上的constructor:Boy,最后new Girl()也继承了它原型对象new Boy()的constructor。所以xiaohong.constructor为Boy。
instanceof 运算符的机理: 遍访xiaohong这个对象的原型链上的每个原型对象,如果遍访到这个原型对象,是某个构造函数的prototype,那么就认为xiaohong是这个构造函数的实例,返回true。
6.5检测数据类型
Object.prototype.toString.call(123); //"[object Number]"
Object.prototype.toString.call("123"); //"[object String]"
Object.prototype.toString.call(true); //"[object Boolean]"
Object.prototype.toString.call(undefined); //"[object Undefined]"
Object.prototype.toString.call(null); //"[object Null]"
Object.prototype.toString.call(function(){}); //"[object Function]"
Object.prototype.toString.call([]); //"[object Array]"
Object.prototype.toString.call({}); //"[object Object]"
七 继承
7.1原型链继承
简单来说就是把父类的实例作为子类的原型。
function People(name){
this.name = name;
}
People.prototype.sayHello = function(){
alert("你好我是" + this.name);
}
function Student(name,xuehao){
this.name = name;
this.xuehao = xuehao;
}
Student.prototype = new People('大明');
var xiaohong = new Student("小红",1001);
这样子类既可以继承父类的属性、方法又可以在自己追加方法。
不过当父类原型上具有引用类型值的属性时,会有大问题:
function People(name){
this.name = name;
}
People.prototype.arr= [1,2,3]
function Student(name,xuehao){
this.name = name;
this.xuehao = xuehao;
}
Student.prototype = new People('大明');
var xiaohong = new Student("小红",1001);
//**注意这里**
xiaohong.arr.push(4);
console.log(xiaohong.arr);//[1,2,3,4]
var xiaoming = new Student("小明",1001);
console.log(xiaoming.arr);//[1,2,3,4]
7.2构造函数继承
function People(name){
this.name = name;
}
People.prototype.sayHello = function(){
alert("你好我是" + this.name);
}
function Student(name,xuehao){
//核心
People.call(this,name);
this.xuehao = xuehao;
}
Student.prototype.study = function(){
alert("好好学习,天天向上");
}
var xiaohong = new Student("小红",1001);
之前说过,用call先执行函数,然后在执行函数的时候改变this指向,
那么 People.call(this,name),也就好理解了。
缺点,所有的属性和方法只能定义在构造函数身上,无法实现函数的复用,子类也不能访问到父类的原型。
7.3. 组合继承
就是将原型链继承和构造函数继承组合在一起;继承两个优点。
function People(name){
this.name = name;
}
function Student(name,xuehao){
//构造继承
People.call(this,name);
this.xuehao = xuehao;
}
//核心语句,
//原型继承
Student.prototype = new People('大明');
Student.prototype.study = function(){
alert("好好学习,天天向上");
}
var xiaohong = new Student("小红",1001);
xiaohong.sayHello();
缺点:
调用了两次父类构造函数,一次是在创建子类原型时,一次是在子类构造函数中。
7.4. 寄生组合继承
function People(name){
this.name = name;
}
function Student(name,xuehao){
People.call(this,name);
this.xuehao = xuehao;
}
function Fn() {};
Fn.prototype = People.prototype
Student.prototype = new Fn();//核心语句,
Student.prototype.study = function(){
alert("好好学习,天天向上");
}
var xiaohong = new Student("小红",1001);
xiaohong.sayHello();
这里只调用了一次People构造函数,避免了组合继承的缺点。
7.5. 圣杯模式
圣杯模式就是将借用的构造函数封装一下
function inherit(People,Student){
function Fn(){}
Fn.prototype = People.prototype
Student.prototype = new Fn();
Student.prototype.constructor = Student;//增强对象
}
inherit(People,Student )