面向对象编程

1、Js中的this

Js 中,this 是一个关键字,代表当前执行上下文中的对象。它的值取决于函数的调用方式,而不是定义时的位置。

1、默认绑定(对立函数调用)

当函数独立调用时 (非对象方法、非显示绑定),this 默认指向全局对象浏览器 中是window,Node 环境中是global )。严格模式下 ('use strict')this 为 undefined。

javascript 复制代码
function func() {
    console.log(this) 

    function func1() {
        console.log(this)
    }
    func1() // 浏览器环境输出 window 对象,Node环境输出 global 对象,严格模式下输出 undefined
}

func() // 浏览器环境输出 window 对象,Node环境输出 global 对象,严格模式下输出 undefined

2、隐式绑定(放法调用)

当函数作为对象的方法 调用时,this 指向调用该方法的对象

注意:若方法被赋值给变量后调用,会退回到默认绑定

javascript 复制代码
const obj = {
    name: "张三",
    fn: function() {
    console.log(this.name)
}
}

obj.fn() // 输出 张三  此时的this 指向 obj

const func = obj.fn;
func() // 浏览器环境指向 window 对象,Node环境指向 global 对象,严格模式下指向 undefined

3、显示绑定(call/apply/bind)

通过call、apply 或 bind 可以强制指定 this 的值。

apply :调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即A对象调用B对象的方法。

call :调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法。

bind :创建一个新的函数,在调用时,这个新函数的 this 值会被绑定到 bind() 方法的第一个参数上,其余参数被‌预设‌到原函数的参数列表开头。

java 复制代码
const func() {
    console.log(this)
}

const obj = {};

func.apply(obj) // func.apply(thisArg, [argsArray])
func.call(obj) // func.apply(thisArg, arg1, arg2)
const func1 = func.bind(obj) // func.bind 生成一个新的函数,将新函数的this指向obj

4、new 绑定(构造函数绑定)

通过 new 调用构造函数时,this 指向新创建的实例对象

javascript 复制代码
function Person(name) {
    this.name = name;
    console.log(this.name)
}

const zs = new Person("张三")
console.log(zs.name); // 输出 张三 this 指向 zs

5、箭头函数的 this

箭头函数 没有自己的 this,它会继承函数外层作用域的 this,并且无法通过显式绑定修改。

javascript 复制代码
const obj = {
    name: "张三",
    greet: () => {
        console.log(this.name)
    },
    greet1: function () {
        console.log(this.name)
    }
}

obj.greet() // 输出 undefined this 继承自外层,可能是全局 window/global
obj.greet1() // 输出 张三 this 指向 obj

6、事件处理函数中的 this

DOM 事件处理函数 中(如:click,focus),this 通常指向触发事件的 DOM 元素。若使用箭头函数 ,this 则会继承外层上下文。

javascript 复制代码
button.addEventListener('click', function() {
    console.log(this) // this 指向 button 元素
})

button.addEventListener('click', () => {
    console.log(this) // this 继承外层上下文
})

7、常见问题与解决方案

问题:回调函数丢失 this 绑定

解决方案:使用 bind箭头函数 保留上下文

javascript 复制代码
const obj = {
  name: '张三',
  delayedGreet: function() {
    setTimeout(function() {
        console.log(this.name);
    }.bind(this), 100);
// 或
    setTimeout(() => {
        console.log(this.name);
    }, 100)
  }
};

问题:嵌套函数 中的 this 不符合预期

解决方法:在 外层中保存 this 引用 或 使用箭头函数

javascript 复制代码
const obj = {
  name: 'Eve',
  outer: function() {
    const that = this;
    function inner() {
        console.log(that.name);
    }
// 活
    const inner = () => {
        console.log(that.name)
    }

    inner();
  }
};

2、面向对象编程(OOP)

Js 支持面向对象编程(OOP) ,主要通过构造函数原型链ES6 的类语法 实现。面向对象的核心概念包括封装、继承和多态。

1、构造函数与原型

使用构造函数创建对象实例,通过原型共享方法和属性:

javascript 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name}`);
};

const person1 = new Person('Alice', 25);
person1.greet(); // 输出: Hello, my name is Alice

2、ES6 类语法

ES6 引入了 class 关键字,简化了面向对象编程:

javascript 复制代码
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const person2 = new Person('Bob', 30);
person2.greet(); // 输出: Hello, my name is Bob

3、核心概念

封装:通过闭包或私有字段实现封装,保护内部数据

javascript 复制代码
class Counter {
  #count = 0; // 私有字段

  increment() {
    this.#count++;
  }

  getCount() {
    return this.#count;
  }
}

const counter = new Counter();
counter.increment();
console.log(counter.getCount()); // 输出: 1

继承:通过 extends 实现继承,子类可以继承父类的属性和方法

javascript 复制代码
class Student extends Person {
  constructor(name, age, grade) {
    super(name, age);
    this.grade = grade;
  }

  study() {
    console.log(`${this.name} is studying in grade ${this.grade}`);
  }
}

const student1 = new Student('Charlie', 18, 12);
student1.greet(); // 输出: Hello, my name is Charlie
student1.study(); // 输出: Charlie is studying in grade 12

多态:通过方法重写实现多态,子类可以覆盖父类的方法

javascript 复制代码
class Animal {
  speak() {
    console.log('Animal makes a sound');
  }
}

class Dog extends Animal {
  speak() { // 重写的方法名需与父类一致
    console.log('Dog barks');
  }
}

const animal = new Animal();
const dog = new Dog();

animal.speak(); // 输出: Animal makes a sound
dog.speak();    // 输出: Dog barks

4、静态方法与属性

使用 static 关键字定义 静态方法属性 ,它们属于类本身而非实例:

javascript 复制代码
class MathUtils {
  static PI = 3.14159;

  static add(a, b) {
    return a + b;
  }
}

console.log(MathUtils.PI);      // 输出: 3.14159
console.log(MathUtils.add(2, 3)); // 输出: 5

5、组合与聚合

通过对象组合实现更灵活的代码复用

javascript 复制代码
class Engine {
  start() {
    console.log('Engine started');
  }
}

class Car {
  constructor() {
    this.engine = new Engine();
  }

  start() {
    this.engine.start();
    console.log('Car started');
  }
}

const car = new Car();
car.start(); // 输出: Engine started \n Car started

3、原型和原型链

1、概念

Js 中,每个对象都有一个原型prototype),原型本身也是一个对象。原型链 是通过原型对象的链接 形成的机制,用于实现继承和属性查找。

2、原型的作用

对象的原型用于 共享属性和方法 。当访问对象的属性或方法时,如果对象本身没有该属性,引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末尾(null)。

3、原型链的构成

原型链多个对象的原型链接而成。例如:

  • 对象A的原型是对象B。
  • 对象B的原型是对象C。
  • 对象C的原型是null

当访问对象A 的某个属性时,引擎会依次在A、B、C 中查找,直到找到属性或返回undefined

4、原型的设置方式

  • 构造函数与原型
javascript 复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log(`Hello, ${this.name}`);
};

const person = new Person('Alice');
person.sayHello(); // 输出: Hello, Alice
  • 直接设置对象的原型
javascript 复制代码
const parent = { 
    greet: function() { 
        console.log('Hi!'); 
    } 
};
const child = Object.create(parent);
child.greet(); // 输出: Hi!

5、原型链的终点

所有原型链的终点是 Object.prototype,其原型为null。如果属性在整条链中未找到,则返回undefined

6、检查原型的方法

Object.getPrototypeOf()

javascript 复制代码
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
 

instanceof 运算符

javascript 复制代码
function Animal() {}
const dog = new Animal();
console.log(dog instanceof Animal); // true

7、修改原型的影响

直接修改原型会影响所有继承该原型的对象

javascript 复制代码
Array.prototype.customMethod = function() { return 'Custom'; };
const arr = [1, 2, 3];
console.log(arr.customMethod()); // 输出: Custom

8、原型链的注意事项

  • 避免在运行时频繁修改原型,可能导致性能问题。
  • 原型链过长会增加属性查找时间。
  • 使用Object.create(null)可以创建无原型的对象,避免原型链干扰。

4、基于函数的面向对象实现

Js 中,函数可以作为构造函数来创建对象实例,实现面向对象编程的核心概念(如封装、继承和多态)。以下是几种常见的实现方式:

1、构造函数模式

通过普通函数定义构造函数,使用 new 关键字实例化对象。构造函数内部的 this 指向新创建的对象实例。

javascript 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
  };
}

const person1 = new Person('Alice', 25);
person1.sayHello(); // 输出: Hello, I'm Alice

2、原型链继承

利用原型对象(prototype)实现方法共享,避免每次实例化都重复创建方法。通过修改构造函数的prototype属性实现继承。

javascript 复制代码
function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise.`);
};

function Dog(name) {
  Animal.call(this, name); // 调用父类构造函数
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.speak = function() {
  console.log(`${this.name} barks.`); // 方法重写
};

const dog = new Dog('Rex');
dog.speak(); // 输出: Rex barks.

3、工厂函数模式

不使用new关键字,而是通过普通函数返回新对象。适合需要灵活控制对象创建的场景。

javascript 复制代码
function createPerson(name) {
  const obj = {};
  obj.name = name;
  obj.greet = function() {
    console.log(`Hi, I'm ${obj.name}`);
  };
  return obj;
}

const person2 = createPerson('Bob');
person2.greet(); // 输出: Hi, I'm Bob

4、闭包实现私有成员

通过IIFE(立即执行函数表达式)闭包模拟私有变量,实现数据封装。

javascript 复制代码
const Counter = (function() {
  let privateCount = 0; // 私有变量

  function changeBy(val) {
    privateCount += val;
  }

  return {
    increment: function() {
      changeBy(1);
    },
    value: function() {
      return privateCount;
    }
  };
})();

Counter.increment();
console.log(Counter.value()); // 输出: 1

5、class关键字

ES6 引入的class本质仍是基于原型的语法糖,但提供了更清晰的面向对象写法。

javascript 复制代码
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }

  get area() {
    return this.calcArea();
  }

  calcArea() {
    return this.height * this.width;
  }
}

const square = new Rectangle(10, 10);
console.log(square.area); // 输出: 100

6、注意事项

  • 使用构造函数时务必通过new调用,否则this会指向全局对象(严格模式下为undefined
  • 原型方法共享可节省内存,但需注意引用类型属性的共享问题
  • 现代项目推荐使用class语法,但需理解其底层仍是基于原型的实现

5、es6中的面向对象

ES6 引入了 class 语法,简化了 JavaScript 的面向对象编程(OOP)实现方式。以下是 ES6 中面向对象的核心特性:

1、类(Class)定义

使用 class 关键字定义类,构造函数通过 constructor 方法声明:

javascript 复制代码
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

2、方法定义

类方法直接写在类内部,无需 function 关键字:

javascript 复制代码
class Person {
  greet() {
    return `Hello, ${this.name}!`;
  }
}

3、继承(Inheritance)

通过 extends 实现继承,super 调用父类构造函数或方法:

javascript 复制代码
class Student extends Person {
  constructor(name, age, grade) {
    super(name, age); // 调用父类构造函数
    this.grade = grade;
  }
}
 

4、静态方法(Static Methods)

static 定义类方法,直接通过类名调用:

javascript 复制代码
class MathUtils {
  static square(x) {
    return x * x;
  }
}
MathUtils.square(3); // 9

5、Getter/Setter

通过 getset 定义属性访问器:

javascript 复制代码
class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }
  get area() {
    return this._width * this._height;
  }
}

6、私有字段(ES2022+)

使用 # 前缀定义私有字段(需现代浏览器或 Node.js 支持):

javascript 复制代码
class Counter {
  #count = 0; // 私有字段
  increment() {
    this.#count++;
  }
}

7、原型链与 ES6 类的本质

ES6class 本质仍是基于原型的语法糖。以下两种写法等价:

javascript 复制代码
// ES6 类
class Foo {
  bar() {}
}

// ES5 等效实现
function Foo() {}
Foo.prototype.bar = function() {};

8、注意事项

  • 类声明不会提升(必须先定义后使用)。
  • 类方法不可枚举(不同于 ES5 构造函数原型方法)。
  • 构造函数必须通过 new 调用,否则报错。
相关推荐
艾莉丝努力练剑1 小时前
静态地址重定位与动态地址重定位:Linux操作系统的视角
java·linux·运维·服务器·c语言·开发语言·c++
进击的尘埃1 小时前
GPU 合成层炸了,页面白屏——从 will-change 滥用聊到层爆炸的治理
javascript
跟着珅聪学java2 小时前
Electron + Vue 现代化“新品展示“和“快捷下单“菜单
开发语言·前端·javascript
泡沫_cqy2 小时前
Java初学者文档
java·开发语言
前进的李工2 小时前
数据库视图:数据安全与权限管理利器
开发语言·数据库·mysql·navicat
C_心欲无痕2 小时前
使用 XLSX.js 导出 Excel 文件
开发语言·javascript·excel
天才熊猫君2 小时前
Vue 3 中 Watch 的陷阱:为什么异步操作后创建的监听会泄漏?
前端·javascript
用户5757303346242 小时前
深入 JavaScript 内存机制:从栈与堆到闭包的底层原理
javascript