1 面向对象

  • 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。

面向对象编程 OOP (Object Oriented Programming)

面向对象的特性:

  • 封装性
  • 继承性
  • 多态性

    2 类和对象

    ES6 中新增加了类的概念,可以使用 class 关键字声明一个类 ```javascript class name { // class body }

var xx = new name();

  1. 注意: 类必须使用 new 实例化对象
  2. <a name="kFHbY"></a>
  3. ## 2.1 类的get和set
  4. ```javascript
  5. // get 和 set
  6. class Phone{
  7. get price(){
  8. console.log("价格属性被读取了");
  9. return 'iloveyou';
  10. }
  11. set price(newVal){
  12. console.log('价格属性被修改了');
  13. }
  14. }
  15. //实例化对象
  16. let s = new Phone();
  17. console.log(s.price); // iloveyou
  18. s.price = 'free';

image.png

2.2 类中的函数存放类原型对象上

  1. class Person {
  2. constructor(name, age) {
  3. this.name = name
  4. this.age = age
  5. }
  6. say(){
  7. console.log('hello world');
  8. }
  9. }
  10. const a = new Person('heh', 18)
  11. console.log(a);
  12. console.log(Person.prototype);

image.png

3 类 constructor 构造函数

  • constructor() 方法是类的构造函数(默认方法),用于传递参数, 返回实例对象,
  • 通过 new 命令生成对象实例时 ,自动调用该方法。
  • 如果没有显示定义, 类内部会自动给我们创建一个constructor()
  1. // 1. 创建类 class 创建一个 明星类
  2. class Star {
  3. // 类的共有属性放到 constructor 里面
  4. constructor(name, age) {
  5. this.name = name;
  6. this.age = age;
  7. }
  8. sing(song) {
  9. console.log(this.uname + '唱' + song);
  10. }
  11. }
  12. // 2. 利用类创建对象 new
  13. var ldh = new Star('刘德华', 18);
  14. console.log(ldh);

一个类只能有一个构造函数
即使有有参构造函数,依然可以无参创建对象, 即

  1. var ldh = new Star();

4 类的继承

  1. // 父类
  2. class Father{ }
  3. // 子类继承父类
  4. class Son extends Father { }

4.1 super 关键字

super 关键字用于访问和调用对象父类上的函数。
可以调用父类的构造函数,也可以调用父类的普通函数

  1. //定义了父类
  2. class Father {
  3. constructor(x, y) {
  4. this.x = x;
  5. this.y = y;
  6. }
  7. sum() {
  8. console.log(this.x + this.y);
  9. }
  10. }
  11. //子元素继承父类
  12. class Son extends Father {
  13. constructor(x, y) {
  14. super(x, y); //使用super调用了父类中的构造函数
  15. }
  16. }
  17. var son = new Son(1, 2);
  18. son.sum(); //结果为3
  1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
  2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
  3. 如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,super 必须在子类this之前调用

    1. class Son extends Father {
    2. constructor(x, y) {
    3. // 利用super 调用父类的构造函数 super 必须在子类this之前调用,放到this之后会报错
    4. super(x, y);
    5. this.x = x;
    6. this.y = y;
    7. }
    8. subtract() {
    9. console.log(this.x - this.y);
    10. }
    11. }

    4.2 ES6类和对象注意点

  4. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象.

  5. 类里面的共有属性和方法一定要加this使用.
  6. 类里面的this指向问题.
  7. constructor 里面的this指向实例对象, 方法里面的this 指向这个方法的调用者

时刻注意this的指向问题,类里面的共有的属性和方法一定要加this使用.

  1. constructor中的this指向的是new出来的实例对象
  2. 自定义的方法,一般也指向的new出来的实例对象
  3. 绑定事件之后this指向的就是触发事件的事件源

5 构造函数

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。
在 ES6之前, JS 中并没用引入类的概念。而是用一种称为构建函数的特殊函数来定义对象和它们的特征。

5.1 对象创建

  1. // 字面量
  2. var obj = {};
  3. // new关键字
  4. var obj = new Object();
  5. // 构造函数
  6. function Person(name,age){
  7. this.name = name;
  8. this.age = age;
  9. }
  10. var obj = new Person('zs',12);

new 在执行时会做四件事情:
① 在内存中创建一个新的空对象。
② 让 this 指向这个新的对象。
③ 执行构造函数里面的代码,给这个新对象添加属性和方法。
④ 返回这个新对象(所以构造函数里面不需要 return )。

5.2 静态成员和实例成员

  • 静态成员:在构造函数上添加的成员称为静态成员,只能由构造函数本身来访问 
  • 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问(实例成员就是构造函数内部通过this添加的成员)
    1. function Star(uname, age) {
    2. this.uname = uname;
    3. this.age = age;
    4. this.sing = function() {
    5. console.log('我会唱歌');
    6. }
    7. }
    8. Star.sex = '男';
    9. var ldh = new Star('刘德华', 18);
    10. console.log(Star.sex);//静态成员只能通过构造函数来访问
    11. console.log(ldh.uname);//实例成员只能通过实例化的对象来访问

    5.3 构造函数问题

    存在浪费内存的问题。
    image.png
    希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎样做呢?
    使用构造函数原型prototype

5.4 构造函数原型prototype

拓展:在vuejs挂载全局函数使用到

  • 构造函数通过原型分配的函数是所有对象所共享的。
  • JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。
  • 注意这个 prototype 就是一个对象(prototype也称为原型对象),这个对象的所有属性和方法,都会被构造函数所拥有。
  • 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

    1. function Star(uname, age) {
    2. this.uname = uname;
    3. this.age = age;
    4. }
    5. Star.prototype.sing = function() {
    6. console.log('我会唱歌');
    7. }
    8. var ldh = new Star('刘德华', 18);
    9. var zxy = new Star('张学友', 19);
    10. ldh.sing();//我会唱歌
    11. zxy.sing();//我会唱歌

    5.5 对象原型(隐式原型)proto

    proto对应的是对象(即实例才有)
    prototype对应的是构造函数(类)

  • 对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,

  • 之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。
  • proto对象原型和原型对象 prototype 是等价的
  • proto对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性, 因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

image.png

5.6 constructor构造函数

  • 对象原型( proto)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性
  • constructor 我们称 为构造函数,因为它指回构造函数本身
  • constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数

一般情况下,对象的方法都在构造函数的原型对象中设置。
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

  1. function Star(uname, age) {
  2. this.uname = uname;
  3. this.age = age;
  4. }
  5. // 很多情况下,我们需要手动的利用constructor 这个属性指回原来的构造函数
  6. Star.prototype = {
  7. // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
  8. constructor: Star, // 手动设置指回原来的构造函数
  9. sing: function() {
  10. console.log('我会唱歌');
  11. },
  12. movie: function() {
  13. console.log('我会演电影');
  14. }
  15. }
  16. var zxy = new Star('张学友', 19);
  17. console.log(zxy)

image.png

5.7 原型链

每一个实例对象又有一个**proto属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__**属性,这样一层一层往上找就形成了原型链。
image.png

5.8 构造函数实例和原型对象三角关系

1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的proto属性指向了构造函数的原型对象prototype
3.构造函数的原型对象prototype的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数
image.png

5.9 原型对象this指向

原型对象prototype里面放的是方法, 这个方法里面的this 指向的是这个方法的调用者, 也就是这个实例对象

5.10 通过原型为数组扩展内置方法

  1. Array.prototype.sum = function() {
  2. var sum = 0;
  3. for (var i = 0; i < this.length; i++) {
  4. sum += this[i];
  5. }
  6. return sum;
  7. };
  8. //此时数组对象中已经存在sum()方法了 可以始终 数组.sum()进行数据的求

注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式

6 JavaScript 的成员查找机制(规则)

① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 proto指向的 prototype 原型对象)。
③ 如果还没有就查找原型对象的原型(Object的原型对象)。
④ 依此类推一直找到 Object 为止(null)。
proto对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

7 ES6之前的继承

ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承

7.1 call() 改变this指向

  1. fun.call(thisArg, arg1, arg2, ...)

thisArg :当前调用函数 this 的指向对象
arg1,arg2:传递的其他参数

  1. function fn(x, y) {
  2. console.log(this);
  3. console.log(x + y);
  4. }
  5. var o = {
  6. name: 'andy'
  7. };
  8. fn.call(o, 1, 2);//调用了函数此时的this指向了对象o,

image.png

7.2 借用构造函数继承父类型属性

核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性

  1. // 父类
  2. function Person(name, age, sex) {
  3. this.name = name;
  4. this.age = age;
  5. this.sex = sex;
  6. }
  7. // 子类
  8. function Student(name, age, sex, score) {
  9. Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数
  10. this.score = score;
  11. }
  12. var s1 = new Student('zs', 18, '男', 100);
  13. console.dir(s1);

7.3 借用原型对象继承父类型方法

一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。

  1. // 1. 父构造函数
  2. function Father(uname, age) {
  3. // this 指向父构造函数的对象实例
  4. this.uname = uname;
  5. this.age = age;
  6. }
  7. Father.prototype.money = function() {
  8. console.log(100000);
  9. };
  10. // 2 .子构造函数
  11. function Son(uname, age, score) {
  12. // this 指向子构造函数的对象实例
  13. Father.call(this, uname, age);
  14. this.score = score;
  15. }
  16. // Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
  17. Son.prototype = new Father();
  18. // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
  19. Son.prototype.constructor = Son;
  20. // 这个是子构造函数专门的方法
  21. Son.prototype.exam = function() {
  22. console.log('孩子要考试');
  23. }
  24. var son = new Son('刘德华', 18, 100);
  25. console.log(son);

image.png

8 对象方法

8.1 Object.keys()

Object.keys(对象) 获取到当前对象中的属性名 ,返回值是一个数组

  1. var obj = {
  2. id: 1,
  3. pname: '小米',
  4. price: 1999,
  5. num: 2000
  6. };
  7. var result = Object.keys(obj)
  8. console.log(result) //[id,pname,price,num]

效果类似 for…in

8.2 定义新属性或修改原有的属性Object.defineProperty

Object.defineProperty设置或修改对象中的属性

  1. Object.defineProperty(对象,修改或新增的属性名,{
  2. value:修改或新增的属性的值,
  3. writable: false, //如果值为false 不允许修改这个属性值
  4. enumerable: false,//enumerable 如果值为false 则不允许遍历
  5. configurable: false //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性
  6. })

默认值都为false

8.3 Object.defineProperty中的get方法

  1. let number = 18
  2. let person = {
  3. name: '张三',
  4. sex: '男'
  5. }
  6. Object.defineProperty(person, 'age', {
  7. get(){
  8. return number
  9. }
  10. })
  11. console.log(person);

image.png
当读取到Person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get(){ } 是固定写法,若是写其他将是undefined

8.4 Object.defineProperty中的set方法

  1. let number = 18
  2. let person = {
  3. name: '张三',
  4. sex: '男'
  5. }
  6. Object.defineProperty(person, 'age', {
  7. get(){
  8. return number
  9. },
  10. set(value){
  11. console.log('调用了set,值是', value);
  12. number = value
  13. }
  14. })
  15. console.log(person);

image.png
当有人修改person的age属性时,set函数(setter)就会被调用,且会修改具体值

通过get和set方法对象就和其他变量产生了关联
Vue2里面的数据劫持,计算属性,数据代理都用到这个Object.defineProperty方法。

8.5 Object.defineProperty数据代理

通过一个对象代理另一个对象中的属性操作(读/写)

  1. let obj1 = {x: 100}
  2. let obj2 = {y: 200}
  3. Object.defineProperty(obj2, 'x', {
  4. get(){
  5. return obj1.x
  6. },
  7. set(value){
  8. obj1.x = value
  9. }
  10. })

通过 obj2 就可以操作obj1 的属性
image.png

8.6 删除对象属性

  1. var obj = {
  2. username: 'kim',
  3. age: 18,
  4. sex: 'boy'
  5. }
  6. delete obj.age
  7. console.log(obj); // {username: "kim", sex: "boy"}

8.7 模拟数据监测

模仿Vue的数据监测

  1. let data = {
  2. name: 'hehe',
  3. address: '广西',
  4. }
  5. // 构造函数,创建监视的实例对象
  6. function Observer(obj) {
  7. //汇总对象中所有的属性形成一个数组
  8. const keys = Object.keys(obj)
  9. //遍历
  10. keys.forEach((k) => {
  11. // this指向Observer实例对象
  12. Object.defineProperty(this, k, {
  13. get() {
  14. return obj[k]
  15. },
  16. set(val) {
  17. console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
  18. obj[k] = val
  19. }
  20. })
  21. })
  22. }
  23. //创建一个监视的实例对象,用于监视data中属性的变化
  24. const obs = new Observer(data)
  25. console.log(obs)
  26. //准备一个vm实例对象
  27. let vm = {}
  28. // 类似于vue加工(加上set/get),再赋值给_data
  29. vm._data = data = obs

image.png

8.8 检查对象是否有该属性

  1. if (todo.hasOwnProperty("isEdit")) {
  2. todo.isEdit = true;
  3. } else {
  4. this.$set(todo, "isEdit", true);
  5. }