1 面向对象
- 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
面向对象编程 OOP (Object Oriented Programming)
面向对象的特性:
var xx = new name();
注意: 类必须使用 new 实例化对象
<a name="kFHbY"></a>
## 2.1 类的get和set
```javascript
// get 和 set
class Phone{
get price(){
console.log("价格属性被读取了");
return 'iloveyou';
}
set price(newVal){
console.log('价格属性被修改了');
}
}
//实例化对象
let s = new Phone();
console.log(s.price); // iloveyou
s.price = 'free';
2.2 类中的函数存放类原型对象上
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
say(){
console.log('hello world');
}
}
const a = new Person('heh', 18)
console.log(a);
console.log(Person.prototype);
3 类 constructor 构造函数
- constructor() 方法是类的构造函数(默认方法),用于传递参数, 返回实例对象,
- 通过 new 命令生成对象实例时 ,自动调用该方法。
- 如果没有显示定义, 类内部会自动给我们创建一个constructor()
// 1. 创建类 class 创建一个 明星类
class Star {
// 类的共有属性放到 constructor 里面
constructor(name, age) {
this.name = name;
this.age = age;
}
sing(song) {
console.log(this.uname + '唱' + song);
}
}
// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
console.log(ldh);
一个类只能有一个构造函数
即使有有参构造函数,依然可以无参创建对象, 即
var ldh = new Star();
4 类的继承
// 父类
class Father{ }
// 子类继承父类
class Son extends Father { }
4.1 super 关键字
super 关键字用于访问和调用对象父类上的函数。
可以调用父类的构造函数,也可以调用父类的普通函数
//定义了父类
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); //使用super调用了父类中的构造函数
}
}
var son = new Son(1, 2);
son.sum(); //结果为3
- 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
- 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,super 必须在子类this之前调用
class Son extends Father {
constructor(x, y) {
// 利用super 调用父类的构造函数 super 必须在子类this之前调用,放到this之后会报错
super(x, y);
this.x = x;
this.y = y;
}
subtract() {
console.log(this.x - this.y);
}
}
4.2 ES6类和对象注意点
在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象.
- 类里面的共有属性和方法一定要加this使用.
- 类里面的this指向问题.
- constructor 里面的this指向实例对象, 方法里面的this 指向这个方法的调用者
时刻注意this的指向问题,类里面的共有的属性和方法一定要加this使用.
- constructor中的this指向的是new出来的实例对象
- 自定义的方法,一般也指向的new出来的实例对象
- 绑定事件之后this指向的就是触发事件的事件源
5 构造函数
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。
在 ES6之前, JS 中并没用引入类的概念。而是用一种称为构建函数的特殊函数来定义对象和它们的特征。
5.1 对象创建
// 字面量
var obj = {};
// new关键字
var obj = new Object();
// 构造函数
function Person(name,age){
this.name = name;
this.age = age;
}
var obj = new Person('zs',12);
new 在执行时会做四件事情:
① 在内存中创建一个新的空对象。
② 让 this 指向这个新的对象。
③ 执行构造函数里面的代码,给这个新对象添加属性和方法。
④ 返回这个新对象(所以构造函数里面不需要 return )。
5.2 静态成员和实例成员
- 静态成员:在构造函数上添加的成员称为静态成员,只能由构造函数本身来访问
- 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问(实例成员就是构造函数内部通过this添加的成员)
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
Star.sex = '男';
var ldh = new Star('刘德华', 18);
console.log(Star.sex);//静态成员只能通过构造函数来访问
console.log(ldh.uname);//实例成员只能通过实例化的对象来访问
5.3 构造函数问题
存在浪费内存的问题。
希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎样做呢?
使用构造函数原型prototype
5.4 构造函数原型prototype
拓展:在vuejs挂载全局函数使用到
- 构造函数通过原型分配的函数是所有对象所共享的。
- JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。
- 注意这个 prototype 就是一个对象(prototype也称为原型对象),这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
ldh.sing();//我会唱歌
zxy.sing();//我会唱歌
5.5 对象原型(隐式原型)proto
proto对应的是对象(即实例才有)
prototype对应的是构造函数(类)对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,
- 之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。
- proto对象原型和原型对象 prototype 是等价的
- proto对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性, 因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
5.6 constructor构造函数
- 对象原型( proto)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性
- constructor 我们称 为构造函数,因为它指回构造函数本身
- constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数
一般情况下,对象的方法都在构造函数的原型对象中设置。
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回原来的构造函数
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star, // 手动设置指回原来的构造函数
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
var zxy = new Star('张学友', 19);
console.log(zxy)
5.7 原型链
每一个实例对象又有一个**proto属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__**属性,这样一层一层往上找就形成了原型链。
5.8 构造函数实例和原型对象三角关系
1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的proto属性指向了构造函数的原型对象prototype
3.构造函数的原型对象prototype的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数
5.9 原型对象this指向
原型对象prototype里面放的是方法, 这个方法里面的this 指向的是这个方法的调用者, 也就是这个实例对象
5.10 通过原型为数组扩展内置方法
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
//此时数组对象中已经存在sum()方法了 可以始终 数组.sum()进行数据的求
注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式
6 JavaScript 的成员查找机制(规则)
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 proto指向的 prototype 原型对象)。
③ 如果还没有就查找原型对象的原型(Object的原型对象)。
④ 依此类推一直找到 Object 为止(null)。
⑤ proto对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
7 ES6之前的继承
ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
7.1 call() 改变this指向
fun.call(thisArg, arg1, arg2, ...)
thisArg :当前调用函数 this 的指向对象
arg1,arg2:传递的其他参数
function fn(x, y) {
console.log(this);
console.log(x + y);
}
var o = {
name: 'andy'
};
fn.call(o, 1, 2);//调用了函数此时的this指向了对象o,
7.2 借用构造函数继承父类型属性
核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性
// 父类
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 子类
function Student(name, age, sex, score) {
Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数
this.score = score;
}
var s1 = new Student('zs', 18, '男', 100);
console.dir(s1);
7.3 借用原型对象继承父类型方法
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('孩子要考试');
}
var son = new Son('刘德华', 18, 100);
console.log(son);
8 对象方法
8.1 Object.keys()
Object.keys(对象) 获取到当前对象中的属性名 ,返回值是一个数组
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
var result = Object.keys(obj)
console.log(result) //[id,pname,price,num]
效果类似 for…in
8.2 定义新属性或修改原有的属性Object.defineProperty
Object.defineProperty设置或修改对象中的属性
Object.defineProperty(对象,修改或新增的属性名,{
value:修改或新增的属性的值,
writable: false, //如果值为false 不允许修改这个属性值
enumerable: false,//enumerable 如果值为false 则不允许遍历
configurable: false //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性
})
默认值都为false
8.3 Object.defineProperty中的get方法
let number = 18
let person = {
name: '张三',
sex: '男'
}
Object.defineProperty(person, 'age', {
get(){
return number
}
})
console.log(person);
当读取到Person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get(){ } 是固定写法,若是写其他将是undefined
8.4 Object.defineProperty中的set方法
let number = 18
let person = {
name: '张三',
sex: '男'
}
Object.defineProperty(person, 'age', {
get(){
return number
},
set(value){
console.log('调用了set,值是', value);
number = value
}
})
console.log(person);
当有人修改person的age属性时,set函数(setter)就会被调用,且会修改具体值
通过get和set方法对象就和其他变量产生了关联
Vue2里面的数据劫持,计算属性,数据代理都用到这个Object.defineProperty方法。
8.5 Object.defineProperty数据代理
通过一个对象代理另一个对象中的属性操作(读/写)
let obj1 = {x: 100}
let obj2 = {y: 200}
Object.defineProperty(obj2, 'x', {
get(){
return obj1.x
},
set(value){
obj1.x = value
}
})
通过 obj2 就可以操作obj1 的属性
8.6 删除对象属性
var obj = {
username: 'kim',
age: 18,
sex: 'boy'
}
delete obj.age
console.log(obj); // {username: "kim", sex: "boy"}
8.7 模拟数据监测
模仿Vue的数据监测
let data = {
name: 'hehe',
address: '广西',
}
// 构造函数,创建监视的实例对象
function Observer(obj) {
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
//遍历
keys.forEach((k) => {
// this指向Observer实例对象
Object.defineProperty(this, k, {
get() {
return obj[k]
},
set(val) {
console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
obj[k] = val
}
})
})
}
//创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
console.log(obs)
//准备一个vm实例对象
let vm = {}
// 类似于vue加工(加上set/get),再赋值给_data
vm._data = data = obs
8.8 检查对象是否有该属性
if (todo.hasOwnProperty("isEdit")) {
todo.isEdit = true;
} else {
this.$set(todo, "isEdit", true);
}