(四)Javascript从原型至对象枚举
原型
-
定义:
原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
//Person函数是对象 //Person.prototype -- 原型(在被构造函数定义时就产生了) //Person.prototype = {} 是祖先(它也是一个对象) Person.prototype.LastName = "Wei";//原型的一个属性 Person.prototype.say = function(){ console.log("hehe"); }//原型的一个方法 function Person(){ //Person中LastName和say,但是继承了祖先的属性和方法 } var person = new Person(); var person1 = new Person(); //person 和 person1 都继承了该原型的属性和方法 console.log(person.LastName);//得到"Wei" function Person(name,age){ this.name = name; this.age = age; } var person = new Person("junjie",19); //一个正常的对象有自己的属性和方法也有继承而来的属性和方法 //原型的一般用法:转移构造函数中一些固定的属性和方法到原型中,减小耦合
-
利用原型特点和概念,可以提取共有属性。
-
对象如何查看原型–>隐式属性 __proto__
Car.prototype = { LastName : "WEI", } function Car(name){ this.name = name; } var car = new Car("junjie"); console.log(car.__proto__); //结果是原型的对象,点开里面有constructer:f Car(name)和__proto__ //可以通过命明规则来说明自己私人的属性,比如 var _pravate=~; //过程:在用new构造对象的时候,发生下面过程 //在Car 里面 var this = { __proto__ : Person.prototype}; //这里的作用相当于连接原型和构造函数 //每一个新产生的car中都有__proto__属性来放原型 //在查找属性LastName时,car会先在Car中查找,然后顺着__proto__再在Car.prototype中查找 //同样可以用于修改, car.__proto__ = obj{LastName : "Wang"}
注意
Person.prototype.name = "sunny"; function Person(){ //var this = {__proto__:Person.prototype} } var person = new Person(); Person.prototype = { name : "cherry" } console.log(person.name); //注意这里得到的结果是"sunny"而不是"cherry" //因为在声明新对象person的时候,它的__proto__连接的是原来的Person.prototype /* 过程理解大致如下: 首先是预编译,提升变量person和function Person 开始执行。先给Person的原型增加新属性name : "sunny" 然后person = new Person(),person变成一个对象 person.__proto__里面的存的是 obj={name:"sunny",constructor:Person()} 下一步执行Person.prototype={name:"cherry"} 因为person的proto属性早就被定义好了 所以这里的修改该的是原型的属性,而不是person中的__proto__, person在自身属性找不到name时,会在__proto中查找; 简化流程: Person.prototype = {name : "sunny"} person.__proto__ = Person.prototype; Person.prototype = {name : "cherry"} */ //注意顺序会影响结果,如果将6~8行代码,放在peoson = new Person()之前 //结果就是"cheery"
-
独立生成对象如何查看对象的构造函数 --> constructor
function Car(){ } var car = new Car(); console.log(car.constructor); //这里的结果会显示构造函数,说明原型中有这个constructor属性,这是系统自动加的 /* 原型中Car.prototype = { constructor:Car(), _proto_:Object } */ //这时候可以改变constructor指向 //一种方式:Car.prototype.constructor = Person; //另一种方式:Car.prototype = {constructor : Person} console.log(car.constructor); //得到的是 Person(){}
原型链
-
如何构成原型链?
- 概念的引入,在用__proto__查看对象的原型时,我们发现原型中也有属性__proto__
//原型的__proto__属性,Grand.prototype.__proro__等于Object.prototype
//Object.prototype是所有对象的最终原型
//访问Object.prototype.__proto__得到null,说明这是终端
Grand.prototype.lastName = "Deng";
function Grand(){
this.name = "daming";
}
var grand = new Grand();
Father.prototype = grand;//grand是构造函数Father的原型
function Father(){
this.name = "xiaoming";
}
var father = new Father();
function Son(){
this.name = "xiaoxiaoming";
}
Son.prototype = father;//father是构造函数Son的原型
var son = new Son();
console.log(son.lastName);
/*这里通过原型链访问,son-->son.__proto__(即father)
-->father.__proto__(即grand)-->grand.__proto__,lastName
有点类似作用域链
-
原型链上属性的增删改查
-
一般子不能修改父,因为只会在自身上面增加新属性
//沿用上例 function Father(){ this.name = "xiaoming"; fortune = { card1 : "visa", card2 : "master" } } //访问son.fortune可以看到父元素的fortune对象 son.fortune = {card1:"mi",card2:"huawei"}; console.log(father.fortune);//这里father中的fortune对象没改 //变的是son中增加了对象fortune,里面含两个属性 //但是以下方式可修改(忽略第10 11行代码) son.fortune.card2 = "mi"; //因为fortune后面跟属性,可以说forture是引用值 //这里相当于son访问的是引用值fortune的card2这个原始值 //沿用上例 function Father(){ this.num = 100; } son.num++; //结果是son中增加了一个新属性num 值是101,而father中没有变
-
this的归属问题
Person.prototype = { name : "a", sayName : function(){ console.log(this.name); } } function Person(){ name : "b"; } var p1 = new Person; p1.name = "c"; console.log(p1.sayName()); //这里的检索过程应该是 //p1-->p1.__proto__(即Person.prototype) //找到函数体后执行,this.name //这时候的this指的是p1,因为是p1过来调用的 //所以结果是"c",如果没有第37行代码,结果就是"b" //因为name = "b" 是所有新造对象都共有的
-
-
绝大多数对象的最终都会继承自身Object.prototype
var obj1={}//这是一个自变量对象的创建
//其实质是调用了系统函数即 var ovj1 = new Object();
//但一般都不用调用系统函数的写法。
//obj1.__proto__ ----> Object.prototype
//绝大多数对象的最终都会继承自身Object.prototype
//例外(联系下面的Object.create())
- Object.create(原型);
//var obj = Object.create(原型);
var obj = {name : "sunny" , age : 123};
var obj1 = Object.create(obj);
//obj1是一个对象,它的原型是obj
//利用create将对象的构造函数的原型中的属性提给对象
Person.prototype.name = "sunny";
function Person(){
}
var person = Object.create(Person.phototype);
//Object.create()中的括号不能不填,不填会报错
//报错说里面必须是 对象(可以是数组) 或者 null
//如果填入了 null,这是构造出来的对象是没有原型的
var person = Object.create(null);
console.log(person.toString());
//toSpring是Object.prototype中的一个方法,是显示类型转换的系统函数
//这里报错会说没有
person.__proto__ = Object.prototype;
console.log(person.toString());
//这里人为的加入Object.prototype是没有用的.
toString拓展
console.log(ture.toString());
//得到"ture"
console.log(123.toString());
//这种调用是错误的,因为123.中的“.”会当成浮点型
//要掉用需要一个变量存123
方法的的重写
var num = 123;
console.log(num.toString());
//-->因为num是原始值,所以会用到包装类 new Number(num).toString();
/*
Number.prototype.toString = function(){
//这里是Object中的方法的 "重写"
//Number的原型中是自带toString的,这里不是Object的toString
return "hehe";
}
*/
Number.prototype.toString = function(){
return "h1h1";
}
var k = 123;
console.log(k.toString());
//此处返回了h1h1
call/apply
-
作用 :
改变this指向
function Person(name,age){ this.name = name; this.age = age; } var person = new Person("deng",100); var obj = {} //一般来说执行一个函数test()相当于teat.call(),call是一个方法 Person.call(obj,"cheng",300); //这里让Person中所有的this都变成obj,同时传入参数 /*得到 obj = {name:"cheng",age=300} //实质:利用call来给obj按Person工厂来制作属性
实现过程(借用别人函数实现自己功能)
function Person(name,age,sex){ this.name = name; this.age = age; this.sex = sex; } function Student(name,age,sex,tel,grade){ this.name = name; this.age = age; this.sex = sex; this.tel = tel; this.grade = grade; } var student = new Student("wei",19,"male",137,2020); /* 用法: 将第7~9行变为 Person.call(student,name,age,sex); 但是上面这个具有唯一性,只针对student 所以要改为 Person.call(this,name,age,sex); */ function Student(name,age,sex,tel,grade){ Person.call(this,name,age,sex); this.tel = tel; this.grade = grade; }
apply和call区别不大
-
区别
后面传的参数形式不同 。
call 需要把实参按照形参的个数传进去
apply 只能传一个参数,是数组
Person.apply(this,[name,age,sex]);
总结:
两者功能相同,都是改变this指向,但传参列表不同。
继承模式
-
传统继承–>原型链
过多的继承了没用的属性
//参考上方原型链中的祖先 父亲 后代
-
借用构造函数(沿用上例call)
-
不能继承借用构造函数的原型
Student的原型仍然是Student.prototype
-
每次构造函数都要多走一个函数(操作上和运行上一般般)
因为call每次构造函数都要多走一次Person函数
-
-
原型共享(比较好)
Father.prototype.lastName = "Deng"; function Father(){ } function Son(){ } Son.prototype = Father.prototype; var son = new Son(); var father = new Father(); //封装一个函数实现继承 //继承的两中语法 //function extend(){} function inherit(Target,Origin){ Target.prototype=Origin.prototype; } //表示Target继承至Origin,传进来的应该是构造函数 /* 这里要注意顺序的问题: 要先执行封装函数,再赋值son= new Son(); 否则不生效 */
- 不能随意改动自己的原型(改变会影响他者的原型)
-
圣杯模式
(方法仍然是公有原型)
Father.prototype.lastName = "Deng"; function Father(){ } function C(){ } function Son(){ } C.prototype = Father.prototype; //Father.prototype是Father和C的公有原型 Son.prototype = new C(); //此处通过原型链的方式来形成关系,Son可通过原型链爬到Father.prototype //当增加Son.prototype.qq增加到的是C中 /*封装函数: function inherit(Target,Origin){ function F(){}; F.prototype = Origin.prototype; Son.prototype = new F(); Target.prototype.constructor = Target; //修正信息用 Target.prototype.uber = Origin.prototype; //此处用于说明继承的对象是谁 } */ //高端的写法 /* var inherit = ( function (){ var F = function(){}; return function(targer,Origin){ F.prototype = Origin.prototype; Son.prototype = new F(); Target.prototype.constructor = Target; Target.prototype.uber = Origin.prototype; 这里是利用了闭包封装私有化的思想,F函数体存于圣杯函数 的作用域中使用,成为圣杯函数的私有化变量,因为F是隐式的 附加的东西,所以这是一个很好的方法 } } ()); */
命明空间
-
管理变量,防止污染全局,适用于模块化开发
-
一个文件里面声明的变量可能与另一个文件中变量相同,导致覆盖。4
解决办法:命明空间
<script type="text/javascript"> var org = { departmeng1:{ jicheng:{ name : "abc", age : 123 }, xvsong:{ } }, departmeng2:{ zhangsan:{ name : "kkk", age : 4 }, xvgao:{ } }, } var jicheng = org.department1.jicheng; //通过闭包实现私有化 //姬成课程13,后面补充 </script>
-
思考问题
-
如何实现链式调用模式 (模仿jquery)
var deng = { smoke : function (){ console.log("Smoking"); } drink : function (){ console.log("drinking"); } perm : function (){ console.log("preming"); } } deng.smoke().drink(); //这里无法连用,因为smoke返回的是undefined //所以只需要在3-4行代码中间加个 return this;这里的this就是deng
-
obj.eat().smoke().drink().eat().sleep();
属性的表示方法
var obj = {
name:"abc"
}
//每次访问obj.name ----> 隐式转化位obj["name"]
//另一种属性表示方法,注意要放"name"不是变量name
//后者更灵活,因为可以利用字符串拼接
var deng = {
wife1 : {name : "xiaoliu"},
wife2 : {name : "xiaozhang"},
wife3 : {name : "xiaohong"},
wife4 : {name : "xiaoqi"},
sayWife : function (num){
return this["wife" + num]
; }
}
对象的枚举(遍历)enumeration
- for in
//目的:遍历一个对象,知道里面的属性值
var obj = {
name : "123",
age : 123,
sex : "male",
height : 180,
weight : 75
}
//forin循环
for(var prop in obj){
console.log(prop + " " + typeof(prop));//都是字符类型
}
//说明检索的属性是字符
//prop相当于一个变量,存属性名,每一圈循环都不一样,prop名字可自定义
var obj = {
name : "123",
age : 123,
sex : "male",
height : 180,
weight : 75,
}
for(var prop in obj){
console.log(obj.prop);
}
//这里得到的都是undefined
//obj.prop ----> obj["prop"]
//说明在obj中检索"prop"这个字符串,这时的prop不是一个变量
var obj = {
name : "123",
age : 123,
sex : "male",
height : 180,
weight : 75,
prop : 234
}
for(var prop in obj){
console.log(obj.prop);
}
//这里得到的六个结果都是234
//更正方法obj.prop改为obj.[prop]
- hasOwnProperty(重要)
var obj = {
name : "123",
age : 123,
sex : "male",
height : 180,
weight : 75,
__proto__ : {
lastName : "deng"
}
}
for(var prop in obj){
console.log(obj.[prop])
}
//这时候也会把原型中的lastName显示出来,但我们的目的是让它不显示
//利用obj.hasOwnProperty(prop)这个方法返回布尔值这个方法来实现
for(var prop in obj){
if(obj.hasOwnProperty(prop)){
console.log(obj.[prop]);
}
}
//prop会延展到原型,但遇到终端Object.prototype时不会遍历它
//但是如果手动设置Object.prototype = {abc:"123"}就会显示
var obj = {
name : "123",
age : 123,
sex : "male",
height : 180,
weight : 75,
__proto__ : {
lastName : "deng"
}
}
Object.prototype.abc = "123";
for(var prop in obj){
if(!obj.hasOwnProperty(prop)){
console.log(obj.[prop]);
}
//这时候得到结果是 "deng" 和 "123"
//小结:prop不会遍历到系统的原型
- in(使用概率极低)
//用来看属性是不是属于对象的,注意前面是要写字符串类型的属性名
"height" in obj
//得到true
//但缺点在于"lastName" in obj得到也是obj
//所以in只能判断这个对象上面能不能访问到这个属性
//并不是判断这个属性属不属于这个对象
-
instanceof(重要,常考)
function Person(){ } var person = new Person(); //A instanceof B //A 是不是 B构造函数构造出来的 /* person instanceof Person 这里得到的是true 但是 person instanceof Object 也是true //数组也是对象,[]是空数组 [] instanceof Array 是true 但是[] instanceof Object 也是true 总结:看A对象的原型链上 有没有 B的原型
它可以解决的一个比较强大的问题
var arr = [] || {} //注意:对象和数组的typeof都是object //现在 如何判断arr是数组还是对象 /*① if(arr instanceof Array){ //这是一个数组 } */ /*② 数组因为也是对象,所以也有constructor console.log([].constructor); 这里得到 function Array{[native code]} 而对象的constructor是function Object{[native code]} */ /*③ 用法:Object.prototype.toString.call([]); 控制台得到[object Array] Object.prototype.toString.call(123); 控制台得到[object Number] Object.prototype.toString.call({}); 控制台得到[object Object] 原因 下面是一个方法 Object.prototype.toString = function(){ //里面有this //谁调用this就是谁 //识别this,返回相应的结果 //这里的this是数组 } */