1.一切都是对象
“一切都是对象”这句话的重点在于如何去理解“对象”这个概念。
——当然,也不是所有的都是对象,值类型就不是对象。首先咱们还是先看看javascript中一个常用的运算符——typeof
<script type="text/javascript">
//说起一切都是对象这种说法的由来,就要好好提一下历史因素了。
//1995年,JavaScript诞生之年,Netscape公司(JavaScript的设计者)与Sun公司(Java语言的发明者和//所有者)合作开发一种可以嵌入网页的脚本语言,将JavaScript的数据结构借鉴Java而设计,包括将值分//成原始值和对象两大类。
//Java中一切皆对象,但基本类型却不是对象,为了解决这个问题,Java让每个基本类型都对应了一个包装器类型。包装器类型将基本类型包装起来,添加了属性和方法,包装器类型即为对象,所以可以这么说Java中的一切都可以充当对象,不会说的那么绝对。
//因此借鉴了Java数据结构的JavaScript也同样在基本类型中各对应了一个包装器类型,JavaScript中的一切都可以充当对象,接下来将详细进行介绍。
//原始类型与对象
//JavaScript中值可以分为两大类:原始类型和对象。
//定义
//在JavaScript中,有六种原始数据类型:
//Booleans - true 或 false
//null - 用 type of 检验 null数据类型时为Object,但它不是对象,这是JS的一个bug
//undefined
//number - JavaScript中的所有数字都是浮点数,没有整数
//string
//symbol(ES6)
//除去上面的原始数据类型,所有其他值都是对象。对象可以进一步分为:
//原始值的包装类型:Boolean,Number,String. - 很少直接使用。
//以下类型生成的对象也可以通过构造函数创建:
//[] 类同于 new Array()
// { } 类同于 new Object()
//function() { } 类同于 new Function()
///\s*/ 类同于 new RegExp("\\s*")
//Dates: new Date("2011-12-24")
//1. 基本
// typeof返回数据类型的字符串表达
// <!--
// 1. 分类
// * 基本(值)类型
// * String: 任意字符串
// * Number: 任意的数字
// * boolean: true/false
// * undefined: undefined
// * null: null
// * 对象(引用)类型
// * Object: 任意对象
// * Function: 一种特别的对象(可以执行)
// * Array: 一种特别的对象(数值下标, 内部数据是有序的)
// 2. 判断
// * typeof:
// * 可以判断: undefined/ 数值 / 字符串 / 布尔值 / function
// * 不能判断: null与object object与array
// * instanceof:
// * 判断对象的具体类型
// * ===
// * 可以判断: undefined, null
// -->
var a
console.log(a, typeof a, typeof a==='undefined',a===undefined )
// undefined 'undefined' true true
console.log(undefined==='undefined')//false
a = 4
console.log(typeof a==='number')//true
console.log(typeof a==='string')//false
console.log(typeof a==='boolean')//false
console.log(typeof a, a===null) //number false
console.log('-----------------')
//2. 对象
var b1 = {
b2: [1, 'abc', console.log],
b3: function () {
console.log('b3')
return function () {
return 'gg'
}
}
}
console.log(b1 instanceof Object, b1 instanceof Array)
// true false
console.log(b1.b2 instanceof Array, b1.b2 instanceof Object)
// true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object)
// true true
console.log(typeof b1.b2, '-------') // 'object' -------
console.log(typeof b1.b3==='function') // true
console.log(typeof b1.b2[2]==='function')//true
b1.b2[2](4) //4
console.log(b1.b3()())//b3 gg
</script>
//new对象的过程
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>new过程</title>
</head>
<body>
<script>
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
var objectFactory = function (constructor) {
var obj = new Object(); //从Object.prototype上克隆一个空的对象
Constructor = [].shift.call(arguments); //取得外部传入的构造器,此例是Person
obj.__proto__ = Constructor.prototype; //指向正确原型
var ret = Constructor.apply(obj, arguments); //借用外部传入的构造器给obj设置属性
return typeof ret === "object" ? ret : obj; //确保构造器总是返回一个对象
};
var a = objectFactory(Person, "sven");
console.log(a);
console.log(a.name);
console.log(a.getName());
console.log(Object.getPrototypeOf(a) === Person.prototype);
</script>
</body>
</html>
//当代码 new Foo(...) 执行时,会发生以下事情:
一个继承自 Foo.prototype 的新对象被创建。
使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
function show(x) {
console.log(typeof x); // undefined
console.log(typeof 10); // number
console.log(typeof 'abc'); // string
console.log(typeof true); // boolean
console.log(typeof function () {}); //function
console.log(typeof [1, 'a', true]); //object
console.log(typeof { a: 10, b: 20 }); //object
console.log(typeof null); //object
console.log(typeof new Number(10)); //object
}
show();
以上代码列出了typeof输出的集中类型标识,其中上面的四种(undefined, number, string, boolean)属于简单的值类型,不是对象。剩下的几种情况——函数、数组、对象、null、new Number(10)都是对象。他们都是引用类型。
判断一个变量是不是对象非常简单。值类型的类型判断用typeof,引用类型的类型判断用instanceof。
var fn = function () { };
console.log(fn instanceof Object); // true
javascript中的对象,到底该如何定义呢?
对象——若干属性的集合(key:value 键值)。
java或者C#中的对象都是new一个class出来的,而且里面有字段、属性、方法,规定的非常严格。但是javascript就比较随意了——数组是对象,函数是对象,对象还是对象。对象里面的一切都是属性,只有属性,没有方法。那么这样方法如何表示呢?——方法也是一种属性。因为它的属性表示为键值对的形式。
而且,更加好玩的事,javascript中的对象可以任意的扩展属性,没有class的约束。这个大家应该都知道,就不再强调了。先说个最常见的例子:
var obj = {
a: 'Tom',
b: [1, 'abc', console.log],
c: function() {
console.log('b3')
return function() {
return 'xfzhang'
}
},
d: {
name: 'js',
year: 2022
}
}
以上代码中,obj是一个自定义的对象,其中a、b、c、d就是它的属性,而且在d的属性值还是一个对象,它又有name、year两个属性。
这个可能比较好理解,那么函数和数组也可以这样定义属性吗?——当然不行,但是它可以用另一种形式,总之函数/数组之流,只要是对象,它就是属性的集合。
以函数为例子:
var fn = function () {
alert(100);
};
fn.a = 10;
fn.b = function () {
alert(123);
};
fn.c = {
name: "js",
year: 2022
};
上段代码中,函数就作为对象被赋值了a、b、c三个属性——很明显,这就是属性的集合吗? 是
问:这个有用吗?
答:可以看看jQuery源码!
在jQuery源码中,“jQuery”或者“$”,这个变量其实是一个函数,不信你可以叫咱们的老朋友typeof验证一下。
console.log(typeof $); // function
console.log($.trim(" ABC "));
验证OK!的确是个函数。那么咱们常用的 $.trim() 也是个函数,经常用,就不用验了吧!
很明显,这就是在$或者jQuery函数上加了一个trim属性,属性值是函数,作用是截取前后空格。
javascript与java/C#相比,首先最需要解释的就是弱类型,因为弱类型是最基本的用法,而且最常用,就不打算做一节来讲。
其次要解释的就是本文的内容——一切(引用类型)都是对象,对象是属性的集合。最需要了解的就是对象的概念,和java/C#完全不一样。所以,切记切记!
最后,有个疑问。在typeof的输出类型中,function和object都是对象,为何却要输出两种答案呢?都叫做object不行吗?——当然不行。
2.函数和对象的关系
上文已经提到,函数就是对象的一种,因为通过instanceof函数可以判断。
var fn = function () { };
console.log(fn instanceof Object); // true
对!函数是一种对象,但是函数却不像数组一样——你可以说数组是对象的一种,因为数组就像是对象的一个子集一样。但是函数与对象之间,却不仅仅是一种包含和被包含的关系,函数和对象之间的关系比较复杂,甚至有一点鸡生蛋蛋生鸡的逻辑,咱们这一节就缕一缕。先看一个小例子:
function Fn() {
this.name = 'JS';
this.year = 2022;
}
var fn1 = new Fn();
说明:对象可以通过函数来创建。对!也只能说明这一点。
但是我要说——对象都是通过函数创建的——有些人可能反驳:不对!因为:
var obj = { a: 10, b: 20 };
var arr = [5, 'x', true];
但是不好意思,这个真的是一种“快捷方式”,在编程语言中,一般叫做“语法糖”。
话归正传——其实以上代码的本质是:
//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];
var obj = new Object();
obj.a = 10;
obj.b = 20;
var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true;
而其中的 Object 和 Array 都是函数:
console.log(typeof (Object)); // function
console.log(typeof (Array)); // function
所以,可以很负责任的说——对象都是通过函数来创建的。现在是不是糊涂了—— 对象是函数创建的,而函数却又是一种对象——天哪!函数和对象到底是什么关系啊?
3.prototype原型
在上文1中说道,函数也是一种对象。他也是属性的集合,你也可以对函数进行自定义属性。
不用等咱们去试验,javascript自己就先做了表率,人家就默认的给函数一个属性——prototype。对,每个函数都有一个属性叫做prototype。
这个prototype的属性值是一个对象(属性的集合,再次强调!),默认的只有一个叫做constructor的属性,指向这个函数本身(该属性保存指向函数F 的一个引用)。
如上图,SuperType是是一个函数,右侧的方框就是它的原型。
原型既然作为对象,属性的集合,不可能就只弄个constructor来玩玩,肯定可以自定义的增加许多属性。例如这位Object大哥,人家的prototype里面,就有好几个其他属性。
接着往下说,你也可以在自己自定义的方法的prototype中新增自己的属性
function Fn() { }
Fn.prototype.name = 'JS';
Fn.prototype.getYear = function () {
return 2022;
};
看到没有,这样就变成了
但是,这样做有何用呢? —— 解决这个问题,咱们还是先说说jQuery吧。
var $div = $('div');
$div.attr('myName', 'JS');
//以上代码中,$('div')返回的是一个对象,对象——被函数创建的。
//假设创建这一对象的函数是 myjQuery。它其实是这样实现的。
myjQuery.prototype.attr = function () {
//……
};
$('div') = new myjQuery();
如果用咱们自己的代码来演示,就是这样:
function Fn() { }
Fn.prototype.name = 'JS';
Fn.prototype.getYear = function () {
return 2022;
}; //Fn() 是函数
var fn = new Fn();//fn是对象 通过函数创建
console.log(fn.name);
console.log(fn.getYear());
即Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用Fn.prototype中的属性。 因为每个对象都有一个隐藏的属性——“__proto__”,这个属性引用了创建这个对象的函数的prototype。即:fn.__proto__ === Fn.prototype
<body>
<!--
1. 每个函数function都有一个prototype,即显式原型(属性)
2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
3. 对象的隐式原型的值为其对应构造函数的显式原型的值
4. 内存结构(图)
5. 总结:
* 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
* 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
* 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
-->
<script type="text/javascript">
//定义构造函数
function Fn() { // 内部语句: this.prototype = {}
}
// 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的
//(没有我们自己定义的数据)Object对象
console.dir(Fn)
console.log(Fn.prototype)
console.log(Fn.__proto__)
console.log("``````````")
// 2. 每个实例对象都有一个__proto__,可称为隐式原型
//创建实例对象
var fn = new Fn() // 内部语句: this.__proto__ = Fn.prototype
console.log(fn.__proto__)
// 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype === fn.__proto__) // true
//给原型添加方法
Fn.prototype.test = function() {
console.log('test()')
}
//通过实例调用原型的方法
fn.test()
fn.__proto__.test()
Fn.prototype.test()
</script>
</body>
4.隐式原型
上节已经提到,每个函数function都有一个prototype,即原型(显示原型)。这里再加一句话——每个对象都有一个__proto__,可成为隐式原型。
<script>
function fn() {}
console.log(fn.prototype)
console.log(fn.__proto__)
console.log("----")
console.log(Function.prototype)
console.log(Function.__proto__)
console.log(Function.__proto__ === Function.prototype)//true
</script>
这个__proto__是一个隐藏的属性,javascript不希望开发者用到这个属性值,有的低版本浏览器甚至不支持这个属性值。所以你在Visual Studio 2019这样很高级很智能的编辑器中,都不会有__proto__的智能提示,但是你不用管它,直接写出来就是了。
<script>
var obj = {}
console.log(obj.__proto__)
console.log("----")
console.log(Object.prototype)
</script>
上面截图看来,obj.__proto__和Object.prototype的属性一样!这么巧!答案就是一样。
obj这个对象本质上是被Object函数创建的,因此obj.__proto__=== Object.prototype。我们可以用一个图来表示。
var o1 = new Object();
var o2 = {};
即,每个对象都有一个__proto__属性,指向创建该对象的函数的prototype。
那么上图中的“Object prototype”也是一个对象,它的__proto__指向哪里?好问题!在说明“Object. prototype”之前,先说一下自定义函数的prototype。自定义函数的prototype本质上就是和 var obj = {} 是一样的,都是被Object创建,所以它的__proto__指向的就Object.prototype。但是Object.prototype确实一个特例——它的__proto__指向的是null,切记切记!
console.log(Object.prototype.__proto__) // null Object的原型对象是原型链尽头
function Foo(){}
var f1=new Foo()
var f2=new Foo()
还有——函数也是一种对象,函数也有__proto__吗?又一个好问题!——当然有。
函数也不是从石头缝里蹦出来的,函数也是被创建出来的。谁创建了函数呢?——Function——注意这个大写的“F”。所有函数都是Function的实例(包含Function)
console.log(Function.__proto__ === Function.prototype)//true
且看如下代码。
function fnn(x, y) {
return x + y;
}
console.log(fnn(10, 10)) //20
var fn1 = new Function("x", "y", "return x+y")
console.log(fn1(6, 7))//13
以上代码中,第一种方式是比较传统的函数创建方式,第二种是用new Functoin创建。
首先根本不推荐用第二种方式。
这里只是向大家演示,函数是被Function创建的。
好了,根据上面说的一句话——对象的__proto__指向的是创建它的函数的prototype,就会出现:Object.__proto__ === Function.prototype。用一个图来表示。
上图中,很明显的标出了:自定义函数Foo.__proto__指向Function.prototype,Object.__proto__指向Function.prototype,唉,怎么还有一个……Function.__proto__指向Function.prototype?这不成了循环引用了?
对!是一个环形结构。
其实稍微想一下就明白了。Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以——Function是被自身创建的。
Function=new Function('参数','函数体')
所以它的__proto__指向了自身的Prototype。
//函数(但Object函数不满足)的显示原型指向的对象默认是空的(我们没有增加属性的
//,原始的)Object实例对象
var fn = new Fn()
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true
最后一个问题:Function.prototype指向的对象,它的__proto__是不是也指向Object.prototype?
答案是肯定的。因为Function.prototype指向的对象也是一个普通的被Object创建的对象,所以也遵循基本的规则。
5.instanceof
又介绍一个老朋友——instanceof。
对于值类型,你可以通过typeof判断,string/number/boolean都很清楚,但是typeof在判断到引用类型的时候,返回值只有object/function,你不知道它到底是一个object对象,还是数组,还是new Number等等。
这个时候就需要用到instanceof。例如:
function Foo() { }
var f1 = new Foo()
console.log(f1 instanceof Foo) // true
console.log(f1 instanceof Object) // true
上图中,f1这个对象是被Foo创建,但是“f1 instanceof Object”为什么是true呢?
至于为什么过会儿再说,先把instanceof判断的规则告诉大家。根据以上代码看下图:
instanceof是如何判断的?
* 表达式: A instanceof B
* 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
Instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。
Instanceof的判断队则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。
按照以上规则,大家看看“ f1 instanceof Object ”这句代码是不是true? 根据上图很容易就能看出来,就是true。
通过上以规则,你可以解释很多比较怪异的现象,例如:
console.log(Object instanceof Function) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true
这些看似很混乱的东西,答案却都是true,这是为何?
正好,这里也接上了咱们上一节说的“乱”。
上一节咱们贴了好多的图片,其实那些图片是可以联合成一个整体的,即:
看这个图片,千万不要嫌烦,必须一条线一条线挨着分析。如果上一节你看的比较仔细,再结合刚才咱们介绍的instanceof的概念,相信能看懂这个图片的内容。
值类型的类型判断用typeof,引用类型的类型判断用instanceof.
数组是对象,函数是对象,对象还是对象,所有函数都是Function的实例(包含Function,Function也是一个函数,函数是一种对象).
对象都是通过函数创建,每个函数都有一个属性叫做prototype.
每个对象都有一个隐藏的属性——“__proto__”,这个属性引用了创建这个对象的函数的prototype,即:fn.__proto__ === Fn.prototype.
问题又出来了。Instanceof这样设计,到底有什么用?到底instanceof想表达什么呢?
即,instanceof表示的就是一种继承关系,或者原型链的结构。
简版构造函数和原型
6.0继承
为何用“继承”为标题,而不用“原型链”?
原型链如果解释清楚了很容易理解,不会与常用的java/C#产生混淆。而“继承”确实常用面向对象语言中最基本的概念,但是java中的继承与javascript中的继承又完全是两回事儿。因此,这里把“继承”着重拿出来,就为了体现这个不同。
javascript中的继承是通过原型链来体现的。先看几句代码
<script type="text/javascript">
function Fn() {
//...
}
Fn.prototype.a = 'test1'
var fn1 = new Fn()
console.log(fn1.a, fn1) // test1 ,Fn {}
var fn2 = new Fn()
fn2.a = 'test2' //重新赋值
console.log(fn1.a, fn2.a, fn2) //test1 ,test2, Fn {a: "test2"}
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function(name) {
this.name = name
}
var p1 = new Person('Tom', 12)
p1.setName('Bob')
console.log(p1) //Person {name: "Bob", age: 12}
var p2 = new Person('Jack', 12)
p2.setName('Cat')
console.log(p1) //Person {name: "Bob", age: 12}
console.log(p2) //Person {name: "Cat", age: 12}
console.log(p1.__proto__ === p2.__proto__) // true
</script>
function Foo() {}
var f1 = new Foo()
f1.a = 10
Foo.prototype.a = 100
Foo.prototype.b = 200
console.log(f1.a) //100
console.log(f1.b) //200
以上代码中,f1是Foo函数new出来的对象,f1.a是f1对象的基本属性,f1.b是怎么来的呢?——从Foo.prototype得来,因为f1.__proto__指向的是Foo.prototype
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。
看图说话:
上图中,访问f1.b时,f1的基本属性中没有b,于是沿着__proto__找到了Foo.prototype.b。
那么我们在实际应用中如何区分一个属性到底是基本的还是从原型中找到的呢?大家可能都知道答案了——hasOwnProperty,特别是在for…in…循环中,一定要注意。
等等,不对! f1的这个hasOwnProperty方法是从哪里来的? f1本身没有,Foo.prototype中也没有,哪儿来的?
好问题。
它是从Object.prototype中来的,请看图:
对象的原型链是沿着__proto__这条线走的,因此在查找f1.hasOwnProperty属性时,就会顺着原型链一直查找到Object.prototype。
由于所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”。
当然这只是一个例子,你可以自定义函数和对象来实现自己的继承。
说一个函数的例子吧。
我们都知道每个函数都有call,apply方法,都有length,arguments,caller等属性。为什么每个函数都有?这肯定是“继承”的。函数由Function函数创建,因此继承的Function.prototype中的方法。不信可以请微软的Visual Studio老师给我们验证一下:
看到了吧,有call、length等这些属性。
那怎么还有hasOwnProperty呢?——那是Function.prototype继承自Object.prototype的方法。有疑问可以看看上一节将instanceof时候那个大图,看看Function.prototype.__proto__是否指向Object.prototype? 是。
6.1 原型链继承
<body>
<!--
方式1: 原型链继承
1. 套路
1. 定义父类型构造函数
2. 给父类型的原型添加方法
3. 定义子类型的构造函数
4. 创建父类型的对象赋值给子类型的原型
5. 将子类型原型的构造属性设置为子类型
6. 给子类型原型添加方法
7. 创建子类型的对象: 可以调用父类型的方法
2. 关键
1. 子类型的原型为父类型的一个实例对象
-->
<script type="text/javascript">
//父类型
function Supper() {
this.supProp = 'Supper property'
}
Supper.prototype.showSupperProp = function() {
console.log(this.supProp)
}
//子类型
function Sub() {
this.subProp = 'Sub property'
}
// 子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper()
// 让子类型的原型的constructor指向子类型
Sub.prototype.constructor = Sub
Sub.prototype.showSubProp = function() {
console.log(this.subProp)
}
var sub = new Sub()
sub.showSupperProp()
// sub.toString()
sub.showSubProp()
console.log(sub) // Sub
</script>
</body>
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('------');
}
var son = new Son('JS', 2021, 100);
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype.constructor);
</script>
6.2借用构造函数继承(假的)
<body>
<!--
方式2: 借用构造函数继承(假的)
1. 套路:
1. 定义父类型构造函数
2. 定义子类型构造函数
3. 在子类型构造函数中调用父类型构造
2. 关键:
1. 在子类型构造函数中通用call()调用父类型构造函数
-->
<script type="text/javascript">
function Person(name, age) {
this.name = name
this.age = age
}
function Student(name, age, price) {
console.log(this)// Student
Person.call(this, name, age) // 相当于: this.Person(name, age)
console.log(this)// Student
/*this.name = name
this.age = age*/
this.price = price
}
//call()
// 语法:obj1.call(obj2[,param1,param2,...])
// 定义:用obj2对象来代替obj1,调用obj1的方法。即将obj1应用到obj2上。
// 说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 obj2 指定的新对象。 如果没有提供 obj2参数,那么 Global 对象被用作 obj2。
var s = new Student('Tom', 20, 14000)
console.log(s.name, s.age, s.price)
</script>
</body>
6.3原型链+借用构造函数的组合继承
1. 利用原型链实现对父类型对象的方法继承
2. 利用super()借用父类型构建函数初始化相同属性
<body>
<!--
方式3: 原型链+借用构造函数的组合继承
1. 利用原型链实现对父类型对象的方法继承
2. 利用super()借用父类型构建函数初始化相同属性
-->
<script type="text/javascript">
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function(name) {
this.name = name
}
function Student(name, age, price) {
Person.call(this, name, age) // 为了得到属性
this.price = price
}
// 子类型的原型为父类型的一个实例对象
Student.prototype = new Person() // 为了能看到父类型的方法
Student.prototype.constructor = Student //修正constructor属性
Student.prototype.setPrice = function(price) {
this.price = price
}
var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)
</script>
</body>
6.4 ES6 class
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已;
1. class 声明类;
2. constructor 定义构造函数初始化;
3. extends 继承父类;
4. super 调用父级构造方法;
5. static 定义静态方法和属性;
6. 父类方法可以重写;
<script>
//ES6之前 通过构造函数+ 原型实现面向对象编程
/*
1.构造函数有原型对象prototype
2.构造函数原型对象prototype里面有constructor 指向 构造函数本身
3.构造函数可以通过原型对象添加方法
4.构造函数创建的实例的对象有__proto__ 原型指向 构造函数的原型对象
*/
//ES6 通过类实现面向对象编程
class Person {
}
//1.类的本质其实还是一个函数 可以简单认为 类就是 构造函数的另外一种的写法
console.log(typeof Person) //function
//2.类有原型对象 prototype
console.log(Person.prototype) //{constructor: ƒ}
//3.类原型对象 prototype 里面有constructor 指向类本身
console.log(Person.prototype.constructor)
//4.类可以通过原型对象添加方法
Person.prototype.getAge = function() {
console.log("---")
}
var js = new Person();
console.log(js) //Person {}
//5.构造函数创建的实例的对象有__proto__ 原型指向 构造函数的原型对象
console.log(js.__proto__ === Person.prototype) //true
</script>
6.5 Class 原型链
每个class都有显示原型prototype,而且也有隐式原型__proto__
(class实际就是函数)
每个实例对象都有隐式原型__proto__
实例对象的__proto__
指向对应class的protype
子类的隐式原型__proto__
指向父类
//类的 prototype 属性和__proto__属性
class A {
}
class B extends A {
}
B.__proto__ === A // true 子类的__proto__属性,表示构造函数的继承,总是指向父类。
B.prototype.__proto__ === A.prototype // true 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
//实例的 __proto__ 属性
//子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。
var p1 = new A();
var p2 = new B();
p1.__proto__ === A.prototype // true 实例对象p1的隐式原型等于函数A的显示原型
p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
7.原型的灵活性
在Java和C#中,你可以简单的理解class是一个模子,对象就是被这个模子压出来的一批一批月饼(中秋节刚过完)。压个啥样,就得是个啥样,不能随便动,动一动就坏了。
而在javascript中,就没有模子了,月饼被换成了面团,你可以捏成自己想要的样子。
首先,对象属性可以随时改动。
对象或者函数,刚开始new出来之后,可能啥属性都没有。但是你可以这会儿加一个,过一会儿在加两个,非常灵活。
在jQuery的源码中,对象被创建时什么属性都没有,都是代码一步一步执行时,一个一个加上的。
其次,如果继承的方法不合适,可以做出修改。
var obj = {
a: 10,
b: 20
}
console.log(obj.toString()) // [object Object]
var arr = [1, 2, true]
console.log(arr.toString()) //1,2,true
如上图,Object和Array的toString()方法不一样。肯定是Array.prototype.toString()方法做了修改。同理,我也可以自定义一个函数,并自己去修改prototype.toString()方法。
function Foo() {
//...
}
var f1 = new Foo()
Foo.prototype.toString = function() {
return 'JS'
}
console.log(f1.toString()) //JS
最后,如果感觉当前缺少你要用的方法,可以自己去创建。
例如在json2.js源码中,为Date、String、Number、Boolean方法添加一个toJSON的属性。
如果你要添加内置方法的原型属性,最好做一步判断,如果该属性不存在,则添加。如果本来就存在,就没必要再添加了。
8.练习
<script type="text/javascript">
//测试题1
function A() {
}
A.prototype.n = 1
var b = new A()
A.prototype = {
n: 2,
m: 3
}
var c = new A()
console.log(b.n, b.m, c.n, c.m) //1 undefined 2 3
//测试题2
function F() {}
Object.prototype.a = function() {
console.log('a()')
}
Function.prototype.b = function() {
console.log('b()')
}
var f = new F()
f.a() //a()
F.a() //a()
F.b() //b()
console.log(f) //F{}
console.log(Object.prototype) //{a: ƒ, constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, …}
console.log(Function.prototype) //ƒ () { [native code] }
f.b() //报错
</script>
9. [[ ]]
[[Prototype]]
是内部属性,我们通常无法直接访问或操作它。双括号 [[Prototype]]
表示这是一个内部属性,用于表示对象的原型。在 JavaScript 中,我们通常使用 __proto__
来间接访问对象的原型。
例如:
const obj = {};
console.log(obj.__proto__); // 输出:{}
在上述示例中,obj
是一个空对象。通过 obj.__proto__
,我们可以访问到它的原型对象,即 {}
。这个原型对象本身也可能有自己的原型,这样就形成了原型链。
需要注意的是,虽然 __proto__
是用于访问原型的一种方式,但它并不是标准的 JavaScript API,并且在一些新的 JavaScript 引擎中已被废弃。推荐使用 Object.getPrototypeOf()
方法来获取对象的原型:
const obj = {};
console.log(Object.getPrototypeOf(obj)); // 输出:{}
这样可以更加规范和可移植地获取对象的原型。
请注意,[[Prototype]]
在 ES6 引入的类和对象字面量的语法中被替代为 [[HomeObject]]
,用于支持 super 关键字和类的继承。但在实际开发中,我们更常用 __proto__
或 Object.getPrototypeOf()
来访问和操作对象的原型。
JavaScript 中并没有 [[ ]]
这样的操作符或语法。[[ ]]
是一种表示内部属性(Internal Slot)的约定表示法,并不是实际的 JavaScript 代码。
内部属性(Internal Slot)是规范中定义的一种特殊的属性,用于描述对象的内部行为和状态。它们不可直接访问,只能通过规范中定义的方法和操作符来进行间接访问和操作。
例如,[[Prototype]]
和 [[Get]]
是两个内部属性的名称,用于描述对象原型和属性获取的行为。但是我们无法直接在 JavaScript 代码中使用 [[Prototype]]
或 [[Get]]
这样的语法。
相反,JavaScript 提供了相应的 API 或语法糖来让我们能够操作和访问对象的内部属性。例如,__proto__
(双下划线proto)属性可以用来访问对象的原型,而 Object.getPrototypeOf()
方法可以获取对象的原型。