学习了coderwhy的JavaScript高级语法视频课的笔记
如有错误或者不合适的地方,敬请见谅,欢迎指出和拓展,谢谢各位了
一、面向对象是现实的抽象方式
- 对象是JavaScript中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物:
- 比如我们可以描述一辆车:Car,具有颜色(color)、速度(speed)、品牌(brand)、价格(price),行驶(travel)等 等;
- 比如我们可以描述一个人:Person,具有姓名(name)、年龄(age)、身高(height),吃东西(eat)、跑步(run) 等等。
- 用对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构:
- 所以有一些编程语言就是纯面向对象的编程语言,比Java;
- 你在实现任何现实抽象时都需要先创建一个类,根据类再去创建对象。
二、JavaScript的面向对象
- JavaScript其实支持多种编程范式的,包括函数式编程和面向对象编程:
- JavaScript中的对象被设计成一组属性的无序集合,像是一个哈希表,有key和value组成;
- key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型;
- 如果值是一个函数,那么我们可以称之为是对象的方法。
- 如何创建一个对象呢?
- 早期使用创建对象的方式最多的是使用Object类,并且使用new关键字来创建一个对象;
var obj = new Object()
obj.name = "why"
obj.age = 18
obj.height = 1.88
obj.running = function() {
console.log(this.name + "在跑步~")
}
- 后来很多开发者为了方便起见,都是直接通过字面量的形式来创建对象。
var info = {
name: "kobe",
age: 40,
height: 1.98,
eating: function() {
console.log(this.name + "在吃东西~")
}
}
- 对属性操作的控制
1、
//获取指定属性值
obj.name
2、
// 删除属性
delete obj.name
3、
// 遍历属性
for (var key in obj) {
console.log(key)
}
- Object.defineProperty(obj,prop,descriptor)方法
- obj要定义属性的对象;
- prop要定义或修改的属性的名称或 Symbol;
- descriptor要定义或修改的属性描述符。
// 属性描述符是一个对象
Object.defineProperty(obj, "属性名", {
// 很多的配置
value: 值
})
- 属性描述符分类(两种)
- 数据属性(Data Properties)描述符(Descriptor);
- 存取属性(Accessor访问器 Properties)描述符(Descriptor);
a. 数据属性描述符:
注意下面两种方式中,数据描述符默认值的差别:
1、
// name和age虽然没有使用属性描述符来定义, 但是它们也是具备对应的特性的
// configurable: true
// enumerable: true
// writable: true
var obj = {
name: "why",
age: 18
}
2、
// 数据属性描述符
// 用了属性描述符, 那么会有默认的特性
Object.defineProperty(obj, "address", {
value: "北京市", // 默认值undefined
// 该特殊不可删除/也不可以重新定义属性描述符
configurable: false, // 默认值false
// 该特殊是配置对应的属性(address)不可以枚举
enumerable: false, // 默认值false
// 该特性是属性不可以赋值(写入值)
writable: false // 默认值false
})
b. 存取属性描述符:
var obj = {
name: "why",
age: 18,
_address: "北京市"// '_'代表私有属性,js里面是没有严格意义的私有属性
}
// 存取属性描述符
// 1.隐藏某一个私有属性被希望直接被外界使用和赋值
// 2.如果我们希望截获某一个属性它访问和设置值的过程时, 也会使用存储属性描述符
//不能和value和writable一起用
Object.defineProperty(obj, "address", {
enumerable: true,
configurable: true,
get: function() {
foo()
return this._address
},
set: function(value) {
bar()
this._address = value
}
})
console.log(obj.address)
- 上面的Object.defineProperty是定义一个,Object.defineProperties() 方法直接在一个对象上定义多个新的属性或修改现有属性,并且返回该对象。
var obj = {
// 私有属性(js里面是没有严格意义的私有属性)
_age: 18,
//也可以在这样写,但是没有直接定义数据属性描述符的操作
set age(value) {
this._age = value
},
get age() {
return this._age
}
}
Object.defineProperties(obj, {
name: {
configurable: true,
enumerable: true,
writable: true,
value: "why"
},
age: {
configurable: true,
enumerable: true,
get: function() {
return this._age
},
set: function(value) {
this._age = value
}
}
})
- 对象方法补充
- 获取对象的属性描述符:
- getOwnPropertyDescriptor
- getOwnPropertyDescriptors
// 1、获取某一个特性属性的属性描述符
Object.getOwnPropertyDescriptor(obj, "name")
// 2、获取对象的所有属性描述符
Object.getOwnPropertyDescriptors(obj)
- 禁止对象扩展新属性:preventExtensions
- 给一个对象添加新的属性会失败(在严格模式下会报错)
Object.preventExtensions(obj)
- 密封对象,不允许配置和删除属性:seal
- 实际是调用preventExtensions
- 并且将现有属性的configurable:false
Object.seal(obj)
- 冻结对象,不允许修改现有属性: freeze
- 实际上是调用seal
- 并且将现有属性的writable: false
Object.freeze(obj)
三、简单叙述对象的原型
- JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。
- 那么这个对象有什么用呢?
- 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;
- 这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;
- 如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性;
- 那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?
- 答案是有的,只要是对象都会有这样的一个内置属性;
- 获取的方式有两种:
- 方式一:通过对象的 __ proto __ 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问 题);
- 方式二:通过 Object.getPrototypeOf 方法可以获取到;
1、
var obj = { name: "why" } // [[prototype]]
console.log(obj.__proto__) // [Object: null prototype] {}
2、
var obj = { name: 'why' }
// ES5之后提供的Object.getPrototypeOf
console.log(Object.getPrototypeOf(obj))// [Object: null prototype] {}
四、简单叙述函数的原型和原型链
function foo() {
}
// 1、函数也是一个对象
console.log(foo.__proto__) // 函数作为对象来说, 它也是有[[prototype]] 隐式原型
// 2、函数它因为是一个函数, 所以它还会多出来一个显示原型属性: prototype
console.log(foo.prototype)
var f1 = new foo()
var f2 = new foo()
console.log(f1.__proto__ === foo.prototype)//true
console.log(f2.__proto__ === foo.prototype)//true
- 创建对象的内存表现
function Person(){}
var p1=new Person()
var p2=new Person()
- 重写原型对象
function foo() {}
// 直接修改整个prototype对象
foo.prototype = {
...
}
// 真实开发中我们可以通过Object.defineProperty方式添加constructor
Object.defineProperty(foo.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: foo
})
- 原型链关系的内存图 在上图中,Person函数的原型对象并不是顶层原型,但是最终指向Object.prototype([ Object: null prototype ] {}),也就是顶层原型对象,在这链接查找的过程中,就形成的原型链。原型链最顶层的原型对象就是Object的原型对象。
五、JavaScript创建多个对象的方案
- 字面量:一个个的创建。
- 工厂模式
function createPerson(name, age, height, address) {
var p = {}
p.name = name
p.age = age
p.height = height;
p.address = address
p.eating = function() {
console.log(this.name + "在吃东西~")
}
p.running = function() {
console.log(this.name + "在跑步~")
}
return p
}
var p1 = createPerson("张三", 18, 1.88, "广州市")
var p2 = createPerson("李四", 20, 1.98, "上海市")
// 工厂模式的缺点(获取不到对象最真实的类型)
console.log(p1, p2)
//返回值
{
name: '张三',
age: 18,
height: 1.88,
address: '广州市',
eating: [Function (anonymous)],
running: [Function (anonymous)]
} {
name: '李四',
age: 20,
height: 1.98,
address: '上海市',
eating: [Function (anonymous)],
running: [Function (anonymous)]
}
3、原型和构造函数
function Person(name, age, height, address) {
this.name = name
this.age = age
this.height = height
this.address = address
}
Person.prototype.eating = function() {
console.log(this.name + "在吃东西~")
}
Person.prototype.running = function() {
console.log(this.name + "在跑步~")
}
var p1 = new Person("why", 18, 1.88, "北京市")
var p2 = new Person("kobe", 20, 1.98, "洛杉矶市")
// 返回结果
Person { name: 'why', age: 18, height: 1.88, address: '北京市' }
Person { name: 'kobe', age: 20, height: 1.98, address: '洛杉矶市' }
六、面向对象的三大特性
- 面向对象有三大特性:封装、继承、多态
- 封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程;
- 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);
- 多态:不同的对象在执行时表现出不同的形态; 继承:
1-1). 原型链实现继承
function Father() {
this.P_name = 'P_xxx'
}
Father.prototype.gz = function () {
console.log('工作赚钱')
}
function Son() {
this.S_name = 'S_xxx'
}
Son.prototype = new Father()
Son.prototype.by = function () {
console.log('毕业了')
}
var S = new Son()
S.by()// 毕业了
S.gz()// 工作赚钱
console.log(S.P_name)// P_xxx
console.log(S.S_name)// S_xxx
注意:下列图中P指的是P=new Father()
继承前:
继承后:
1-2). 原型链继承的常见弊端
- 第一,我们通过直接打印S对象是看不到这个继承的属性;
console.log(S)//Father { name: 'S_xxx' }
- 第二,这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题; 如果是S.arr='father'添加到的是S实例对象
function Father() {
this.P_name = 'P_xxx'
this.arr = []
}
Father.prototype.gz = function () {
console.log('工作赚钱')
}
function Son() {
this.S_name = 'S_xxx'
}
Son.prototype = new Father()
Son.prototype.by = function () {
console.log('毕业了')
}
var S = new Son()
var S2 = new Son()
S.arr.push('father')
console.log(S.arr)// [ 'father' ]
console.log(S2.arr)// [ 'father' ]
- 第三,不能给Person传递参数,因为这个对象是一次性创建的(没办法定制化); 2-1). 组合式继承,其中就有借用构造函数继承和原型链继承
解决了原型链继承的常见弊端
function Father(P_name) {
this.P_name = P_name
}
Father.prototype.gz = function () {
console.log('工作赚钱')
}
function Son(P_name, S_name) {
Father.call(this, P_name)
this.S_name = S_name
}
Son.prototype = new Father()
Son.prototype.by = function () {
console.log('毕业了')
}
var S = new Son('father', 'son')
console.log(S.P_name)// father
console.log(S.S_name)// son
2-2). 借用构造函数继承的常见弊端
- Father函数至少调用了两次
- S的原型对象上会多出一些属性, 但是这些属性是没有存在的必要
- 父类原型赋给子类原型
// 上面2-1借用构造函数继承的Son.prototype = new Father()改为下面方式
// 直接将父类的原型赋值给子类, 作为子类的原型
Son.prototype = Father.prototype
弊端:就是所有继承Father子类添加方法都在Father.prototype上共享。
- 原型式继承-对象
var obj = {
name: "why",
age: 18
}
// 1、原型式继承函数
function createObject1(o) {
var newObj = {}
Object.setPrototypeOf(newObj, o)
return newObj
}
// 2、
function createObject2(o) {
function Fn() {}
Fn.prototype = o
var newObj = new Fn()
return newObj
}
// var info = createObject2(obj)
// 3、
var info = Object.create(obj)
console.log(info)
console.log(info.__proto__)
- 寄生式继承-对象
var personObj = {
running: function() {
console.log("running")
}
}
function createStudent(name) {
var stu = Object.create(personObj)
stu.name = name
stu.studying = function() {
console.log("studying~")
}
return stu
}
var stuObj = createStudent("why")
var stuObj1 = createStudent("kobe")
var stuObj2 = createStudent("james")
- 寄生组合式继承(上面所有方案的最终方案)
function createObject(o) {
function Fn() {}
Fn.prototype = o
return new Fn()
}
function inheritPrototype(SubType, SuperType) {
SubType.prototype = Objec.create(SuperType.prototype)
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function() {
console.log("running~")
}
Person.prototype.eating = function() {
console.log("eating~")
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
inheritPrototype(Student, Person)
Student.prototype.studying = function() {
console.log("studying~")
}
var stu = new Student("why", 18, ["kobe"], 111, 100)
console.log(stu)
stu.studying()
stu.running()
stu.eating()
console.log(stu.constructor.name)
七、原型内容补充
- hasOwnProperty
- 对象是否有某一个属于自己的属性(不是在原型上的属性)
- in/for in 操作符
- 判断某个属性是否在某个对象或者对象的原型上
var obj = {
name: 'why',
age: 18
}
var info = Object.create(obj, {
address: {
value: '北京市',
enumerable: true
}
})
// hasOwnProperty方法判断
console.log(info.hasOwnProperty('address'))// true
console.log(info.hasOwnProperty('name'))// false
// in 操作符: 不管在当前对象还是原型中返回的都是true
console.log('address' in info)// true
console.log('name' in info)// true
// for in 遍历对象属性
for (var key in info) {
console.log(key)
}
- instanceof
- 用于检测构造函数的pototype,是否出现在某个实例对象的原型链上
function createObject(o) {
function Fn() {}
Fn.prototype = o
return new Fn()
}
function inheritPrototype(SubType, SuperType) {
SubType.prototype = createObject(SuperType.prototype)
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person() {
}
function Student() {
}
inheritPrototype(Student, Person)
console.log(Person.prototype.__proto__)
var stu = new Student()
console.log(stu instanceof Student) // true
console.log(stu instanceof Person) // true
console.log(stu instanceof Object) // true
- isPrototypeOf
- 用于检测某个对象,是否出现在某个实例对象的原型链上
function Person() {
}
var p = new Person()
console.log(p instanceof Person)// true
console.log(Person.prototype.isPrototypeOf(p))// true
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj)
// console.log(info instanceof obj)
console.log(obj.isPrototypeOf(info)) //true
- 原型继承关系图