面向对象编程
编程思想
面向对象
JS中的面向对象
new关键字
function sum(a,b){
this.a = a; // 通过this给实例对象添加属性
this.b = b;
this.total = a + b
return a + b;
}
sum(1,1); // 2
new sum(1,1); // {a:1,b:1,total:2}
- 和普通函数执行一样,形成执行上下文EC、AO、作用域链等
- new操作符会在函数执行前创建一个对象
- 在执行上下文初始化环境时将this指针指向刚刚通过new创建的对象
- 执行上下文进栈执行时,通过this指针给new创建的实例对象设置‘"私有"属性、方法
- return 时,检测返回值,如果没有返回值或者返回值是一个原始数据类型则返回通过new创建的那个实例对象;如果有返回值并且返回值是引用数据类型,则返回该引用数据
模拟实现new方法
/*
* Func[function]:类
* params[array]:基于剩余运算符获取传递的实参,都是给类传递的实参
*/
function _new(Func,...params){
// 1. 创建实例对象
let obj = {};
obj.__proto__ = Func.prototype;
// 2. 将类作为普通函数执行,基于call方法强制改变this的指向,将this指向新创建的实例对象obj
let result = Func.call(obj,...params);
// 3. 当返回的结果为引用数据类型时,则直接返回该值,否则返回obj
if(result !== null && /^(object|function)$/i.test(typeof result)) return result;
return obj;
}
function _new(Func,...params){
let obj = Object.create(Func.prototype);
let result = Func.call(obj,...params);
if(result !== null && /^(object|function)$/i.test(typeof result)) return result;
return obj;
}
Object.create = function create(obj){
if(obj === null || typeof obj !== 'object'){
throw new TypeError('Object prototype may only be an Object');
}
function fn(){};
fn.prototype = obj;
return new fn();
}
原型及原型链
类和原型
- 所有函数都会天生自带一个属性:
prototype
原型(显示原型) prototype
属性默认指向一个对象数据类型- 在对象中存储的是供实例调用的所有公共属性和方法
- 类的原型对象上默认有个属性:
constructor
构造函数,属性值指向当前类本身
实例和原型链
- __proto__ 属性的属性值:当前实例对象所属类的原型(prototype),及
实例.__proto__ === 类.prototype
类、原型、实例之间的关系
function Fn(){
this.x = 100;
this.y = 200;
this.getX = function(){
console.log(this.x);
}
}
Fn.prototype.getX = function(){
console.log(this.x);
}
Fn.prototype.getY = function(){
console.log(this.y);
}
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX); // false
console.log(f1.getY === f2.getY); //true
console.log(f1.__proto__.getY === Fn.prototype.getY); // true
console.log(f1.__proto__.getX === f2.getX); // false
console.log(f1.getX === Fn.prototype.getX); // false
console.log(f1.constructor); // Fn
console.log(Fn.prototype.__proto__.constructor); // Object
f1.getX(); // 100
f1.__proto__.getX(); // undefined
f2.getY(); // 200
Fn.prototype.getY(); // undefined
- 实例上的函数执行时,通过this用到某个属性,首先看是否是自己实例的私有属性,如果是自己的私有属性,那么就直接操作自己的私有属性
- 如果不是自己的私有属性,会默认基于__proto__找所属类的原型上的公共属性和方法
- 如果所属类的原型上也没有,则会继续基于原型链__proto__继续向上查找,直到找到Object.prototype上为止
原型重定向
原型重定向带来的问题
- 新定向的原型对象上,没有constructor属性,结构不完善
- 浏览器默认生成的原型对象会因为缺少引用而被释放掉,可能会导致原始加入的属性和方法丢失掉
- 内置对象是不允许被重定向的,防止把内置对象的方法丢失
function Fn(){
this.x = x;
this.y = y;
}
Fn.prototype.getX = function(){};
Fn.prototype = { //重定向的新对象,没有constructor,也没有getX了
getY:function(){}
};
let f1 = new Fn;
那原型重定向给我们带来了这么多弊端,那我们为什么还要做原型重定向呢?
给原型批量添加方法
function Fn(){
this.x = x;
this.y = y;
}
Fn.prototype.getX = function(){};
Fn.prototype.getY = function(){};
Fn.prototype.getZ = function(){};
function Fn(){
this.x = x;
this.y = y;
}
let A = Fn.prototype
A.getX = function(){};
A.getY = function(){};
A.getZ = function(){};
function Fn(){
this.x = x;
this.y = y;
}
Fn.prototype.z = 300;
Fn.prototype = Object.assign(Fn.prototype,{
getX(){},
getY(){},
getZ(){},
})
基于内置类原型拓展方法
- 写在内置类原型上的方法,实例在调用的时候
实例.xxx()
xxx()
方法执行- 执行主体
this
是要操作的实例
- 向内置类原型上拓展方法的目的是,将一些公共的属性和方法拓展到内置类的原型上,这样调用起来就比较的方便
- 自定义属性或方法名一定要与原型上本来就存在的属性和方法名区分开来,避免自己的方法将原型的方法覆盖
let arr = [1,1,2,3,4,2,3,1,4,4]
// 1.方法一
function unique(arr){
return Array.from(new Set(arr));
}
// 2.方法二
Array.prototype.unique = function unique(){
//保证this是一个数组
if(!Array.isArray(this)) throw new TypeError('确保操作的是一个数组');
return Array.from(new Set(this));
}
unique(arr) //=>[1,2,3,4]
arr.unique(); //=>[1,2,3,4]
面试题
let n = 10;
let m = n.plus(10).minus(5);
console.log(m) //=>15 (10+10+-5)
Array.prototype.plus = function plus(num){
return this + num;
}
Array.prototype.minus = function minus(num){
return this - num;
}
函数的三种角色
Function内置类
虽然函数的原型指向的不是一个对象,但是这个匿名空函数的表现却和对象是一样的,没有任何区别,而且这个函数没有函数自带的属性prototype却存在对象的__proto__
属性。
虽然Function.prototype指向的是一个匿名的空函数,但是我们却同样能够通过函数的实例来调用Function原型上的公共属性和方法,从这我们能够看出函数其实也是一种对象。
函数的三种角色
函数的三种角色之间没有必然的联系
终极原型链
- 所有的内置类都是Function的实例(包括Object)
- Function本身,既是Object的实例也是Function的实例
- Object本身,既是Object的实例也是Function的实例
Object.__proto__.__proto__ === Object.prototype
Function.__proto__ === Function.prototype
- 在Function.prototype上有call、apply、bind三种改变this的方法,所有函数都能调用者三个方法
阿里面试题
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
var getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); //3
参考资料
- 《this--函数的执行主体》
- 《JavaScript函数的存储机制和运行原理》
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!