JS继承模式
ECMAScript不支持接口继承,只支持实现继承
继承就是获取存在对象已有属性和方法的一种方式.简单来说,A对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。
为什么要继承?
1.传统模式(原型链)
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针,让这个原型对象(子的原型)等于要继承的引用类型(父)的实例,由于引用类型(父)的实例包含一个指向(父)原型对象的内部指针,以此类推,层层递进,便构成实例与原型的链条,即原型链。
优点:
将父类的实例作为子类的原型,可以方便的基础父类型的原型中的方法;
缺点:
过多的继承了没有用的属性
只执行一次,无法给属性传值
//父类型
function Person(name,age,sex){
this.name=name;
this.age=age;
this.sex=sex;
}
//子类型
Person.prototype.sayHi=function(){
console.log('大家好,我是'+this.name);
}
function Student(){
this.score=100;
}
Student.prototype= new Person();
Student.prototype.constructor=Student;
var s1=new Student();
say.sayHi()
属性的继承没有意义
注意点:
(1)给原型添加方法一定要放在替换原型的语句之后
(2)在通过原型链实现继承时,不能使用对象字面量创建原型方法
原型链存在的问题:
(1)子类型的所有实例都可以共享父类型的属性
(2)子类型的实例无法在不影响所有对象的情况下,给父类型的构造函数传递参数
2.构造继承(伪造对象或经典继承)
在子类型构造函数的内部调用超类型构造函数(通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数);
优点
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承
- 可以方便的继承父类型的属性,但是无法继承原型中的方法
缺点:
-
实例并不是父类的实例,只是子类的实例
-
只能借用方法,不能借用原型
-
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
Person.prototype.lastname = 'deng';
Person.prototype.hobbit = 'running';
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name,age,sex,tel,grade){
// var this = {name:"",age :"", sex:""}
Person.call(this,name,age,sex);
this.tel = tel;
this.grade = grade;
}
var student = new Student('sunny',123,'male',139,2017);
console.log(Student.hobbit)//undefiend
console.log(Student.lastname)//undefiend
方法都在构造函数中定义,函数复用变得没有意义
1.就是虽然Student()利用了call实现了对Person()功能的继承,可是它不能借用构造函数的原型
2.每当需要用call来实现功能的借用的时候,都要去执行函数一次。
3.共享原型(标准)
函数.prototype = 函数.prototype
不能随便改动自己的原型
Father.prototype.lastName = "Deng";
function Father() {
}
function Son() {
}
Son.prototype = Father.prototype;
var father = new Father();
var son = new Son();
console.log(son.lastName );//Deng
这种共享原型的方式,Father和Son同时指向同一个原型,
·它们共用的一个原型,一个更改了原型上的属性,其他的都会更改。
现在我们用一个函数来对上面的例子进行封装
Father.prototype.lastName = "Deng";
function Father() {
}
function Son() {
}
function inherit(Targrt, Origin){
Targrt.prototype = Origin.prototype;
}
inherit(Son,Father);
Son.prototype.sex = 'male'
var son = new Son()
var father = new Father();
当我们增加Son的原型的时候,Father的原型也就跟着增加了,可是我们并不想增加一个函数的原型的时候,另一个函数的原型也跟着增加,
所以就引出了我们的圣杯模式
圣杯模式
就是构建一个构造函数F,
F.prototype =Father.prototype
Son.prototype = new F();
Father.prototype.lastName = "Deng";
function Father() {}
function Son() {}
function inherit(Target, Origin){
function F() {} //新建一个空的构造函数
F.prototype = Origin.prototype;//让这个空的构造函数(F)的原型指向原始函数(Father)的原型
Target.prototype = new F();//让要继承函数(Son)的原型指向这个空函数的原型,这样这个要继承别人的函数添加自己原型属性,也不会污染那个原始函数。
}
inherit(Son,Father);
var son = new Son()
var father = new Father();
//这样就形成了Son继承子F,F继承自Father。
查看son的构造函数是Father(理应应该是Son),这里是有一个过程的:
son.proto—>new F(),对象上没有constructor ,
再往上找 new F().proto---->Father.prototype,
Father.prototype上有constructor,它指向Father,
这样就指向紊乱了。需要给它归位。如下:
// JavaScript Document
function Father() {}
function Son() {}
function inherit(Target, Origin){
function F() {}
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target //让对象的constructor 属性指向自己
}
inherit(Son,Father);
var son = new Son()
var father = new Father();
问题:如果把F.prototype = Origin.prototype;和 Target.prototype = new F();换个位置还能实现这种继承模式吗?
function inherit(Target, Origin){
function F() {}
Target.prototype = new F();
F.prototype = Origin.prototype;
}
这样就不行了,因为,new F()的是原来的那个原型,new完了再改让它指向Origin.prototype已经晚了。
在看一个应用的变量的的小栗子:
function inherit(Target, Origin){
function F() {}
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target
Target.prototype.uber = Origin.prototype;
}
var inherit = (function () {
var F = function () {};
return function (Target, Origin){
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target
Target.prototype.uber = Origin.prototype;
}
}());
这两种方式下面的一种是闭包的封装,实现F函数的私有化
var F = function () {};这里的F函数执行完之后就会销毁,但是他传递原型断开映射的功能以及实现,他就成为了私有变量或私有函数
F函数本来就是用来过渡的
(建议以后的圣杯模式就写成下面这种方式。)
对象枚举
仿jquery封装`连续调用`
var jq = {
a : function () {
console.log("aaa");
return this;//在每个函数的末尾返回this对象,可以实现对象连续调用多个方法
//因为this就是谁调用这个函数,函数里面的this就指向谁。
},
b : function () {
console.log("bbb");
return this;
},
c : function () {
console.log("ccc");
return this;
}
}
jq.a().b().c();//aaa bbb ccc
对象访问属性
- 对象.属性–>obj.name
- 对象[字符串形式的属性名]---->obj[‘name’]
stu = {name:xm, age:18};
var age1 = stu.age;
var age2 = stu["age"];
alert(age1 == age2);//true
内部原理:
每当你访问 obj.name 的时候,系统隐式的访问的是 obj[‘name’];
直接使用这样的方式obj[‘name’]更方便,因为内部就是这样执行的,访问速度更快。
示例:实现输入索引就输出对应索引属性名属性的值(就是实现属性名的拼接)
var q = {
a1 :{name : "dong"},
a2 :{name : "nan"},
a3 :{name : "xi"},
a4 :{name : "bei"},
sayA :function (num){
return this['a' + num];
//实现属性名的拼接,只能用对象['属性名' + ...]的方式
}
}
console.log(q.sayA(1));//Object { name: "dong" }
console.log(q.sayA(2));//Object { name: "nan" }
console.log(q.sayA(3));//Object { name: "xi" }
console.log(q.sayA(4));//Object { name: "bei" }
对象的枚举
例如给你一组数据,找出每一个数据就是遍历枚举
var obj = {
name : '13',
age : 89,
sex : "male",
height : 180,
weight : 75
}
for(var prop in obj) {//通过对象的个数控制循环圈数
console.log(prop + " " + typeof(prop));
//name string
//age string
//sex string
//height string
//weight string
console.log(obj.prop); //undefined undefined undefined undefined undefined
//这个时候系统是把prop当做obj的一个属性,因为它没有这个属性所以会输出undefined
console.log(obj[prop]);
}
用obj[prop]就是正确的,这样就会把它当做一个变量来看
不能加上obj[‘prop’],prop现在本来就是一个字符串
hasOwnProperty 方法
- hasOwnProperty(),括号里面传进去的是要判断属性名的字符串形式,返回布尔值
- hasOwnProperty就是过滤性掉原型上的属性,留下只属于自己的属性
var obj = {
name : '13',
age : 89,
sex : "male",
height : 180,
weight : 75,
__proto__ : {//手动给原型上添加一个属性
lastName : "deng"
}
}
Object.prototype.abc = "123";
for (var prop in obj){
for (var prop in obj){
if(obj.hasOwnProperty(prop)){//过滤性掉原型上的属性
console.log(obj[prop]);//13 89 male 180 75
}
}
for (var prop in obj){
//!hasOwnProperty
if(!obj.hasOwnProperty(prop)){
console.log(obj[prop]);//deng 123
}
}
in属性(很少用)
- in
只能判断这个对象能不能访问到这个属性
,包括原型上的。(父类的也就返回) - in 的语法格式就是前面就是这个属性的字符串形式,不能直接用属性名
console.log('height' in obj);//true
console.log('lastName' in obj);//true
console.log(height in obj);//ReferenceError: height is not defined
注意最后一种情况,属性一定要加双引号
instanceof 属性
function Person() {
}
var person = new Person();
// A对象是不是B构造函数构造出来的?
//看A对象的原型链上有没有B的原型
// A instanceof B
console.log(person instanceof Person);//true
console.log(person instanceof Object);//true
console.log({} instanceof Object);//true
console.log(person instanceof Array);//false
区分数组还是对象,var arr = [] || {}?
方法一:判断constructor方法是否一致
方法二:既然数组是一种对象,那么数组的原型链上一定有对象的原型,而对象的原型链上没有数组的原型,instanceof 方法
方法三:使用Object.prototype.toString.call([]),将[]数组按照Object的原型的toString的方法打印
var arr = [] || {};//区分是数组还是对象
//1,用constructor方法:[].constructor是function Array(), obj.constructor是 function Object()
//2,用instanceof方法:[] instanceof Array是true, obj instanceof Array是 false
//3,用toString,各个构造函数重写了toString方法
Object.prototype.toString.call([]);
//谁调用this,this就是谁
Object.prototype.toString = function () {
// 识别this
// 返回相应的结果
}
// obj.toString();
命名空间
var name="bcd";
var init=(function(){
var name="abc";
function callName(){
console.log(name);
}
return function(){
callName();
}
}())
init();
输出结果是abc
在外面定义的name与内部name不互相影响,函数在外部调用内部函数函数形成了闭包,init()代表callname()功能,变量私有化不会污染全局变量。
习题穿插
第一题
Question: javaScript中定义函数的方式有_______, ______, _______.
1,函数声明 2,函数的表达式 3,Function构造器
var fn = new Function('var a = 10;console.log(a)');
fn();
var fn1 = function(){
var a = 10 ;
console.log(a);
}
funciton Function(){} 产生匿名的函数
// var obj = new Object()
第二题
var value = "web"
function bar() {
var value = "duyi";
function foo() {
console.log(value);
}
return foo;
}
bar()()//变成了 (function foo(){ console.log(value)})()
问输出什么
duyi
第三题
Question: 实现一个函数addCounter,用于返回累加n操作的函数
function add(a) {
function sum(b) { // 使用闭包
a = a + b; // 累加
return sum;
}
sum.toString = function() { // 重写toString()方法,返回sum值
return a;
}
return sum; // 返回一个函数
}
习题:
输出什么?
function里面变成表达式会立刻执行, 整个函数转换成true,f已经是undefined,在执行x+=typeof f;这时候f已经没得了,但惟独放在typeof里面不报错,1+undefined就是undefined。
输出undefined
this
this指向的几种情况:
函数预编译过程 this 指向 window
function test(c){
// var this = Object.create(test.prototype);第一种最标准的隐式this的写法
//var this = { 第二种方法
// __proto__ : test.prototype
// }
var a = 123;
function b() {}
}
//预编译阶段生成AO对象,
//AO对象不仅有变量的声明提升和函数声明提升,还有arguments实参列表和this指向window
// AO {
// arguments : [1],
// this : window,
// c : 1,
// a : undefined,
// b : function () {}
// }
test(1);
new test();
当new一个对象的时候,调用函数的时候,
在函数内部生成一个隐式的this对象(var this = Object.create(test.prototype);)去覆盖AO里面的this
function test() {
console.log(this);
}
test();
打印this其实就是window,打印window和它是一样的。
全局作用域里 this 指向 window
在全局作用域也就是GO对象里面也有一个this,它自然是指向window的。
call/appy可以改变函数运行时的this指向
obj.function(), function里面的this指向obj
var obj = {
a : function () {
console.log(this.name);
},
name : 'abc'
}
obj.a();//abc
this笔试题
var name = "222";
var a = {
name : "111",
say :function () {
console.log(this.name);
}
}
var fun = a.say;
fun()
a.say()
var b = {
name : "333",
say : function (fun) {
fun();
}
}
b.say(a.say);
b.say = a.say;
b.say();
分析:
var name = "222";
var a = {
name : "111",
say :function () {
console.log(this.name);
}
}
var fun = a.say;//a.say代表函数的引用,代表函数体
fun();//就是把
// function () {
// console.log(this.name);
// }放到全局范围内执行,也没人调用它,所以this指向window,所以打印222
a.say()// 这个就是对象调用这个方法,this指向对象a,打印的就会是111
var b = {
name : "333",
say : function (fun) {
//this---->b,333
fun();
}
}
b.say(a.say);//b.say,对象b调用它的say方法,现在b的say方法里面的this指向b
//然后里面放进去一个a.say,也就是把function () {
// console.log(this.name);
// }放到b的say函数里面执行,这个函数执行只不过是在另外一个函数里面,也没人调用它,这个时候走的就是预编译的环节,this它就指向window,打印出来的就是222
b.say = a.say;//把a.say放到b.say里面,那就是var b = {
// name : "333",
// say : function () {
// console.log(this.name);
// }
// }
b.say();//再调用b.say方法当然是this指向自己,打印333
arguments
- arguments 是一个类似数组的对象, 对应于传递给函数的参数。
找到最大的一个参数的值:
x = findMax(1, 123, 500, 115, 44, 88);
function findMax() {
var i, max = arguments[0];
if(arguments.length < 2) return max;
for (i = 0; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
创建一个函数用来统计所有数值的和:
x = sumAll(1, 123, 500, 115, 44, 88);
function sumAll() {
var i, sum = 0;
for (i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
arguments.callee
function test{
console.log(arguments.callee==test)
}
test();
打印结果是true
callee是arguments对象的属性。在函数体内,它指向当前正在执行的函数。
实现10的阶乘
var num=(function (n){
if (n==1){
return 1;
}
return n * arguments.callee(n-1);
}(10))
function test() {
conso.log(arguments.callee);//打印test函数
function demo () {
conso.log(arguments.callee);//打印demo函数
}
demo();
}
test();
就是说在哪个函数里面的arguments.callee就指向那个函数的引用。