一、原型规则
- 所有的引用类型(数组 对象 函数),都具有对象的特征,即可自由拓展属性(除了’null’ 以外);
- 所有的引用类型(数组 对象 函数),都有一个__proto__[隐式原型]属性,属性值是一个普通对象;
- 所有的函数,都有一个prorotype[显式原型]属性,属性值也是一个普通对象;
- 所有的引用类型(数组 对象 函数),__proto__属性值指向它的构造函数的'prototype’属性值;
当试图得到一个对象的某个属性时,如果该对象本身没有这个属性,那么会去它的__proto__属性即( 构造函数的prototype ) 中寻找, 由此形成了原型链;
var obj = {}; obj.a = 100; var arr = []; arr.a = 100; function fn(){}; fn.a = 100; console.log(obj.__proto__,arr.__proto__,fn.__proto__) console.log(fn.prototype) console.log(obj.__proto__ === Object.prototype)//true ---------------------------------------------- //属性查找 //构造函数 function Person(name, age){this.name = name;} Person.prototype.alertName = function(){ alert(this.name) } //创建示例 var f = new Person('jerry'); f.printName = function(){alert(this.name)} f.printName();//子类自己补充的方法 f.alertName();//调用通过prototype原型对象定义的alertName方法 f.toString();//需要在f.__proto__.__proto__里面找 f instanceof Person //true 用于判断 引用数据类型 属于哪个构造函数的方法 f instanceof Object //true 再往上一层找 是属于Object 所以也是对的 f.__proto__ === Person.prototype //true __proto__指向构造函数的prototype
二、实现继承方式
父类代码:
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
this.data = [1];
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
一 、原型链继承
核心:将父类的实例作为子类的原型
function Cat(){
}
Cat.prototype = new Animal();//主要代码 `子类的原型指向父类实例`
Cat.prototype.name = 'cat';
// 实例化一个猫
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
// 再次实例化一个猫
var kitty = new Cat();
console.log(kitty.data);//[1];
cat.data.push(2)//实例一 往父级data属性添加
console.log(kitty.data);//[1,2]
特点:
1.非常纯粹的继承关系,实例是子类的实例,也是父类的实例
2.父类新增原型方法/原型属性,子类都能访问到
3.简单,易于实现
缺点:
1.要想为子类新增属性和方法,必须要在new Animal()
这样的语句之后执行,不能放到构造器中
2.继承单一 子类原型只能指向一个父类,无法实现多继承
3.所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
4.无法向父类传参
总结:最大缺陷3、4
二 、借用构造函数继承
核心:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
function Cat(name){
Animal.call(this); //主要代码 `调用父类并把this指向子类`
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
特点:
1.解决子类实例共享父类引用属性的问题
2.创建子类实例时,可以向父类传递参数
3.可以实现多继承(call多个父类对象)
缺点:
1.实例并不是父类的实例,只是子类的实例
2.只能继承父类的实例属性和方法,不能继承原型属性
/方法
3.每个新实例都有父类构造函数的副本,臃肿,影响性能
总结:缺点3
三 、组合继承 (常用)
核心:组合原型链继承和构造函数继承
function Cat(name){
Animal.call(this); //借用构造函数模式
this.name = name || 'Tom';
}
Cat.prototype = new Animal(); //原型链继承
var cat = new Cat();
console.log(cat.name); //tom; 子类属性
console.log(cat.sleep()); //tom正在睡觉!; 继承构造函数方法
console.log(cat.eat('饭'));//tom正在饭; 继承父类原型的属性
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
特点:
1.可以继承父类原型上的属性,可以传参,可复用。
2.每个新实例引入的构造函数属性是私有的。
缺点:
调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
四、寄生组合继承 (常用)
核心: 通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Cat(name){
Animal.call(this); //借用构造函数模式继承
this.name = name || 'Tom';
}
//借用函数容器 砍掉父类的实例属性 输出对象和承载继承的原型
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
特点:
1.可以继承父类原型上的属性,可以传参,可复用。
2.每个新实例引入的构造函数属性是私有的。
3.避免了调用了两次父类构造函数
三、补充点Es6
class 开始定义类
constructor 构造函数
static 静态方法
class Child extends Parent 继承
super 传参给父类 或者调用父类的静态方法
//父类
class Parent{
constructor(place = "china"){
this.from=place;
}
//定义静态方法
static tell(){
console.log('tell'); //`不会被实例继承,而是直接通过类来调用 或者 从`super`对象上调用。
}
}
//继承
class Child extends Parent{
//构造函数
constructor(name,birth,age){
this.args = ["hello","world"];
this.name = name;
this.age = age;
super("USA"); //子类向父类传递参数
}
//取值函数(getter)和存值函数(setter)
get age(){
return this.age;
}
set age(val){
this.age = val;
}
//原型方法方法
goSchool(str){
console.log(this.name+'迫不及待想要上学'+str);
}
//加* 方法是一个 Generator 函数
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}
补充 call apply 与 bind
call(), apply() 和 bind()这三个函数都是用来完成函数调用, 并且设置this指向。
call()和apply() 会立即调用函数 , apply第二个参数接受一个数组
bind() 不会立即调用, 而是返回了一个函数的拷贝。 另外还需要触发调用, 另一方面 它在拷贝的地方调用传入的参数也是会传给原始函数的
(1)call( )
var dist = 'Beijing';
function greet(name, hometown) {
var word = `Welcome ${name} from ${hometown} to ${this.dist}!`
console.log(word);
}
var obj1 = {
dist: 'Chengdu'
};
greet.call(obj1, "Tom", "Dallas"); // Welcome Tom from Dallas to Chengdu!
greet("Jerry", "Houston"); // Welcome Jerry from Houston to Beijing!
因为greet.call(obj)传入了obj1作为第一个参数,所以在 greet()函数执行时, this指向 obj1。其余的参数就将作为参数传给greet()函数。
当没有使用call()而直接调用greet()时, this指向 window对象.
(2)apply()
var dist = 'Beijing';
function greet(name, hometown) {
var word = `Welcome ${name} from ${hometown} to ${this.dist}!`
console.log(word);
}
var obj1 = {
dist: 'Chengdu'
};
var args = ["Tom", "Dallas"];
greet.apply(obj1, args); // Welcome Tom from Dallas to Chengdu!
greet("Jerry", "Houston"); // Welcome Jerry from Houston to Beijing!
apply()函数和call()函数非常的相似,第一个参数都用来设置目标函数运行时的this指向。 唯一的区别就是 apply()的第二个参数接受一个数组, 其他表现则一样。
(3)bind( )
var dist = 'Beijing';
function greet(name, hometown) {
var word = `Welcome ${name} from ${hometown} to ${this.dist}!`;
console.log(word);
}
var obj1 = {
dist: 'Chengdu',
};
var obj2 = {
dist: 'Chongqing',
};
var greet1 = greet.bind(obj1, 'Tom', 'Dallas');
var greet2 = greet.bind(obj2, 'Spike', 'San Antonio');
greet('Jerry', 'Houston');
greet1();
setTimeout(function() {
greet2();
}, 1000);
结果输出:
Welcome Jerry from Houston to Beijing!
Welcome Tom from Dallas to Chengdu!
Welcome Spike from San Antonio to Chongqing!
结论:bind()函数同样完成了this会指向bind()的第一个参数。但并不会立即执行目标函数, 而是返回了一个函数的拷贝,其余传给bind()的参数都会按顺序传给返回的函数。我们就可以异步调用这个函数返回值了。
但是需要注意的是,bind()方法返回的函数拷贝在使用 new 操作时, 第一个参数是会被忽略的。
如果在调用返回的函数拷贝的时候, 又传入了新的参数, 会发生什么呢, 只有再写一个例子试试。
var obj1 = {
dist: 'Chengdu',
};
function greet(name, hometown) {
console.log(Array.prototype.slice.call(arguments));
var word = `Welcome ${name} from ${hometown} to ${this.dist}!`;
console.log(word);
}
var greet1 = greet.bind(obj1, 'Tom', 'Dallas');
greet1('Jerry', 'Houston');
输出结果:
[ "Tom", "Dallas", "Jerry", "Houston" ]
Welcome Tom from Dallas to Chengdu!
结论:两个地方传入的参数都会被传给目标函数,函数拷贝调用时传入的参数会追加在bind()函数调用时传入的参数后面。
当然因为原函数只用到两个参数 所以在结果上就只显示了:Welcome Tom from Dallas to Chengdu!
参考资料:
https://www.cnblogs.com/humin...
https://www.cnblogs.com/ranyo...
https://www.jianshu.com/p/a00...
https://juejin.im/post/5c433e...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。