JS面向对象
封装
原始实例对象写法
js中没有class类,所有为了表达一个对象的属性和方法,原始的写法为:
var person1 = {
name: "wy",
age: "26",
run: function () {
console.log(this.name + "running");
}
};
person1.run();
这种写法最大的问题是,如果是多个对象就需要多次进行定义声明,对其进行改进
函数式写法
function person1(name, age, run) {
return {
name: name,
age: age,
run: run
}
};
var f = function run() {
console.log("run");
}
var p1 = person1("wy", 26, f);
此时不同的参数可以构造出不同的对象,后来参考java等new出对象的方式,最后形成了下面的构造函数式的写法
构造函数式
function Person(name, age, run) {
this.name = name;
this.age = age;
this.run = run;
};
var f = function run() {
console.log("run");
}
var p1 = new Person("wy", 26, f);
这种写法相对上面那种,不仅更加符合面向对象的思想和写法,也为继承奠定了基础。
但是思考java中的静态方法,静态属性,既然js是面向对象的,那么这些和类绑定的思想需要如何来进行实现了,这个就涉及到了原型。我们可以先简单理解为原型就是java中的类对象,里面的属性和方法类似java的静态方法,比如person对象的公共方法可以写为
prototype
function Person(name, age, run) {
this.name = name;
this.age = age;
this.run = run;
};
Person.prototype.sleep=function () {
console.log("sleep")
}
var f = function run() {
console.log("run");
}
var p1 = new Person("wy", 26, f);
var p2 = new Person("sz", 76, f);
Person.prototype.sleep();
p1.sleep();
p2.sleep();
此时,p1,p2都可以调用原型定义的方法sleep,设置Person.prototype也可以调用,因为此时我们把Person.prototype就当做class类对象,当然可以调用其静态方法sleep。
继承
apply继承
function Person() {
console.log("run");
};
function Sofer(code) {
Person.apply(this,arguments);
this.code = code;
}
var s1=new Sofer("js");
console.log(s1.code);
这种继承方式通过apply将Person函数直接‘copy’过来了,上篇文章JS基础讲过,apply可以改变函数的this指向,更可以理解为copy了一个这样的函数。
但是这样写法有个很大缺陷,定义的原型方法无法继承过来,毕竟只是拷贝了一个函数,并不是真正的面向对象的思想,比如
function Person() {
console.log("run");
};
Person.prototype.eat = function () {
console.log("eat");
};
function Sofer(code) {
Person.apply(this, arguments);
this.code = code;
}
var s1 = new Sofer("js");
console.log(s1.code);
s1.eat();//Uncaught TypeError: s1.eat is not a function
prototype模式
function Person() {
console.log("run");
};
Person.prototype.eat = function () {
console.log("eat");
}
function Sofer(code) {
this.code = code;
}
Sofer.prototype = new Person();//run
Sofer.prototype.constructor = Sofer
var s1 = new Sofer("js");
console.log(s1.code);//js
s1.eat();//eat
通过子类原型指向父类对象,然后在将子类原型的构造函数指向自己本身,这种方式用的最多。
继承父类的公共属性和方法
如果只需要继承父类的公共属性和方法,即都是原型里面的属性方法,那么可以子类原型指向父类原型
function Person() {
this.filed1 = "filed1";
}
Person.prototype.eat = function () {
console.log("eat");
};
function Sofer(code) {
this.code = code;
}
Sofer.prototype = Person.prototype;
Sofer.prototype.constructor = Person;
var s1 = new Sofer("js");
console.log(s1.code);//js
s1.eat();//eat
console.log(s1.filed1);//undefined
继承父类的公共属性和方法改进
子类原型指向父类原型会增加内存开销,因为此时子类原型的修改都修改父类的原型,所以可以用一个空对象作为中介:
var F = function () {
};
function Person() {
this.filed1 = "filed1";
}
Person.prototype.eat = function () {
console.log("eat");
};
function Sofer(code) {
this.code = code;
}
F.prototype = Person.prototype;
Sofer.prototype = new F();
Sofer.prototype.constructor = Person;
var s1 = new Sofer("js");
console.log(s1.code);//js
s1.eat();//eat
console.log(s1.filed1);//undefined
此时子类的原型对象的修改就不会影响父类的原型了。F的对象内存也会很小。
原型链
概念:
- 所有的函数都有prototype属性,指向函数的原型对象。(显式原型)
- 所有的对象都有__proto__属性,指向是该对象的构造函数的原型。(隐式原型)
function Person(name){
this.name = name;
}
var p1 = new Person('louis');
console.log(Person.prototype);//Person原型 {constructor: Person(name),__proto__: Object}
console.log(p1.__proto__ == Person.prototype);//true
Person.prototype的log结果是constructor和__proto__,其中,constrctor指代的就是Person这个函数,因为其就是构造函数(自己的原型属性指向了自己);__proto__是所有对象都有的属性,所以对象Person.prototype也会有该属性,根据定义,其指向的应该是Person.prototype的构造函数的原型对象,那到底是什么呢?p1的__proto__指向构造函数的原型对象,和概念一致。
原型链探究
下面我们来深究下__proto__
console.log(Person.__proto__ === Function.prototype);//true
这行代码说明构造函数的构造函数是Function,类似java的Class,Method等,事实上,所有的构造器都继承于Function.prototype
console.log(Math.__proto__ === Object.prototype); // true
console.log(JSON.__proto__ === Object.prototype); // true
但是Math,JSON是以对象形式存在的,无需new
那什么是Function.prototype呢
Function.prototype
console.log(typeof Function.prototype) // "function"
实际上, Function.prototype也是唯一一个typeof XXX.prototype为 “function”的prototype。其它的构造器的prototype都是一个对象。如下:
console.log(typeof Number.prototype) // object
console.log(typeof Boolean.prototype) // object
console.log(typeof String.prototype) // object
console.log(typeof Object.prototype) // object
console.log(typeof Array.prototype) // object
console.log(typeof RegExp.prototype) // object
console.log(typeof Error.prototype) // object
console.log(typeof Date.prototype) // object
作者:路易斯
链接:https://juejin.im/post/58f9d0290ce46300611ada65
再看
console.log(Function.prototype.__proto__ === Object.prototype) // true
再看
console.log(Object.prototype.__proto__ === null);//true,什么都没有,Object就是基类无疑了
所以,普通构造函数的构造函数是Function,
Function的原型的构造函数是Object,
Object的原型的构造函数是null
proto
只要是函数,它的__proto__就指向 Funtion.prototype. 即函数的构造函数都为Function
对于非函数,其指向的是构造函数的原型。
比如
console.log(Object.__proto__ == Function.prototype);//true
console.log(Function.__proto__ == Function.prototype);//true
原型总结
- 1.在JS里,万物皆对象。函数是一等公民。
- 函数有prototype,它是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。
- 对象有属性__proto__,指向对象的构造函数的原型对象。
- Function是所有函数的构造函数
- Function.prototype的构造函数是Object,Object.prototype的构造函数是null
- 原型可以类比java的静态属性,方法,隐式原型构成了原型链,使得js具有面向对象的可能。