1、call,apply和bind
相同点:
都可以把一个函数应用到其他对象上;
都是用来改变函数的this对象的指向的;
第一个参数都是this要指向的对象;
都可以利用后续参数传参。
不同点:
call,apply是修改函数的作用域,即修改this指向,并且立即执行,
而bind是返回了一个新的函数,不是立即执行;
而call和apply的区别是call接受逗号分隔的无限多个参数列表,apply则是接受数组作为参数。
var xw = {
name: "小王",
gender: "男",
age: 24,
say: function() {
console.log(this.name + " , " + this.gender + " ,今年" + this.age);
}
}
var xh = {
name: "小红",
gender: "女",
age: 18
}
xw.say();//小王 , 男 ,今年24
xw.say.call(xh)//小红 , 女 ,今年18
xw.say.apply(xh)//小红 , 女 ,今年18
xw.say.bind(xh)()//小红 , 女 ,今年18
接受参数:
var a = {
name: "小王",
gender: "男",
age: 24,
say: function(school, grade) {
console.log(this.name + " , " + this.gender + " ,今年" + this.age + " ,在" + school + "上" + grade);
}
}
var b = {
name: "小红",
gender: "女",
age: 18
}
a.say.call(b, "实验小学", "六年级");
a.say.apply(b, ['第一中学', '初一'])
// bind返回一个函数,调用的时候传参
a.say.bind(b, "高级中学", "高三")();
2、new一个对象发生了什么
• 创建一个空对象,将它的引用赋给 this,继承函数的原型。
• 通过 this 将属性和方法添加至这个对象
• 最后返回 this 指向的新对象,也就是实例(如果没有手动返回其他的对象)
重学前端专栏中,看到了比较符合我心意的,同时也是符合原理的描述:
• 以构造器的prototype属性为原型,创建新对象;
• 将this(也就是上一句中的新对象)和调用参数传给构造器,执行;
• 如果构造器没有手动返回对象,则返回第一步创建的新对象,如果有,则舍弃掉第一步创建的新对象,返回手动return的对象。
new过程中会新建对象,此对象会继承构造器的原型与原型上的属性,最后它会被作为实例返回这样一个过程。知道了原理,我们来手动实现一个简单的new方法。
// 构造器函数
let Parent = function (name, age) {
this.name = name;
this.age = age;
};
Parent.prototype.sayName = function () {
console.log(this.name);
};
//自己定义的new方法
let newMethod = function (Parent, ...rest) {
// 1.以构造器的prototype属性为原型,创建新对象;
let child = Object.create(Parent.prototype);
// 2.将this和调用参数传给构造器执行
let result = Parent.apply(child, rest);
// 3.如果构造器没有手动返回对象,则返回第一步的对象
return typeof result === 'object' ? result : child;
};
//创建实例,将构造函数Parent与形参作为参数传入
const child = newMethod(Parent, 'echo', 26);
child.sayName() //'echo';
//最后检验,与使用new的效果相同
child instanceof Parent//true
child.hasOwnProperty('name')//true
child.hasOwnProperty('age')//true
child.hasOwnProperty('sayName')//false
扩展:形参。。。
https://www.cnblogs.com/echolun/p/10668186.html
3、原型原型链
构造函数上的方法
- 在构造函数上直接定义方法(不共享)
function Person() {
this.say = function () {
console.log('hello');
}
}
let p1 = new Person();
let p2 = new Person();
p1.say(); // hello
p2.say(); // hello
console.log(p1.say === p2.say); // false
很明显,p1 和 p2 指向的不是一个地方。 所以 在构造函数上通过this来添加方法的方式来生成实例,每次生成实例,都是新开辟一个内存空间存方法。这样会导致内存的极大浪费,从而影响性能。
.
- 通过原型添加方法(共享)
构造函数通过原型分配的函数,是所有对象共享的。
function Person(name) {
this.name = name;
}
Person.prototype.say = function () {
console.log('hello ' + this.name);
}
let p1 = new Person('张三');
let p2 = new Person('李四');
p1.say(); // hello 张三
p2.say(); // hello 李四
console.log(p1.say === p2.say); // true
所以我们经常 将公共属性定义到构造函数里,将公共方法放到原型对象上 。
要点
1、所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。
2、所有的引用类型都有一个’_ _ proto_ _
'属性(也叫隐式原型,它是一个普通的对象)。
3、所有的函数都有一个’prototype
’属性(这也叫显式原型,它也是一个普通的对象)。
4、所有引用类型,它的’_ _ proto_ _
’属性指向它的构造函数的’prototype’
属性。
5、当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’_ _ proto_ _
'属性(也就是它的构造函数的’prototype’
属性)中去寻找。
【当对象属性不存在就会去他隐原型找,但是隐原型指向了构造函数的显示原型,所以去构造函数的显式原型中找】
原型
先来看一个原型的例子。
//这是一个构造函数
function Foo(name,age){
this.name=name;
this.age=age;
}
/*根据要点3,所有的函数都有一个prototype属性,这个属性是一个对象
再根据要点1,所有的对象可以自由扩展属性
于是就有了以下写法*/
Foo.prototype={
// prototype对象里面又有其他的属性
showName:function(){
console.log("I'm "+this.name);//this是什么要看执行的时候谁调用了这个函数
},
showAge:function(){
console.log("And I'm "+this.age);//this是什么要看执行的时候谁调用了这个函数
}
}
var fn=new Foo('小明',19)
/*当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它
构造函数的'prototype'属性中去找*/
fn.showName(); //I'm 小明
fn.showAge(); //And I'm 19
这就是原型,很好理解。那为什么要使用原型呢?
试想如果我们要通过Foo()来创建很多很多个对象,如果我们是这样子写的话: