1、原型是什么?
原型就是对象里面一个以__proto__为键的隐式属性。这个属性的值指向一个prototype对象,这个对象也叫原型对象。prototype是构造器下的一个属性。所有被同一个构造函数创建出来的对象的原型,都指向构造器同一个prototype对象,所以这些对象具有prototype对象里面相同的属性和方法。除非在实例化一个对象obj1之后,又给这个构造器下的prototype对象指向其他的对象,然后又用这个构造函数创建出另外的实例对象obj2,此时obj1的原型对象和obj2的原型对象,是不一样的。这也说明,一个对象一旦被创建,它的原型对象也被创建。
function Car() {}
Car.prototype.brand = "Benz"
var car1 = new Car();
var car2 = new Car();
console.log(car1.__proto__ === car2.__proto__); //true
//把一个新的对象赋给构造器下的prototype属性
Car.prototype = { brand :"Benz" }
var car3 = new Car();
Car.prototype.age=2
console.log(car1.__proto__ === car3.__proto__); //false
console.log(car1.age);//undefined
console.log(car3.age);//2
car1.__proto__.length=5
console.log(car2.__proto__.length);//5
分析以上代码:
第5行代码:说明在Car.prototype没有赋予新的对象之前,所有被同一个构造函数创建出来的对象的原型,都指向同一个原型对象。
第10行代码:说明Car.prototype没有赋予新的对象之后,所创建出来的对象的原型,和Car.prototype没有赋予新的对象之前,所有创建出来的对象的原型,指向的不是同一个对象。
第11行的结果undefined和12的结果是2,进一步说明car1和car3的原型,指向的是不同的对象。
第13行:给car1的原型对象添加一个length属性值为5;
第14行打印出car2的原型对象的length属性的值为5。
综合13、14行代码,进一步说明car1和car2的原型对象是同一个对象。
2、继承
JS引擎寻找对象属性的机制:先在对象的自身上查找,如果找不到就到原型对象上找,如果还是找不到就到原型对象上的原型上找,直到Object.prototype。如果找到就立即返回,不再往下查找,否则返回undefined。对象的原型对象就是继承的桥梁,把一个obj对象,设置为构造器的prototyoe属性的值,那么该构造器创建出来的obj2对象就继承了obj对象。obj对象就是obj2对象的祖先。
Object.prototype.superSkill="All"
professor.prototype.tSkill = "Java"
function professor() {}
function Teacher() {
this.mSkill = 'JS'
}
Teacher.prototype = new professor();
var teacher = new Teacher()
Student.prototype = teacher;
function Student() {
this.pSkill = 'CSS'
}
var student = new Student()
console.log(student.pSkill);//CSS
console.log(student.mSkill);//JS
console.log(student.tSkill);//Java
console.log(student.superSkill);//All
继承的关系如下图:
。
3、原型链
由于对象有原型,指向原型对象。原型对象也是对象,所以它也有自己的原型,它的原型又指向另外的原型对象。这样形成有序的链条,叫原型链。 原型链的尽头是什么,有意义的是Object.prototype,没有意义的是null。js引擎查找对象的某一个属性或者方法,只查到Object.prototype这一级就结束。
3-1:子类操作父类属性:
1、父类的原始值属性:只能访问,不能增删改。
2、父类的对象属性:可以随意操作,包括删除。
3、父类的方法:子类可以访问,可以重写(对自己生效),其他子类无效。
4、圣杯模式
有一个这样的场景,比如父类有些私有属性,不想让子类访问,公共的属性对所有的子类都可见。 主要思想是通过中间对象实现间接继承,隔离父子的原型直接访问,代码实现如下:
function inherit(Target, Origin) {
// 1、声明一个构造函数Buffer
function Buffer() {}
// 2 把父级的原型对象赋给Buffer的原型对象
Buffer.prototype = Origin.prototype
// 3、把Buffer的实例赋给子类的原型对象
Target.prototype = new Buffer();
// 还原构造器
Target.prototype.constructor = Target;
Origin.prototype.constructor = Origin;
}
// 公共属性
Parent.prototype = {
firstName: '姓',
say: function() {
console.log("hello world");
},
makeMoney: function() {
console.log(`通过${this.job}赚钱`);
}
}
// 私有属性
function Parent() {
this.job = "种地";
this.privateName = "个人名字";
}
function Childern() {
this.job = "写代码"
}
inherit(Childern, Parent);
var child = new Childern();
var parent = new Parent();
console.log(child.privateName);//undefined
//打印出undefined,证明父类的私有属性,子类访问不到。
child.makeMoney();//通过写代码赚钱
parent.makeMoney();//通过种地赚钱
5、其他说明
5-1: 不是所有的对象都继承Object.prototype,比如Object.create(null)。
5-2: call、aplly、bind:他们都可以该this的指向,所以他们可以借用构造数里面的方法,并不能真正的实现继承:
call和apply都可以改变被调用函数this的指向,而且是立即执行,只是给函数传参的方式不同:
1、apply:是以数组传参。
2、call:传多个单个参数。
3、bind:也可以改变被调用函数this的指向,但是指向绑定函数,并不执行,按需执行。传参和call一样。
Animal.prototype.food="fish"
function Animal(a,b,c){
this.species='animals'
this.move=function(){
console.log(`I am ${this.name},I can ${this.skill},I belong to ${this.species}。 ${a} ${b} ${c} Go !`);
}
}
function Cat(){
Animal.call(this,1,2,3)
this.name='cat'
this.skill="runing"
}
function Bird(){
Animal.apply(this,[1,2,3])
this.name='bird'
this.skill="Flying"
}
var cat=new Cat();
cat.move();
console.log(cat.eat);//undefined
var bird=new Bird();
bird.move();
通过console.log(cat.eat)打印出undefined,可以证明他们不能实现真正的继承,因为访问不到原型上的属性。
通过节流的案例说明bind的用法:
var inp = document.getElementById("inputId")
inp.oninput = throttle(function() { console.log(this.value); }, 500)
function throttle(fn, delay) {
var timer
return function() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(fn.bind(this), delay)
}
}
5-3:立即执行函数/IIFE(immediately invoke function Exprssion)
1、立即执行函数实现插件:
(function() {
function add(a, b) {
return a + b
}
function multiply(a, b) {
return a * b
}
window.add = add;
window.mul = multiply;
})()
console.log(window.add(2, 6));
console.log(window.mul(2, 6));
1、立即执行函数实现模块化,模块化是防止全局变量污染:
(function() {
var a = 3
var b = 6
function initAdd() {
console.log(a + b);
}
function initMultiply() {
console.log( a * b);
}
window.init = function() {
initAdd();
initMultiply();
}
})()
window.init()
5-4:链式调用
var obj={
fn1:function(){
console.log('fn1');
return this
},
fn2:function(){
console.log('fn2');
return this
},
fn3:function(){
console.log('fn3');
return this
}
}
obj.fn1().fn2().fn3()
5-5:对象访问属性
最早的JS引擎都是用方括号都是obj['key']来访问的 ,现在可以用点运算符来访问属性,再JS的底层是执行时都会转换成方括号运算符来访问,方括号运算符支持表达式,即obj['key'+1]
5-6:判断数组的3种方法
console.log(Array.isArray([]));//true
console.log([] instanceof Array);//true
console.log(Object.prototype.toString.call([])==='[object Array]');//true
5-7:对象原型链上属性操作方法 1、for in 只能遍历原型链上可枚举的属性;
2、in 判断原型链上是否存在某个属性或者方法;
3、Object.hasOwnProperty('key',obj)/obj.hasOwnProperty('key')判断key属性是不是obj的自身属性
4、Object.keys获取对象自身可枚举的属性和方法名称;
5、Object.getOwnPropertyNames,获取对象自身的属性和方法名称。
Fn1.prototype.top='top'
Fn1.prototype.say=function (){console.log("hello world");}
function Fn1(){
this.fn1="fn1"
}
Fn2.prototype=new Fn1()
function Fn2(){
this.fn2="fn2"
}
Fn3.prototype=new Fn2()
function Fn3(){
this.fn3="fn3"
this.eat=function(){}
}
var obj=new Fn3()
Object.defineProperty(obj,'name',{
enumarable:false,
value:'tony'
})
for (let key in obj) {
console.log(obj[key]);//fn3、f ()、fn2、fn1、top、f ()
}
console.log(obj.name);//tony
console.log('name' in obj);//true
console.log('say' in obj)//true
console.log(Object.keys(obj));//["fn3", "eat"]
console.log(Object.getOwnPropertyNames(obj));//["fn3", "eat", "name"]