创建类和生成实例
class People {
constructor(uname, age) {
this.uname = uname
this.age = age
}
}
let zs = new People('张三', 18);
let ls = new People('李四', 20);
1、通过class 关键字创建类,类名首字母大写
2、类里有constructor函数,接受传过来的参数,同时返回实例对象
3、只要new生成实例时,就会自动调用constructor函数。若不写,类也会自动生成该函数
4、new不能省略,注意规范,创建类后面不加小括号,生成实例后面要加小括号,构造函数不需要添加function
类中添加共有方法
class People {
constructor(uname, age) {
this.uname = uname
this.age = age
}
sing (song) {
console.log(this.uname + '会唱' + song);
}
}
let ldh = new People('刘德华');
ldh.sing('男人哭吧哭吧不是罪')
继承extends
创建一个类,该类是另一个类的子类
class Father {
money() {
console.log('这是父亲的遗产');
}
}
class Son extends Father {
}
let son = new Son();
son.money();
super关键字
用于访问和调用一个对象的父对象上的函数。相当于是指向当前对象的父类的引用。
在构造函数中使用时,super
关键字将单独出现,并且必须在使用this
关键字之前使用。
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
let son = new Son(1, 2);
// 报错 Must call super constructor in derived class before accessing 'this' or returning from derived constructor
son.sum();
分析: 父类(Father)的方法(sum)中,this指向的是父类constructor里的数据,构造函数Son的数据1,2指向的是子类constructor里的数据
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
super(x, y); // 调用了父类中的构造函数
}
}
let son = new Son(1, 2);
son.sum();
super
关键字也可以用来调用父对象上的函数。
class Father {
say() {
return '我是父亲';
}
}
class Son extends Father {
say() {
console.log(super.say() + '的儿子');
}
}
let son = new Son();
son.say();
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
// this.x = x; // ReferenceError,super 需要先被调用!
super(x, y); // 调用了父类中的构造函数
// 在派生的类中, 在你可以使用'this'之前, 必须先调用super()
this.x = x;
this.y = y;
}
sub() {
console.log(this.x - this.y);
}
}
let son = new Son(10, 2);
son.sum();
son.sub();
创建对象
// 利用new Object 创建对象
let obj = new Object();
// 利用对象字面量 创建对象
let obj1 = {}
// 利用构造函数 创建对象
function People (uname, age) {
this.uname = uname;
this.age = age
}
let obj2 = new People('对象', 18)
实例成员和静态成员
// 实例成员是构造函数内部通过this添加的成员,uname age sing 就是实例成员
function People (uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('我会唱歌');
}
}
let ldh = new People('刘德华', 18);
// 实例成员只能通过实例化的对象来访问
console.log(ldh.uname);
ldh.sing();
// console.log(People.uname); // undefined,不可以通过构造函数来访问实例成员
// 静态成员 在构造函数本身上添加的成员, sex 就是静态成员
People.sex = '男'
console.log(ldh.sex); // undefined,不可以通过对象来访问静态成员
console.log(People.sex);
构造函数和原型
构造函数的问题,存在浪费内存的问题。每new一个函数, 就会在内存中,生成一个独立的内存区域,用来存储当前的对象,以及对象上面的方法和属性。
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('我会唱歌');
}
}
let ldh = new Star('刘德华', 18); // new 一个对象 ===》 function () {}
let zxy = new Star('张学友', 20); // new 一个对象 ===》 function () {}......
console.log(ldh.sing === zxy.sing) // false
所有引用类型(函数,数组,对象)都拥有__proto__属性(隐式原型)
所有函数拥有prototype属性(显示原型)(仅限函数)
let a = {};
console.log(a.prototype); //undefined
console.log(a.__proto__); //Object{}
let b = function(){};
console.log(b.prototype); //Object{}
console.log(b.__proto__); //function(){}
// 由上述代码可知,prototype是函数才有的属性
原型是一种对象(属性的集合),除了constructor
外,还可以自定义许多属性。
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
对象原型__proto__
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
let ldh = new Star('刘德华', 18); // 对象上系统自动添加一个__proto__ 指向我们构造函数的原型对象
ldh.sing();
console.log(ldh.__proto__ === Star.prototype ) // true
// 对象调用方法查找规则
// 先查看ldh对象身上是否有sing方法,如果有就执行这个对象上的sing
// 如果没有,因为ldh对象上有__proto__的存在,就会去构造函数原型对象prototype身上去查找sing方法
1、Star构造函数有个原型对象 Star.prototype,把sing方法放到原型对象里面
// Star.prototype
2、利用构造函数,new一个 ldh 实例对象,ldh这个对象实例,并不存在sing方法,
但是实例对象上因有__proto__原型的存在,指向了构造函数的原型对象prototype
constructor 属性
对象原型__proto__和构造函数prototype原型对象里有个constructor属性,该属性指向的是构造函数的本身。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function () {
console.log('我会唱歌');
}
let ldh = new Star('刘德华', 18);
console.log(Star.prototype) console.log(ldh.__proto__)
//constructor指向的是构造函数Star本身
constructor主要是用于记录该对象引用于哪个构造函数,它可以让原型对象指向原来的构造函数。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// Star.prototype.sing = function () {
// console.log('我会唱歌');
// }
// Star.prototype.movie = function () {
// console.log('我会演电影');
// }
Star.prototype = {
sing: function () {
console.log('我会唱歌');
},
movie: function () {
console.log('我会演电影');
}
}
let ldh = new Star('刘德华', 18);
// 不再指向构造函数本身 需要手动利用constructor这个属性指回原来的构造函数
console.log(Star.prototype.constructor)
console.log(ldh.__proto__.constructor)
手动利用constructor这个属性指回原来的构造函数
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype = {
constructor: Star, // 手动利用constructor这个属性指回原来的构造函数
sing: function () {
console.log('我会唱歌');
},
movie: function () {
console.log('我会演电影');
}
}
let ldh = new Star('刘德华', 18);
console.log(Star.prototype.constructor)
console.log(ldh.__proto__.constructor)
如果我们修改了原来的原型对象,并给原型对象赋值的是一个对象,需要手动利用constructor属性指回原来的构造函数。
构造函数、实例、原型对象三者之间的关系
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype = {
constructor: Star, // 手动利用constructor这个属性指回原来的构造函数
sing: function () {
console.log('我会唱歌');
},
movie: function () {
console.log('我会演电影');
}
}
let ldh = new Star('刘德华', 18);
console.log(Star);
console.log(Star.prototype);
console.log(Star.prototype.constructor);
console.log(ldh);
console.log(ldh.__proto__);
console.log(ldh.__proto__.constructor);
1、每个构造函数Star都有一个原型对象prototype,通过Star.prototype指向原型对象
2、原型对象里有个constructor属性,指回了构造函数
3、构造函数通过new,创建了一个实例对象ldh,指向了这个对象实例
4、实例对象ldh有一个__proto__原型,指向原型对象
5、该实例对象的__proto__原型里也有个constructor属性,通过ldh.__proto__指向了原型对象,并通过原型对象指向了构造函数。
原型链
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype = {
constructor: Star, // 手动利用constructor这个属性指回原来的构造函数
sing: function () {
console.log('我会唱歌');
},
movie: function () {
console.log('我会演电影');
}
}
let ldh = new Star('刘德华', 18);
console.log(Star.prototype)
1、ldh这个实例对象里,有个__proto__原型,它指向的是构造函数Star的原型对象prototype
2、Star原型对象也有个__proto__原型,它指向的是Object的原型对象
3、Object原型对象也有个__proto__原型,它指向的是null
这就是原型链。
在构造函数Star中,this指向的是对象实例ldh,原型对象函数里的this,也指向的是实例对象ldh.
利用原型对象扩展内置对象方法
Array.prototype.sum = function () {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
var arr = [1, 2, 3, 4, 5];
console.log(arr.sum()); // 15
console.log(Array.prototype);
继承
call方法的作用
function fn (x, y) {
console.log(this);
console.log('我在调用函数' + x + y);
}
// 1、调用函数fn.call();
// 此时的this指向的是window
// 2、改变this的指向
var o = {
name: '函数'
}
fn.call(o, 1, 2); // 此时的this指向的是o, 后面就是参数
组合继承,利用父构造函数继承属性
// 父构造函数
function Father(uname, age) {
console.log(this, 0);
this.uname = uname;
this.age = age;
}
// 子构造函数
function Son(uname, age) {
console.log(this, 1);
Father.call(this, uname, age);
}
let son = new Son('刘德华', 18);
let fater = new Father('张学友', 20)
console.log(son, 2);
console.log(fater, 3);
new实例对象son,this指向的是构造函数Son,通过call(),将构造函数Father里this指向Son.
new实例对象Father,又会将this指向构造函数Father.
组合继承,利用父构造函数继承方法
// 父构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
Father.prototype.money = function () {
console.log('我是Father');
}
// 子构造函数
function Son(uname, age) {
Father.call(this, uname, age);
}
// 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化,会有exam方法
// Son.prototype = Father.prototype;
Son.prototype = new Father(); // 覆盖了Son里面的constructor
Son.prototype.constructor = Son; // ,需要手动指向构造函数
Son.prototype.exam = function() {
console.log('我要考试');
}
let son = new Son('刘德华', 18);
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype.constructor);