在学习了
js
高级以及es6
之后,再来学习面向对象编程,之前在学习es6
,canvas
的时候已经有接触到了这种思想,感觉还是需要在深入的学习一下,这部分内容牵扯到很多原型链部分的东西,当做复习一下还是很不错的!
ES5
中的面向对象面向对象编程(OOP)
具有灵活、代码可复用、高度模块化等特点。
在
ES5
中没有class
类的概念,所以面向对象是基于构造函数和原型链的,注意:
构造函数的名字的第一个字母要大写
特点
this
关键字,代表了所要生成的对象实例new
关键字实例化function Dog() {
this.name = name;
this.age = age;
}
var dog1 = new Dog('dw', 10)
如果没有使用
new
关键字,则结果会是undefind
,原因是该函数没有返回值
instanceof
的用法
instanceof
运算符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上。返回true
orfalse
通过instanceof
来判断当前的的对象是否是实例化出来的,如果是实例化出来的this
指向实例化出来的对象,也就是这里的Person
,否则作为普通函数来说当前的this
指向window
function Person(name, age){
if(this instanceof Person){
// this指向了当前的实例,外部使用了关键字new
this.name = name;
this.age = age;
}else{
// this指向了window,外部没有使用了关键字new
return new Person(name,age);
}
new
命令内部原理this
,继承函数原型this
指向构造函数的对象实例,执行构造函数内容为新对象添加属性和方法var obj = {}//创建空对象
obj.__proto__ = Person.prototype;//继承作用域
Person.call(obj,)//改变this指向
return obj //返回对象
constructor
属性每个对象在创建时都会自动拥有一个构造函数属性constructor
constructor是对象__proto__
上的一个属性(如果该对象是函数,则在其prototype
上),通常指向生成这个对象的函数,也就是指向构造函数的引用
obj.constructor === Obj
function Foo(){};
var foo = new Foo();
原型对象:
Foo.prototype
实例对象:
foo
就是实例对象,每一个原型对象中都有一个__proto__
,每个实例对象都有一个constructor
属性,这个constructor
通过继承关系继承来的,它指向了当前的构造函数Foo
构造函数:
用来初始化新创建对象的函数,Foo
是构造函数,自动给构造函数赋予一个属性prototype
,该属性指向了实例对象的原型对象
function Foo(){}
var foo = new Foo()
prototype
属性的作用继承机制:通过原型对象(prototype)实现继承
原型对象的作用,就是定义所有实例对象共享的属性和方法
Foo.prototype.name = 'ljc';
tips:
所有的对象都有自己的原型对象
原型链:对象的原型 => 原型的原型 => 原型的原型的原型 => null
所有的对象都继承了
Object.prototype
上的属性和方法
查找属性和方法的规则:js
引擎会先寻找对象本身的属性和方法,如果找不到就到它的原型对象去找,如果还是找不到,就到原型的原型去找,如果直到最顶层的Object.prototype
还是找不到,就会返回undefined
constructor
属性注意点:一旦我们修改构造函数的原型对象,为防止引用出现问题,同时也要修改原型对象的constructor属性
function MyArray(){};//构造函数
MyArray.prototype = Array.prototype;//复制数组的原型对象,获得方法
MyArray.prototype.constructor = MyArray;//改变constructor指向
var arr = new MyArray();//实例化对象
字面量
var Person = { name: 'ljc' }
new
关键字var obj = new Object();
obj.name = 'ljc';
console.log(obj);
Object.create(对象)
var a = {
getName:function(){
console.log('ljc');
}
}
var b = Object.create(a);
b.getName();
缺点
:代码冗余
优点:
能够创建多个类似的对象
function createObj(name,age) {
var o = new Object();//创建对象
//添加属性和方法
o.name = name;
o.age = age;
o.sayName = function() {
console.log(this.name);
}
//返回对象
return o
}
var obj = createObj('ljc',19)//每次调用返回一个对象
缺点:
所有的constructor
指向都相同,没有解决对象识别问题
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
var man = new Person('ljc',19);
var woman = new Person('dw',18);
通过在外部使用new
关键字,将属性和方法通过this
绑定到相应的对象上,解决了工厂模式的遗留问题
缺点:
每个对象都会有一个sayName
方法,执行的功能是一样的,但是却仍然占用了不同的内存空间,浪费了内存资源
构造函数扩展模式
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
console.log(this.name);
}
将sayName
方法定义成全局函数,解决了内存浪费的问题
缺点
:污染全局空间
function createObj(name,age) {
var o = new Object();//创建对象
//添加属性和方法
o.name = name;
o.age = age;
o.sayName = function() {
console.log(this.name);
}
//返回对象
return o
}
var obj = new createObj('ljc',19)
结合了工厂模式和构造函数模式:创建一个函数,函数体内部实例化一个对象,并且将对象返回,在外部使用new
来实例化对象
缺点
:由于没有使用构造函数,所以instanceof
运算符和prototype
属性没有意义
function Person(name){
var a = 10;
var obj = new Object();
obj.sayName = function(){
console.log(a);
console.log(name);
}
return o;
}
var p1 = Person('ljc');
p1.sayName();
没有公共属性,并且它的方法也不引用this
,name
属于函数内部的私有属性,有点像闭包,p1
被称为稳妥对象
缺点
:由于没有使用构造函数,所以instanceof
运算符和prototype
属性没有意义
将属性方法绑定在
prototype
上,实现共享
注意:
需要改变constructor
指向
function Person(){}
Person.prototype = {
constructor: Person,
name: 'ljc',
age: 19,
friends:['dw','xy'],
sayName:function(){
console.log(this.name);
}
}
var me = new Person();
var you = new Person();
每个通过Person
实例化出来的对象都会继承函数的原型
缺点
:当我们修改其中一个对象的属性时,另一个对象下的也会被修改
me.friends.push('jc');
console.log(you.friends);//["dw","xy","jc"]
认同度最高的一种创建自定义对象的模式
function Person(name,age){
// 定制当前对象自己的属性
this.name = name;
this.age = age;
this.friends = ['dw','xy'];
};
//定制公共的方法
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
}
var wo = new Person('wo', 18);
var you = new Person('you', 20);
解决了原型模式状态共享问题,将私有的属性定义在函数内部,共有的方法通过原型去实现继承引用
在原型对象的所有属性和方法都能被实例所共享
通过重写原型对象的方式,将一个父对象的属性和方法作为子对象的原型对象的属性和方法
function Animal(){
this.name = 'alex';
this.colors = ['red','green','blue'];
}
Animal.prototype.getName = function(){
return this.name;
}//添加方法
function Dog(){};
Dog.prototype = new Animal();//将animal的原型对象继承给dog
Dog.prototype.constructor = Dog;//指向dog
var d1 = new Dog();
var d2 = new Dog();
存在问题:
通过在子类构造函数内部调用父类构造函数,实现继承,解决了原型链继承存在的问题
使用到了
call
关键字,改变当前函数的this
指向当前的实例对象
在new
的过程中会将this
指向当前的实例对象,因此在构造函数内部的this
指向当前的实例对象
function Animal(name){
this.name = name;
this.colors = ['red','green','blue'];
}
Animal.prototype.getName = function(){
return this.name;
}
function Dog(name){
Animal.call(this,name);
}
var d1 = new Dog('阿黄');
var d2 = new Dog('小红');
存在问题:父类定义的共享方法不能被子类所继承下来,只能继承属性
原型链继承和借助构造函数继承结合,将2者的优点集合在一起
function Animal(name) {
this.name = name;
this.colors = ['red', 'green', 'blue'];
}
Animal.prototype.getName = function() {
return this.name;
}
function Dog(name) {
Animal.call(this, name);//借助构造函数继承
}
Dog.prototype = new Animal();//原型链继承
Dog.prototype.constructor = Dog;
var d1 = new Dog('阿黄');
var d2 = new Dog('阿红');
利用原型链继承的方式(重写原型对象),将父类的共享方法继承下来,同时在子类构造函数中调用父类构造函数,使得修改一方的值,不影响另一方的值
存在问题:无论在什么情况下,都要调用父类构造函数2次
利用
Object.create
方法将父类的原型对象传入给子类的原型对象
将组合模式继承中的第11行代码改为以下即可
Dog.prototype = Object.create(Animal.prototype)
利用Object.assign
方法来拷贝原型对象,从而实现多重继承(混入技术Mixin)
Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
Me.prototype = Object.create(Person.prototype);
Object.assign(Me.prototype, Parent.prototype);
通过这样的方式,将父类构造函数的原型对象拷贝到本身的构造函数上,同时实现了将多个父函数的原型绑定到一个子元素上
Object.keys()
:参数是一个对象,返回是一个数组,可枚举的才返回,等同于for...in
循环
let a = {
name:'ljc',
age:19
}
let arr = ['l','j','c'];
console.log(Object.keys(arr));// ["0", "1", "2"]
console.log(Object.keys(a));// ["name", "age"]
注意
:数组也是一个对象,可以作为参数传进去,返回的是数组索引
Object.getOwnPropertyNames()
接收一个对象作为参数,返回了一个数组,包含了该对象自身的所有属性名,包括不可枚举的属性
let arr = ['l','j','c'];
console.log(Object.getOwnPropertyNames(arr));// ["0", "1", "2", "length"]
Object.getPrototypeOf()
:参数是一个对象,返回该对象的原型,也是获取原型对象的标准方法
function Fn() {}
let f1 = new Fn();
console.log(Object.getPrototypeOf(f1) === Fn.prototype);//true
等同于构造函数的
prototype
方法
注意
:空对象的原型是Object.prototype
console.log(Object.getPrototypeOf({}) === Object.prototype);//true
注意
:函数的原型是Function.prototype
console.log(Object.getPrototypeOf(foo) === Function.prototype)//true
Object.setPrototypeOf()
:接收两个参数,第一个参数是现有对象,第二个参数是原型对象,把第二个作为第一个的原型
var a = {};
var b = {
x: 1
};
Object.setPrototypeOf(a, b);//把b作为a的原型
console.log(a.x);// 1
console.log(Object.getPrototypeOf(a))// {x: 1}
模拟new
关键字
function F(){
this.foo = 'foo';
}
var f = Object.setPrototypeOf({},F.prototype);//创建空对象,继承原型,返回对象
F.call(f);//改变this指向
console.log(f);// F {foo: "foo"}
通过Object.setPrototypeof
方法可以模拟实现new
内部原理
Object.create()
:创建一个新对象,使用现有的对象(传入的参数)来提供新创建的对象的__proto__
,也就是将这个参数作为这个新对象的原型
var A = {
say :function() {
console.log('ljc');
}
}
var B = Object.create(A)
B.say();// ljc
该方法还有第二个参数,有点复杂 MDN链接
valueOf()
:返回指定对象的原始值。
// Function:返回函数本身
function foo(){}
console.log( foo.valueOf() === foo ); // true
// Array:返回数组对象本身
var array = ["ABC", true, 12, -5];
console.log(array.valueOf() === array); // true
当遇到要预期的原始值的对象时,JavaScript会自动调用它。
我们可以根据这样的特性来自定义这个方法,从而实现我们想要的操作
var obj = new Object()
obj.valueOf = function(){
return 2;
}
console.log(obj.valueOf() === obj);
console.log(1 + obj);// 1[object Object] ------> 3
原理
:valueof
是属于Object原型对象的方法,Object.valueof
的层级会优先于原型对象内的方法,所以会先调用我们自定义的valueof
方法
toString()
:返回一个表示该对象的字符串。
var o = new Object();
o.toString(); // returns [object Object]
覆盖默认的toString
方法
function Dog(name,breed,color,sex) {
this.name = name;
this.color = color;
}
var theDog = new Dog("Gabby", "chocolate");
Dog.prototype.toString = function dogToString() {
var ret = "Dog " + this.name + " is " + this.color + " " ;
return ret;// 'Dog GAbby' is chocolate
}
注意:
在每个不同的数据类型中,定义了自己的toString
方法
toLocaleString()
返回一个该对象的字符串表示,字符串的形式与用户地区有关
Array
:Array.prototype.toLocaleString()
Number
:Number.prototype.toLocaleString()
Date
:Date.prototype.toLocaleString()
这几个感觉有点说不清楚,上面的代码链接到了MDN文档对应部分
isPrototypeOf()
用于检测一个对象是否存在于另一个对象的原型链上
instanceof
运算符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上
区别
: inPrototypeOf
是判断这个对象是不是另一个对象的儿子,instanceof
是判断这个实例对象是不是由这个构造函数创建的
hasOwnProperty()
方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
o = new Object();
o.prop = 'exists';
o.hasOwnProperty('prop');
注意
:只有自身的属性才为true,继承来的属性返回false,
getOwnPropertyDescriptor()
通过getOwnPropertyDescriptor
属性获得描述属性,只能获取自身的属性,不能获取继承的属性
writable
:是否可写
enumerable
:是否可枚举
configurable
:是否可配置,例如删除
set
:作为属性的 setter 函数,如果没有 setter 则为undefined
。函数将仅接受参数赋值给该属性的新值。
get
:作为该属性的 getter 函数,如果没有 getter 则为undefined
。函数返回值将被用作属性的值。
var obj = {
name:'ljc'
}
console.log(Object.getOwnPropertyDescriptor(obj,'name'));
//{value: "ljc", writable: true, enumerable: true, configurable: true}
propertyIsEnumerable()
返回一个布尔值,表示指定的属性是否可枚举
var arr = [1,2,3];
console.log(arr.propertyIsEnumerable('length'));//false
注意
:只能判断实例对象自身的属性,不能判断继承来的属性,否则为false
defineProperty()
直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
传参方式
Object.defineProperty(属性所在的对象,属性名,属性描述对象)
示例
let obj = {};
Object.defineProperty(obj, "key", {
enumerable: false,
configurable: false,
writable: false,
value: "ljc"
});
console.log(obj)// {key: "ljc"}
defineProperties()
方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。可以一次定义多个属性
示例
var obj = Object.defineProperties({},{
p1:{
value:123,
enumerable:true
},
p2:{
value:"ljc",
enumerable:false
},
p3:{
get:function(){
return this.p1 + this.p2;
},
enumerable:true,
configurable:true
}
})
console.log(obj);// {p1: 666, p2: "ljc"}
console.log(obj.p1);// 666
其中的p2
属性颜色变浅,因为设置了不可枚举
注意
:一旦定义了取值函数get
,就不能同时定义value
属性,否则会报错,也不能设置writable
属性
注意
:调用了该方法但有传一个空对象,所有值都会被设为false
var obj4 = Object.defineProperty({},'foo',{});
在不可写的情况下,更改属性值,不会报错,也不会更改
几个注意点
如果enumrable
设置false,通常以下三个操作不会取到该属性
for...in
Object.keys()
JSON.stringify()
注意
:虽然不能获取自身的,但是可以获取继承来的属性
configurable
为false,属性描述对象 value,writable,enumrable,configurable都不能被修改
writable
,true改为false会允许的
value
属性 只要writable和 configurable有一个为true,就允许被修改
get
方法,在获取属性时会自动调用这个方法
set
方法,在修改属性时会被自动调用,需要一个参数
let obj = {
num: 2,
get a() {
return '我被调用了,我的值自增' + ++this.num
},set a(value) {
console.log('我被改了');
}
}
console.log(obj.a);//我被调用了,我的值自增3
obj.a = 2;//我被改了
有关面向对象的知识就到这了!
在ES6中新增了class类的关键字,以及一些相关属性,优化了先前的面向对象代码可读性低的问题,新的class
写法让对象原型的写法更加清晰,更加的像面向对象编程的语法,因此class
也只是一个语法糖,其所有内容都可以通过es5中对象原型方式实现