面向对象
-
含义:面向对象不是语法,是一个思想,一种编程模式
-
面向过程:脸朝着过程 ---> 关注着过程的编程模式
-
面向对象:脸朝着对象 ---> 关注着对象的编程模式
-
实现一个效果
- 在面向过程的时候, 我们要关注每一个元素, 每一个元素之间的关系, 顺序...
- 在面向过程的时候, 我们要关注的就是找到一个对象来帮我做这个事情, 我等待结果
对象的创建方式
- 字面量创建对象
var obj = {}
obj.name = 'QF'
- 内置构造函数创建
var obj = new Object()
obj.name = 'QF'
- 使用工厂函数创建
// 1. 创建一个工厂函数
function createObj() {
// 1.1 手动创建对象
var obj = new Object()
// 1.2 手动向对象中添加成员
obj.name = 'QF'
obj.age = 18
// 1.3 手动返回一个对象
return obj
}
// 2. 使用工厂函数创建对象
var o1 = createObj()
var o2 = createObj()
- 使用自定义构造函数
- 自定义构造函数的函数名称首字母建议大写,区分普通函数
- 自定义构造函数调用的时候必须和关键字
new一起用 - 自定义构造函数的小括号内也可以传参数
- 自定义构造函数的
this指向的不是window而是自动创建出的对象 - 自定义构造函数不用写
return- 返回基本数据类型没有效果
- 返回引用数据类型就失去了构造函数的作用
// 1. 先书写一个构造函数
function Person(name, gender) {
// 1.2 在构造函数内向对象添加一些成员
this.age = 18
this.name = name
this.gender = gender
}
// 2. 使用这个构造函数创建一个对象 (和 new 连用)
var p1 = new Person('jack', 'man')
var p2 = new Person('rose', 'woman')
console.log(p1.age) // 18
console.log(p2.age) // rose
缺点
- 自定义构造函数创建的对象内有函数的时候,每次调用都会创建一个内容完全一样新的函数,生成一个新的地址
- 可以将函数创建在全局
- 或者借助prototype
原型
- 每个函数都有一个对象空间,构造函数也是函数
- 将构造函数内声明的对象提取到一个公共的地方
prototype
- 每个函数自带一个成员叫做
prototype,是一个对象空间 - 这个对象空间可以通过
函数名.prototype来访问
function Person () {}
console.log(Person.prototype) // 是一个对象
Person.prototype是一个对象,那么也可以向对象中添加内容
function Person() {}
Person.prototype.name = 'prototype'
Person.prototype.sayHi = function () {}
重点:对象空间里存储的内容不是给函数用的,而是给构造函数的每一个实例化对象使用的
proto
- 每一个对象都天生自带一个成员叫做
__proto__ - 实例化对象也是对象,所以也有
__proto__ - 这个
__proto__对象空间是给每一个对象使用的 - 每个对象的__proto__ 都指向当前对象的构造函数的prototype,二者共有一个对象空间
function Person() {}
var p1 = new Person()
console.log(p1.__proto__ === Person.prototype) // true
- 将功能函数书写在构造函数的prototype对象空间内,调用的时候通过实例化对象来调用
- 总结
- 书写构造函数时
- 属性直接写在函数体内部
- 方法书写在构造函数的原型上
原型链
构造函数的prototype是一个对象,也有__proto__属性,那么这个对象的__proto__指向何处
- 每一个对象都有所属的构造函数
- 数组的构造函数是 Array()
- 函数的构造函数是 Function()
constructor
- 对象的__proto__里面有一个constructor属性,这个属性指向当前对象的构造函数
链状结构
- 万物皆对象
- js中所有引用数据类型都可以当作对象来使用
- 对象内部属性的查找规则
- 当我们在一个对象内部查找一个属性的时候, 会现在当前对象的自身查找
- 如果找到直接使用并停止查找, 如果没有找到, 那么会去到当前对象的 proto 中继续查找
- .......
- 一直到最顶层的对象 Object.prototype, 还是没有找到, 那么返回一个 undefined
面试题
/**
* 1. p.__proto__ === ???
* p 其实就是一个 实例化对象, 构造函数是 Person
* p.__proto__ === Person.prototype
*
* 2. Person.__proto__ === ???
* Person 是构造函数, 本质上就是一个函数
* 在 JS 中, 只要是一个函数, 那么就是 Function 这个构造函数的 实例化对象
* Person.__proto__ === Function.prototype
*
* 3. Person.prototype.__proto__ === ???
* Person 是一个构造函数, 本质上就是一个函数
* Person.prototype 其实就是一个函数的原型对象 本质上就是一个对象
* 所以 Person.prototype.__proto__ 其实就是指向了一个对象的构造函数的原型对象
* 因为JS 中 对象的构造函数就是 Object
* 所以我们可以得到一个等式 Person.prototype.__proto__ === Object.prototype
*
* 4. Function.__proto__ === ???
* Function 是 JS 中内置的一个构造函数, 本质上就是一个函数
*
* 所以我们现在其实就是在找一个函数的构造函数的原型对象, 因为函数的原型对象是 Function
* Function.__proto__ === Function.prototype
*
* 5. Function.prototype.__proto__ === ???
* Function 内置构造函数, 其实就是一个函数
* Function.prototype 指向了当前构造函数的原型对象, 本质上就是一个对象
*
* 所以我们现在本质上就是在找一个对象的 构造函数的原型对象是谁
*
* Function.prototype.__proto__ === Object.prototype
*
* 6. Object.__proto__ === ???
* Object 是 JS 中 一个内置构造函数, 其实就是一个函数
* 现在就相当于在找一个函数的 构造函数的原型对象
*
* Object.__proto__ === Function.prototype
*
* 7. Object.prototype.__proto__ === ???
* 在 JS 中最顶层的对象就是 Object.prototype, 所以如果再向上找的话, 就找不到任何东西了
* 然后 在 JS 中, 给我们返回了一个数据 null
*
* Object.prototype.__proto__ === null
*/
判断数据类型
- typeOf()
- 缺点:引用数据类型判断不准确
- constructor
- 数据.constructor === 构造函数;通过对比他等于那个构造函数, 然后确认他是那个数据类型
- 缺点:undefined 和 null 不能用
- constructor 是一个对象的内部属性,可以被修改
- instanceof
- 数据 instanceof 构造函数
- 缺点: undefined 和 null 不能使用
- 引用数据类型有可能会被识别为对象类型
- Object.prototype.toString.call()
- Object.prototype.toString.call(需要判断数据类型的数据)
- 能判断js 中所有的数据类型
继承
定义:一个构造函数的实例使用另一个构造函数的属性和方法
- 原型继承:用父类的实例化对象替换掉子类构造函数的原型对象
function Person () {
this.name = 'zhangsan'
}
Person.prototype.abc = function () {
console.log('我是Person');
}
function Stu () {
this.age = 20
}
Stu.prototype.asd = function () {
console.log('我是Stu');
}
// console.log(new Stu());
// 覆盖掉原来Stu.prototype里面的内容,如果想再Stu.prototype加内容需要在继承之后
Stu.prototype = new Person()
- 借用对象:在子类函数内调用父类函数并改变this指向(只能拿到对象本身的属性,原型空间内的不行)
function Person () {
this.name = 'zhangsan'
this.sex = '男'
}
Person.prototype.abc = function () {
console.log('我是Person');
}
function Stu () {
this.age = 20
// 再Stu里面调用Person函数
// 把Person内的this改成Stu的实例化对象
Person.call(this)
}
const stu = new Stu()
console.log(stu);
console.log(new Person());
- 组合继承:原型继承和借用继承整合;但原型对象里面有一组多余的数据
function Person () {
this.name = 'zhangsan'
this.sex = '女'
}
Person.prototype.abc = function () {
console.log('我是Person');
}
function Stu () {
this.age = 20
// 调用Person
Person.call(this)
}
// 原型继承
Stu.prototype = new Person()
// 借用继承
const s1 = new Stu()
// 原型继承和借用继承的优点都有
console.log(s1);
- 拷贝继承:通过遍历父类实例化对象内的所有属性,添加到子类的构造函数原型对象中
// 父类
function Person(name, sex) {
this.name = name
this.sex = sex
}
Person.prototype.abc = function () {
console.log('我是Person');
}
function Stu(age, ...args) {
this.age = age
// 实例化父类的构造函数
const p = new Person(...args)
// 通过遍历对象把父类的实例化对象添加到子类的protype
// for...in 循环遍历会把对象的protype里面的键值对也遍历进去
for (let key in p) {
Stu.prototype[key] = p[key]
}
}
const s1 = new Stu(18,'张三','男')
console.log(s1);
- ES6继承:固定写法
// ES6 固定写法
class Person {
constructor(name, sex) {
this.name = name
this.sex = sex
}
abc() {
console.log('我是Person');
}
}
class Stu extends Person {
constructor(age, ...args) {
// super(父类的参数) ,必须写在constructor 的第一行
super(...args)
this.age = age
}
}
const s1 = new Stu(18, '张三', '男')
console.log(s1);
深浅拷贝
浅拷贝
- for...in遍历对象
const newObj = {}
for (let key in obj) {
newObj[key] = obj[key]
}
- 构造函数assign方法
// obj是想要拷贝的对象
const newObj = Object.assign({},obj)
深拷贝
- 面试版
const newObj = {}
function fn(target, origin) {
for (let key in origin) {
const type = Object.prototype.toString.call(origin[key])
if (type === '[object Object]') {
target[key] = {}
fn(target[key], origin[key])
} else if (type === '[object Array]') {
target[key] = []
fn(target[key], origin[key])
} else {
target[key] = origin[key]
}
}
}
fn(newObj, obj)
- 工作版
// 通过JSON转字符串在转回对象
const newObj = JSON.parse(JSON.stringify(obj))