5. 深入面向对象
1. 字面量创建数据和实例的方式创建数据
1. 实例的创建方式,通过new操作符调用当前类型的构造函数,会得到当前类型的一个实例
创建引用数据类型:
var ary2 = new Array(1, 2, 3, 4); // 用实例的方式创建一个数组
ary2.push(5);
var ary = [1, 2, 3, 4]; // 字面量创建数组
ary.push(5);
创建引用基本类型
var str1 = new String('javascript');
console.log(str1.toUpperCase());
var str2 = 'javascript';
console.log(str2.toUpperCase());
console.log(typeof str1); // 'object'
console.log(typeof str2); // 'string'
基本数据类型的只能通过字面量的方式创建,如果用实例的方式创建,这个实例将会是变成一个对象,而对象不是基本数据类型;
2. 字面量创建方式:
创建引用数据类型:字面量创建的引用该数据类型和实例的方式创建的引用数据类型没有区别
// 1. 对象:{}
var obj = {};
// 2. 数组:[]
var ary = [1, 2, 4];
// 3. 正则 /^\d$/
var reg = /^\d$/;
创建基本数据类型:
var num = 1;
var str = 'abc';
var bool = true;
var empty = null;
var notDefined = undefined;
var sym = Symbol('cbd');
new 调用构造函数和普通调用的区别
function Teacher(name, age, subject, from) {
this.name = name;
this.mission = '传道受业解惑';
this.age = age;
this.subject = subject;
this.from = from;
this.teach = function () {
console.log(`${name} 老师教 ${subject} 学科`);
};
// 上面这种通过this.xxx = xxx的方式直接向实例上添加的属性成为私有属性。
var career = '前端'; // 这只是一个私有变量,不会和实例产生联系
}
// 普通函数执行:
let t1 = Teacher('你好', 18, 'js', '啦啦');
console.log(t1); // undefined
let t2 = new Teacher('你好啊', 19, 'js', '啦啦');
console.log(t2); // Teacher {naem:......}
为啥会出现这种情况呢?这是因为new调用和普通调用有着本质的区别;
函数的普通调用过程:
新开辟栈内存作为执行的作用域 形参赋值 私有作用域变量提升 代码从上到下执行 释放栈内存
new 调用时:
开辟栈内存
形参赋值
变量提升
隐式创建一个实例对象,并且把当前构造函数中的this指向这个实例对象。
执行函数体中的代码,当遇到this.xxx = xxx时就是在向实例对象上增加属性;
隐式返回实例对象
释放栈内存
显式设置返回值
返回基本数据类型:
function Teacher(name, age, subject, from) {
this.name = name;
this.mission = '传道受业解惑';
this.age = age;
this.subject = subject;
this.from = from;
this.teach = function () {
console.log(`${name} 老师教 ${subject} 学科`);
};
return 61; // 返回基本类型值
}
let t1 = new Teacher('马宾', 18, 'js', 'zf');
console.log(t1); // Teacher {....}
返回引用数据类型
function Teacher(name, age, subject, from) {
this.name = name;
this.mission = '传道受业解惑';
this.age = age;
this.subject = subject;
this.from = from;
this.teach = function () {
console.log(`${name} 老师教 ${subject} 学科`);
};
return {haha: '哈哈'}
}
let t2 = new Teacher('你好', 19, 'js', '你好啊');
console.log(t2); // {haha: ...}
如果我们手动修改构造函数的返回值时:
如果return一个基本数据类型的值,没有任何影响,不会覆盖原有实例; 如果return 引用数据类型,原有的实例就会被这个引用类型值覆盖
// 慎重修改构造函数的返回值
原型模式
function Teacher(name, age, subject, from) {
this.name = name;
this.mission = '传道受业解惑';
this.age = age;
this.subject = subject;
this.from = from;
this.teach = function () {
console.log(`${name} 老师教 ${subject} 学科`);
};
}
// let t1 = new Teacher('mabin', 18, 'js', 'zf');
// let t2 = new Teacher('mabin', 19, '架构', 'zf');
//
// t1.teach();
// t2.teach();
// console.log(t1.teach === t2.teach); // false 因为t1和t2是两个实例,而teach又是t1和t2的私有方法
原型对象:
原型prototype:每一个函数(普通函数、构造函数【类】)都天生自带一个属性prototype(原型)。这个属性的值是一个对象,用来存储当前类型的共有的属性和方法。保存在原型上面的属性和方法称为公有属性或公有方法。
所以改造Teacher类型:
function Teacher(name, age, subject, from) {
this.name = name;
this.mission = '传道受业解惑';
this.age = age;
this.subject = subject;
this.from = from;
}
Teacher.prototype.teach = function () {
console.log(`${this.name} 老师教 ${this.subject} 学科`);
};
console.log(Teacher.prototype);
let t1 = new Teacher('mabin', 18, 'js', 'zf');
let t2 = new Teacher('jiangwen', 19, '架构', 'zf');
t1.teach();
t2.teach();
console.log(t1.teach === t2.teach); // true
如何检测属性是公有还是私有?
hasOwnProperty() 方法:检测某个属性是否是对象的私有属性,如果是私有属性,返回true,否则返回false;
var r1 = t1.hasOwnProperty('name'); // true
var r2 = t2.hasOwnProperty('teach'); // false
原型链
function Teacher(name, age, subject, from) {
this.name = name;
this.mission = '传道受业解惑';
this.age = age;
this.subject = subject;
this.from = from;
}
Teacher.prototype.teach = function () {
console.log(`${this.name} 老师教 ${this.subject} 学科`);
};
console.log(Teacher.prototype);
let t1 = new Teacher('mabin', 18, 'js', 'zf');
let t2 = new Teacher('jiangwen', 19, '架构', 'zf');
t1.teach();
t2.teach();
原型链:对象的属性查找机制
每个实例都有一个属性proto 属性,它指向当前实例所属类的prototype对象。当我们访对象的一个的属性时,如果有,就使用私有属性,如果没有就通过实例proto找到实例所属类的prototype(原型)上查找,如果找到就使用prototype上的属性,如果还没找到,就通过prototype的proto继续向上查找,一直找到Object的prototype就停止查找。如果还没找到就返回undefined。