继承:
JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。
JavaScript的原型链和Java的Class区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。
前面所讲,JavaScript对象包括了数组Array,函数function,{}对象。JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。在原型对象中定义的方法,对于每个对象是通用的。
例如,创建一个Array
对象:
var arr = [1, 2, 3];
原型链如下:
arr ----> Array.prototype ----> Object.prototype ----> null
typeof(arr); // "object"
typeof(Array.prototype); // "object"
typeof(Array); // function
Array.prototype对象定义了indexOf()、shift()等方法,因此你可以在所有的Array对象上直接调用这些方法。
由于数组的类型是object,如何确定是数组,有以下方式:
Array.isArray(arr); // 返回 true
arr instanceof Array // 返回 true
arr.constructor.toString().indexOf("Array") > -1; // 返回true
创建一个函数时:
function foo() {
return 0;}
原型链是:
foo ----> Function.prototype ----> Object.prototype ----> null
typeof(foo); // "function"
typeof(Function.prototype); // "function"
typeof(Function); // "function"
由于Function.prototype定义了apply()等方法,因此,所有函数都可以调用apply()方法。
如果原型链很长,那么访问一个对象的属性就会因为花更多的时间查找而变得更慢,因此要注意不要把原型链搞得太长。
构造函数:
首先理清之前函数定义的方法:
- 申明式函数定义:function 函数名 (参数){语句块} 没有分号结尾。这种定义方式,会将函数声明提升到该函数所在作用域的最开头,也是就无论你在哪儿使用这种方式声明的函数,在这个作用域内,你都可以调用这个函数为你所用。
- 函数表达式:let fun = function(){}; 有分号结尾。此方式定义的函数,作用域为该定义之后。
- new Function 形式: var fun1 = new Function (arg1 , arg2 ,arg3 ,…, argN , body );有分号结尾。函数的内置对象Function。Function构造函数所有的参数都是字符串类型。除了最后一个参数, 其余的参数都作为生成函数的参数即形参。这里可以没有参数。最后一个参数, 表示的是要创建函数的函数体。这种效率很慢。
var temp=500
function foo1()
{
var temp = 100;
console.log('foo1 this.temp=',this.temp,this)
return temp + this.temp;
}
var foo3 = function()
{
var temp = 100;
console.log('foo3 this.temp=',this.temp,this)
return temp + this.temp;
}
var foo2 = new Function('var temp = 100; return temp + this.temp;');
console.log('foo1 type=',typeof(foo1));
// function,并不会运行里面的程序
console.log(foo1());
/*
foo1 this.temp= 500 Window https://www.liaoxuefeng.com/wiki/1022910821149312/1023021997355072
600
*/
console.log('foo2 type=',typeof(foo2)); // function
console.log(foo2()); // 600
console.log('foo3 type=',typeof(foo3)); // function
console.log(foo3());
/*
foo3 this.temp= 500 Window https://www.liaoxuefeng.com/wiki/1022910821149312/1023021997355072
600
*/
区别:
- 构造函数也是一个普通函数,创建方式和普通函数一样,但普通函数首字母小写,构造函数习惯上首字母大写.
- 作用不一样(构造函数用来新建实例对象)
- 调用方式不一样。普通函数的调用方式:直接调用 person(); 构造函数的调用方式:需要使用new关键字来调用 new Person();
- 构造函数的函数名与类名相同:Person( ) 这个构造函数,Person 既是函数名,也是这个对象的类名。
- 内部用this 来构造属性和方法 。它绑定的
this
指向新创建的对象,并默认返回this
,也就是说,不需要在最后写return this。
function Person(name,job,age)
{
this.name=name;
this.job=job;
this.age=age;
this.sayHi=function()
{
return `Hi,${this.name}!`; //注意这里使用反引号
};
// 不需要return
}
var xiaoming = new Person('小明');
/*
如果不写new,Person就是一个普通函数,它返回undefined,那么变量xiaoming的值就是undefined。
但是,如果写了new,Person就变成了一个构造函数。Person.prototype类似一个类对象,xiaoming类似一个实例对象。
*/
console.log(typeof(Person),"Person:",Person);
// function Person: function Person(name, job, age)
console.log(typeof(xiaoming),"xiaoming:",xiaoming);
// object xiaoming: Object { name: "小明", job: undefined, age: undefined, sayHi: sayHi() }
那么,xiaoming就是一个对象,Person.prototype也是一个对象,其原型链如下:
xiaoming ----> Person.prototype ----> Object.prototype ----> null
typeof(Person.prototype); // "object"
typeof(Person); // "function"
也就是说,xiaoming
的原型指向函数Person的原型。如果你又创建了xiaohong
、xiaojun
,那么这些对象的原型与xiaoming
是一样的,原型链如下:
xiaoming ↘
xiaohong -→ Person.prototype ----> Object.prototype ----> null
xiaojun ↗
new Student()
创建的对象还从原型上获得了一个constructor
属性,它指向函数Student
本身:
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
xiaoming instanceof Student; // true
实例共享一个函数方法:
将各个实例需要的公用的函数放到原型中去,那么这些实例对象实际上只需要共享同一个函数,这样就可以节省内存。
xiaoming=new Person('小明');
xiaohong=new Person('小红');
xiaoming.name; // '小明'
xiaohong.name; // '小红'
xiaoming.sayHi; // function sayHi()
xiaohong.sayHi; // function sayHi()
xiaoming.sayHi === xiaohong.sayHi; // false 说明同一方法在不同对象中分别创建了
公用同一方法,节省内存开销
function Person(name,job,age)
{
this.name=name;
this.job=job;
this.age=age;
/*this.sayHi=function()
{
return `Hi,${this.name}!`
}; 公用方法不在Person函数里面定义,而是在Person.prototype这个原型对象中定义(或者说添加)该方法
*/
// 不需要return
}
Person.prototype.sayHi=function(){return `Hi`;};
var xiaoming = new Person('小明'), xiaohong = new Person('小红');
xiaoming.sayHi; // function sayHi()
xiaohong.sayHi; // function sayHi()
xiaohong.sayHi===xiaoming.sayHi; // true 共用函数
xiaohong.sayHi(); // Hi,小红!
xiaohong.sayHi(); // Hi,小明!
初始化原型对象的一个范例:
// 定义构造函数,使用||来选择默认的初始化值,传入的参数是一个对象
function Student(props) {
this.name = props.name || '匿名'; // 默认值为'匿名'
this.grade = props.grade || 1; // 默认值为1
console.log('this=',this);
}
// 使函数共用,减少内存的开销
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
// 定义一个createStudent()函数,显然:一是不需要new来调用,二是参数非常灵活,可以不传,也可以传少数的参数
function createStudent(props) {
return new Student(props || {})
}
var xiaoming = createStudent({
name: '小明'
});
// 创建实例的时候就会得到this= Student { name: '小明', grade: 1 }
xiaoming.grade; // 1