JavaScript prototype 使用介绍
用过JavaScript的同学们肯定都对prototype如雷贯耳,但是这究竟是个什么东西却让初学者莫衷一是,只知道函数都会有一个prototype属性,可以为其添加函数供实例访问,其它的就不清楚了,最近看了一些 JavaScript高级程序设计,终于揭开了其神秘面纱。
每个函数都有一个prototype属性,这个属性是指向一个对象的引用,这个对象称为原型对象,原型对象包含函数实例共享的方法和属性,也就是说将函数用作构造函数调用(使用new操作符调用)的时候,新创建的对象会从原型对象上继承属性和方法
私有变量、函数
在具体说prototype前说几个相关的东东,可以更好的理解prototype的设计意图。
之前写的一篇JavaScript 命名空间文章中提到过JavaScript的函数作用域,
在函数内定义的变量和函数如果不对外提供接口,那么外部将无法访问到,
也就是变为私有变量和私有函数。
function Obj(){
var a=0; //私有变量
var fn=function(){ //私有函数
}
}
这样在函数对象Obj外部无法访问变量a和函数fn,它们就变成私有的,
只能在Obj内部使用,即使是函数Obj的实例仍然无法访问这些变量和函数
复制代码 代码如下:
var o=new Obj();
console.log(o.a); //undefined
console.log(o.fn); //undefined
静态变量、函数
当定义一个函数后通过 “.”为其添加的属性和函数,通过对象本身仍然可以访问得到,
但是其实例却访问不到,这样的变量和函数分别被称为静态变量和静态函数,
用过Java、C#的同学很好理解静态的含义。
复制代码 代码如下:
function Obj(){
}
Obj.a=0; //静态变量
Obj.fn=function(){ //静态函数
}
console.log(Obj.a); //0
console.log(typeof Obj.fn); //function
var o=new Obj();
console.log(o.a); //undefined
console.log(typeof o.fn); //undefined
实例变量、函数
在面向对象编程中除了一些库函数我们还是希望在对象定义的时候同时定义一些属性和方法,
实例化后可以访问,JavaScript也能做到这样
function Obj(){
this.a=[]; //实例变量
this.fn=function(){ //实例方法
}
}
console.log(typeof Obj.a); //undefined
console.log(typeof Obj.fn); //undefined
var o=new Obj();
console.log(typeof o.a); //object
console.log(typeof o.fn); //function
这样可以达到上述目的,然而
复制代码 代码如下:
function Obj(){
this.a=[]; //实例变量
this.fn=function(){ //实例方法
}
}
var o1=new Obj();
o1.a.push(1);
o1.fn={};
console.log(o1.a); //[1]
console.log(typeof o1.fn); //object
var o2=new Obj();
console.log(o2.a); //[]
console.log(typeof o2.fn); //function
上面的代码运行结果完全符合预期,但同时也说明一个问题,在o1中修改了a和fn,
而在o2中没有改变,由于数组和函数都是对象,是引用类型,
这就说明o1中的属性和方法与o2中的属性与方法虽然同名但却不是一个引用,
而是对Obj对象定义的属性和方法的一个复制。
这个对属性来说没有什么问题,但是对于方法来说问题就很大了,
因为方法都是在做完全一样的功能,但是却又两份复制,如果一个函数对象有上千和实例方法,
那么它的每个实例都要保持一份上千个方法的复制,这显然是不科学的,这可肿么办呢,prototype应运而生。
prototype
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,
默认情况下prototype属性会默认获得一个constructor(构造函数)属性,
这个属性是一个指向prototype属性所在函数的指针,有些绕了啊,写代码、上图!
复制代码 代码如下:
function Person(){
}
let Person = new Person();
image
根据上图可以看出Person对象会自动获得prototyp属性,而prototype也是一个对象,会自动获得一个constructor属性,该属性正是指向Person对象。
当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(很多浏览器这个指针名字为__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,
而不是实例与构造函数之间。
复制代码 代码如下:
function Person(name){
this.name=name;
}
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');
关于this矫正
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
</body>
<script>
// 小案例
let a = {
username: "张三",
fn(){
console.log(this.username)
}
}
a.fn() //张三
let b = a.fn
b() // undefined
b.call(a) // 张三
/**
* @description: b.call(a) 给第一个参数添加要把b添加到哪一个环境中 就是this就会指向这个对象
* @param : 可以传递多个参数
* @return:
*/
let p = {
name: "lisi",
showName(a,aa){
console.log(this.name)
console.log(a+aa)
}
}
let p1 = p.showName
p1.call(p,2,5)
console.log("------------------------------------------------")
/**
* @description: apply方法和call方法有些相似,它也可以改变this的指向
* @param : apply同样也是可以传递多个参数的 但是第二个参数必须是数组
* @return:
*/
let a1 = {
user: "小曾",
showUser(e,ee){
console.log(this.user)
console.log(e+ee)
}
}
let b1 = a1.showUser
b1() //undefined
b1.apply(a1,[10,100]) // 小曾 110
/*这边要注意的是如果call 还有apply第一参数写的是null那么this指向的是window*/
let a2 ={
user: "王五",
showUsers(){
console.log(this)//Window {external: Object, chrome: Object, document: document, a: Object, speechSynthesis:
}
}
let b2 = a2.showUsers
b2.apply(null)
/**
* @description: bind方法和call、apply方法有些不同,但是不管怎么说它们都可以用来改变this的指向。
bind方法返回的是一个修改过后的函数
* @param : 同样bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。
* @return:
*/
let a3 = {
user: "鲁智深",
showUserss(a,b,c,d){
console.log(this.user)
console.log(a,b,c,d)
}
}
let b3 = a3.showUserss
let c3 = b3.bind(a3,121212)
console.log(c3)/*ƒ showUserss(){
console.log(this.user)
}*/
c3(1111,2222,3333) // 121212 1111 2222 3333
/**总结
* @description:
总结:call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,
并且可以将参数在执行的时候添加,这是它们的区别,根据自己的实际情况来选择使用。
* @param :
* @return:
*/
</script>
</html>