在典型的面向对象语言中(java),都存在类的概念,类是对象的模板,对象是类的实例,但在ES6之前,js中并没有引用类的概念
创建对象可以通过以下三种方式:
var obj={};
var obj=new Object();
把对象的公共部分抽出放在构造函数里面,通过构造函数创建对象
function Star(uname,age){
this.uname=uname;
this.age=age;
this.sing=function(){
console.log('I can sing')
}
}
var hh = new Star('易烊千玺',18); //创建 hh 对象
var cc = new Star('王一博',21); // 创建 cc 对象
hh.sing();
cc.sing();
构造函数:一种特殊的函数,主要用来初始化对象,即为对象成员赋初始值
,总与new
一起使用。把对象中的公共属性和方法抽取出来,然后封装到这个函数里面。
静态成员
:由构造函数本身添加的成员,只能由构造函数本身来访问
实例成员
:在构造函数内部通过this添加的成员,只能由实例化对象来访问
function Star(uname,age){
this.uname=uname;
this.age=age;
this.sing=function(){
console.log('I can sing')
}
}
var hh=new Star('易烊千玺',18); //创建 hh 对象
hh.sing();
console.log(Star.uname);//undefined,不能通过构造函数Star访问
console.log(hh.uname);//易烊千玺,uname为实例成员,只能由实例化对象来访问
Star.sex='男' //静态成员
console.log(Star.sex) //男 ,sex为静态成员,只能由构造函数Star本身来访问
console.log(hh.sex) // undefined
以上代码的实例成员
有:uname,age,sing,静态成员:sex
构造函数存在问题: 每实例化一次对象,就会在内存中创建一个内存空间,存在浪费内存的问题。
console.log(ldh.sing===zxy.sing)//false,内存地址不一样,方法代码一样,还在内存中各自占用一个内存空间,存在浪费
怎么实现所有对象使用同一个函数,从而节省内存空间呢?
构造函数通过原型prototype分配的函数是所有对象所共享
的。
JS中规定,每一个构造函数都有一个prototype属性
,指向一个对象,prototype就是一个对象,这个对象的所有属性与方法,都会被构造函数所拥有。
可以把不变的方法,直接定义在prototype对象上。这样所有对象的实例就可以共享这些方法了。
function Star(uname,age){
this.uname=uname;
this.age=age;
//this.sing=function(){
// console.log('I can sing')
//}
}
Star.prototype.sing =function(){
console.log('I can sing')
}
var hh=new Star('易烊千玺',18); //创建 hh 对象
var cc=new Star('王一博',21); // 创建 cc 对象
hh.sing();
cc.sing();
答:原型是每一个构造函数里都有的一个对象,所以prototype
也称为原型对象
问题2:原型的作用是什么?
答:使用原型分配函数可以使,所有对象的实例能够共享方法,节省内存空间。
总结:
构造函数中的公共属性定义到构造函数里面,而公共方法
则放到构造函数的原型对象prototype身上
。
function Star(uname,age){
this.uname=uname;
this.age=age;
}
Star.prototype.sing =function(){
console.log('I can sing')
}
var hh=new Star('易烊千玺',18); //对象身上有一个原型__proto__,指向构造函数Star的原型对象prototype
hh.sing();
为什么实例对象 hh、cc本身没有sing()
方法,也能调用Star的原型对象中的sing()方法
呢?
console.log(hh.__proto__===Star.prototype) //true,对象原型__proto__与构造函数Star的原型对象prototype是等价的
总结:对象都会有一个属性__proto__
指向构造函数
的原型对象prototype
,之所以我们对象可以使用构造函数prototype
原型对象的属性和方法,就是因为对象有(两个下划线)__proto__
原型的存在。
hh.sing();
方法查找规则:先查找hh对象身上有没有,有就执行对象hh身上的sing()
,
如果对象hh本身没有,去__proto__
指向的构造函数原型对象prototype
身上找sing()
方法。
对象原型__proto__
与构造函数(prototype
)原型对象里都有一个属性constructor
,我们称之为 构造函数
,因为它指向构造函数本身
。
constructor 作用:记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数
(Star)。
下面第二种赋值,直接覆盖了Star.prototype对象,所以其constructor 指向的是一个Object,如果我们修改了原来的原型对象,给原型对象赋值一个Object,则需要手动利用constructor 指向原来的构造函数。
Star.prototype.sing =function(){
console.log('I can sing')
}
constructor
指向原来的构造函数Star。
Star.prototype={
constructor:Star,//没有这一步,constructor指向的是一个Object
sing:function(){
console.log('I can sing')
},
eat:function(){
console.log('I can eat')
}
}
prototype
关系prototype
,通过Star.prototype
指向原型对象的;prototype
里有一个属性叫constructor
,Star.prototype.constructor
指回向构造函数new
构造函数来创建 实例对象
prototype
关系__proto__
原型,指向构造函数的原型对象prototype
,它是对象独有的。__proto__
原型中有一个属性constructor
,通过原型对象Star.prototype
指向Star 构造函数
function Star(uname,age){
this.uname=uname;
this.age=age;
}
Star.prototype.sing =function(){
console.log('I can sing')
}
var hh=new Star('易烊千玺',18); //对象身上有一个原型__proto__,指向构造函数Star的原型对象prototype
hh.sing()
console.log(Star.prototype.__proto__===Object.prototype);//true
console.log(Object.prototype.__proto__===null);//true
1、只要是对象就有__proto__
原型,指向原型对象
2、构造函数Star的原型对象prototype
里的__proto__
原型(Star.prototype.__proto__
)指向Object原型对象(Object.prototype
)
3、Object 构造函数创建原型对象(Object.prototype
),Object原型对象.constructor(Object.prototype.constructor
)指向 Object构造函数
4、Object.prototype
原型对象里的__proto__
原型指向null
我们发现每个对象都有__proto__
原型,__proto__
原型又指向原型对象prototype
,形成了一条原型链,当查找成员变量时,会一层一层往上找,如果查找完全部都找不到,就会显示null
总结:JavaScript 查找成员机制(规则)
问题1: 原型是什么?
答:原型是每一个构造函数里都有的一个对象,所以prototype
也称为原型对象
问题2: 原型的作用是什么?
答:使用原型分配函数可以使,所有对象的实例能够共享方法,节省内存空间。
问题3: 什么是原型链?
function Star(uname,age){
this.uname=uname;
this.age=age;
}
var that;
Star.prototype.sing =function(){
console.log('I can sing')
that=this;
}
var hh=new Star('易烊千玺',18);
hh.sing();
console.log(that===hh) //true
1、在构造函数中,里面this
指向的是实例对象 hh
2、原型对象函数里面的this
指向的是函数调用者,这里是 实例对象 hh
可以通过原型对象,对原来的内置对象进行拓展自定义的方法,比如给数组增加自定义求偶数和的功能
Array.prototype.sum=function(){
var sum=0;
for(let i = 0; i < this.length ; i++){
sum+=this[i];
}
return sum;
}
var arr=new Array(1,2,3)
arr.sum() //6
//打印 Array.prototype
不要通过下面这种方法添加方法,这样会覆盖掉原型原有的方法,constructor指向也不一样了
// Array.prototype={
// sum:function(){
// var sum=0;
// for(let i = 0; i < this.length ; i++){
// sum+=this[i];
// }
// return sum;
// }
//}
ES6 之前没有提供 extends
继承。我们可以通过构造函数+原型对象
模拟实现继承,被称之为组合继承
语法:func.call(thisArg, arg1, arg2, ...argN)
参数 | 含义 |
---|---|
thisArg |
this 的指向对象 |
arg1, arg2, ...argN |
为调用func函数需要传入的参数,全都用逗号分隔,直接放到后面 |
作用:调用 func 函数
,并改变
函数运行时的this指向
例子:
function fn(x,y){
console.log(this);
console.log(x+y);
}
var o={
name:'sam'
}
fn.call(o,3,8)
语法:func.apply(thisArg, [argsArray])
参数 | 含义 |
---|---|
thisArg |
必选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。 |
[argsArray] |
可选的。但是必须是一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。 |
作用:apply()
方法调用一个参数为数组(或类似数组对象)
,给定this值的函数。
例子:
function fn(x,y){
console.log(this);
console.log(x+y);
}
var o={
name:'sam'
}
fn.apply(o,[3,8]) //输出 {name:'sam'} 11
fn.apply(o,3,8) //报错 ,因为第二个参数不是数组或类数组
var arr=[1,54,78,3,6,85,100]
Math.max.apply(Math,arr); //不改变this指向,求数组最大值
//100
语法:let boundFunc = func.bind(thisArg, arg1, arg2, ...argN)
参数 | 含义 |
---|---|
thisArg |
this 的指向对象 |
arg1, arg2, ...argN |
调用func函数需要传入的参数,全都用逗号分隔,直接放到后面,返回一个函数(闭包),函数延迟执行,需要调用才执行 |
return | 返回由指定的this值和初始化参数改造后的原函数拷贝 ,相当于返回一个新的函数 |
作用:bind()不会调用函数,但是能改变函数内部的this指向。 如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向此时用bind()
例子:
function fn(x,y){
console.log(this);
console.log(x+y);
}
var o={
name:'sam'
}
fn.bind(o,3,8) // 输出fn 函数,不会执行
fn.bind(o,3,8)() //执行 fn 函数, 输出 {name:'sam'} 11
实例: 有一个按钮,当我们点击之后,禁用按钮,3秒钟后开启按钮
var btn =document.querySelector('button');
btn.onclick=function(){
this.disabled=true; //这个this 指向btn 按钮
console.log(this);//定时器函数里面的 this 指向 btn
//setTimeout(function(){
// console.log(this);//定时器函数里面的 this 指向window
//},3000)
setTimeout(function(){
console.log(this);//加了bind(),定时器函数里面的 this 指向 btn
}.bind(this),3000) //this 在定时器外面,指向的是btn对象 ,将btn对象绑定到定时器上
}
}
多个按钮
改变定时器中的this指向,原来是指向Window,改变之后指向btn ,而且不会立即调用函数
var obj={
name:'张三',
age:18,
myFun:function(fm,to){
console.log(this.name+" 年龄"+this.age,fm + "去往" + to);
}
}
var db={
name:'德玛',
age:99
}
obj.myFun.call(db,'成都','上海'); //德玛 年龄99 成都去往上海
obj.myFun.apply(db,['成都','上海']); //德玛 年龄99 成都去往上海
obj.myFun.bind(db,'成都','上海'); // 返回一个函数,这里就是 bind 和 call/apply 的区别之一,bind 的时候不会立即执行
obj.myFun.bind(db,'成都','上海')(); //德玛 年龄99 成都去往上海
obj.myFun.bind(db,['成都','上海'])(); //德玛 年龄99 成都,上海去往undefined
相同点
1、都可以改变上下文 this 指向的,第一个参数都是 this 的指向对象
2、三者的参数不限定是string类型,允许是各种类型,包括函数 、 object
不同点
1、call()
、apply()
会调用函数,并且改变函数内部this指向。
2、传递参数形式不同,call()
后面的参数全都用逗号分隔 ,apply()
后面的参数必须传一个数组
obj.myFun.call(db,'成都', ... ,'string' );
obj.myFun.apply(db,['成都', ..., 'string' ]);
3、bind()
不会调用函数,除了返回的是原函数改变this之外产生的新函数
以外,它的参数和call()
一样。如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向此时用bind()
方法
4、应用对象不同,call()
、apply()
是直接使用在函数上,而 bind()
绑定 this 后返回执行上下文被改变的函数(闭包),不会立即执行,需要()
才会执行。
5、主要应用场景不同,call()
常用在继承;apply()
经常与数组有关,比如借助于数学对象实现数组最大值最小值;bind()
不调用函数,但是还想改变this指向。比如改变定时器内部的this指向。
Array.prototype.sum=function(){
var sum=0;
for(let i = 0; i < this.length ; i++){
sum+=this[i];
}
return sum;
}
var arr=new Array(1,2,3)
arr.sum() //6
//打印 Array.prototype
核心原理:通过call()把父类型的this
指向子类型的this
,这样就实现子类型继承父类型的属性。
function Father(uname,age){、
//this指向父构造函数 的对象实例
this.name=uname;
this.age=age;
}
function Son(uname,age,score){
//this指向 子构造函数 的对象实例
Father.call(this,uname,age) //改变this指向
this.score=score; //儿子有自己的属性
}
var son =new Son('易烊千玺',25)
console.log(son)
function Father(uname,age){
//this指向父构造函数 的对象实例
this.name=uname;
this.age=age;
}
Father.prototype.money=function(){
console.log("1000元")
}
function Son(uname,age,score){
//this指向 子构造函数 的对象实例
Father.call(this,uname,age) //改变this指向
this.score=score; //儿子有自己的属性
}
// Son.prototype=Father.prototype //不能这样直接赋值,如果修改了子原型对象(添加exam 方法),父原型对象也会改变
Son.prototype=new Father(); //相当于= 新的对象 {}
// 如果利用对象形式修改 原型对象,别忘了利用constructor 指回 原来的构造函数
Son.prototype.constructor = Son; //如果不改回来,Son.prototype.constructor 指向的就是Father构造函数了。
Son.prototype.exam=function(){
console.log("只有儿砸才要考试哦!");
}
var son =new Son('易烊千玺',18,100)
console.log(son)
console.log(Father.prototype)
第一种:Son.prototype=Father.prototype
这样直接赋值会有问题,如果修改了子原型对象(添加exam 方法),父原型对象也会改变(父亲也添加了exam),所以不能这样直接赋值
第二种:
Son.prototype=new Father()
Son.prototype.constructor = Son;
1、通过创建一个父实例对象,让子原型对象指向该父实例对象,这样即实现了继承父构造函数的money方法,同时修改子原型对象(添加exam 方法)也不会直接修改父原型对象,也能创建属于子构造函数的方法
2、如果利用对象形式修改 原型对象,利用 constructor 指回 原来的构造函数(Son构造函数),如果不改回来,就会出现如下:
参考链接:
阮一峰:ECMAScript 6 入门