(七)-前端-原型链
7.原型及原型链
原型
- 函数都带有一个prototype 属性,这是属性是指向构造函数的原型对象,这个对象包含所有实例共享的属性和方法。
- 原型对象都有一个constructor 属性,这个属性指向所关联的构造函数。
- 每个对象都有一个__proto__ 属性[非标准的方法],这个属性指向构造函数的原型 prototype
原型链
- 当访问实例对象的某个属性时,会先在这个对象本身的属性上查找,如果没有找到,则会 通过 proto 属性去原型上查找,如果还没有 找到则会在构造函数的原型的__ proto__中查 找, 这样一层层向上查找就会形成一个作用域链,称为原型链
原型相关习题
![img](file:///C:/Users/admin/AppData/Local/Temp/msohtmlclip1/01/clip_image006.png)
![img](file:///C:/Users/admin/AppData/Local/Temp/msohtmlclip1/01/clip_image007.png)
Object.create****的作用:
let obj = {a:123};
let o = Object.create(obj);
//该函数返回了一个新的空对象,但是该空对象的__proto__是指向了obj这个参数
// 手写Object.create
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
new****的执行过程是怎么回事?
new操作符做了这些事:
· 它创建了一个全新的对象
· 它会被执行[[Prototype]](也就是__proto__)链接
· 它使this指向新创建的对象
· 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
· 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用
//模拟new
function objectFactory() {
const obj = new Object();
const Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
const ret = Constructor.apply(obj, arguments);
return typeof ret === "object" ? ret : obj;
}
call,apply,bind****三者的区别?
**
** apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数fun.apply(thisArg, [argsArray])
apply 和 call 基本类似,他们的区别只是传入的参数不同。
apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。
call做了什么:
将函数设为对象的属性
执行&删除这个函数
指定this到函数并传入给定参数执行函数
如果不传入参数,默认指向为 window
//实现一个call方法:
Function.prototype.myCall = function(context) {
//此处没有考虑context非object情况
context.fn = this;
let args = [];
for (let i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
context.fn(...args);
let result = context.fn(...args);
delete context.fn;
return result;
};
/ 模拟 apply
Function.prototype.myapply = function(context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn(" + args + ")");
}
delete context.fn;
return result;
};
实现bind要做什么
返回一个函数,绑定this,传递预置参数
bind返回的函数可以作为构造函数使用。故作为构造函数时应使得this失效,但是传入的参数依然有效
// mdn的实现
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 维护原型关系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();
return fBound;
};
}
// 简单版
Function.prototype.myBind = function(context,...arg){
// this ---> fn
var _this = this;
return function(...ary){
// _this(...arg)
return _this.apply(context,arg.concat(ary))
}
}
实现类的继承
类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。
function Parent(name) {
this.parent = name
}
Parent.prototype.say = function() {
console.log(`${this.parent}: 你打篮球的样子像kunkun`)
}
function Child(name, parent) {
// 将父类的构造函数绑定在子类上
Parent.call(this, parent)
this.child = name
}
/**
1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
3. Object.create是创建了父类原型的副本,与父类原型完全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);
}
// 注意记得把子类的构造指向子类本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打篮球的样子像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是练习时长两年半的cxk
谈谈你对this指向的理解
this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象
改变 this 的指向我总结有以下几种方法:
· 使用 ES6 的箭头函数
· 在函数内部使用 _this = this
· 使用 apply、call、bind
· new 实例化一个对象
全局作用域下的this指向window
如果给元素的事件行为绑定函数,那么函数中的this指向当前被绑定的那个元素
函数中的this,要看函数执行前有没有 . , 有 . 的话,点前面是谁,this就指向谁,如果没有点,指向window
自执行函数中的this永远指向window
定时器中函数的this指向window
构造函数中的this指向当前的实例
call、apply、bind可以改变函数的this指向
箭头函数中没有this,如果输出this,就会输出箭头函数定义时所在的作用域中的this