1 面向对象
- 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
面向对象编程 OOP (Object Oriented Programming)
面向对象的特性:
var xx = new name();
注意: 类必须使用 new 实例化对象<a name="kFHbY"></a>## 2.1 类的get和set```javascript// get 和 setclass Phone{get price(){console.log("价格属性被读取了");return 'iloveyou';}set price(newVal){console.log('价格属性被修改了');}}//实例化对象let s = new Phone();console.log(s.price); // iloveyous.price = 'free';
2.2 类中的函数存放类原型对象上
class Person {constructor(name, age) {this.name = namethis.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. 利用类创建对象 newvar 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 = 18let 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 = 18let 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.ageconsole.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),再赋值给_datavm._data = data = obs

8.8 检查对象是否有该属性
if (todo.hasOwnProperty("isEdit")) {todo.isEdit = true;} else {this.$set(todo, "isEdit", true);}
