JavaScript中函数、对象、类关系 记录
函数和对象的关系
函数可以有属性,对象也可以有属性,在函数名前使用 new
操作符即可返回一个函数的实例化对象
function fn () {}
fn.a = 'haha'
console.log(fn.a) //"haha"
let obj = {}
obj.a = 'heihei'
console.log(obj.a) //"heihei"
let newObj = new fn()
每个函数都有一个属性(prototype)原型对象,发现有constructor
属性和 __poroto__
属性,constructor
指向创建它的构造器函数,这里要明确的是 函数也会有构造函数,而这个__poroto__
与它的构造函数的 prototype 是同一个东西,见图 虚线指向的CFp就是构造函数的prototype,小的cf 是通过CF创建的对象实例
// fn.prototype
{
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
我们既可以在CF上添加属性,也可以在prototype
添加
区分:函数上添加属性,构造函数生成实例上添加、函数原型添加
function fn() {
this.c = 'c'
}
fn.a = 'a'
fn.prototype.b = 'b'
let obj = new fn()
obj.d = 'd'
// fn.prototype 上定义 属性b ,obj定义了属性d,fn上添加了属性 a,
// fn函数内部有个this的属性c, 那么obj有几个属性呢? 答案是 c b d
下面就来分析下为什么会这样,首先图中的CF上的P1、P2在对象实例中均存在,参考下面的代码
function CF(){
this.p1 = 'p1',
this.p2 = 'p2'
}
let cf1 = new CF()
见下图
可以看到属性均出现在了对象实例上,现在就来说一下构建实例的发生了什么
- 创建新对象cf1
- 构造函数的作用域赋给新对象,this指向新对象
- 执行构造函数中的代码,即注入属性
- 返回新对象
不过this上的属性构造函数是不具有的,而在函数上直接定义的属性它当然是有的,通过prototyoe
设置的是在图的CFp位置的,实例继承它,也就是对象实例会得到这些属性,对象实例在添加prototype
属性前创建依然有效
函数和对象的关系也就清楚了,通过构造函数方式可以创建对象,且可以得到函数原型上的属性和方法,也可以在构造函数里通过this为其设置一些不是公有的属性方法等
扩展:
-
使用this添加的属性具有一定“私有”性,但是还有些需要注意的地方,见下面代码
function fn() { let value = 'a' this.a = 'a' this.privateValues = function (){ return value } } let newObj1 = new fn() let newObj2 = new fn() console.log(newObj1.a === newObj2.a) //true,基本类型的比较只比较值 console.log(newObj1.privateValues === newObj2.privateValues) // false,每次进入对象都不相同
使用函数将其包裹起来返回具有更好的“私有”性
-
使用对象重写原型对象
function fn() {} fn.prototype = { name: 'kangkang', sayName: { console.log(this.name) } }
使用对象重写会导致创建一个新的
prototype
对象, 对象里的constructor
不再指向构造函数,这时需要重新指定下function fn() {} fn.prototype = { constructor: fn name: 'kangkang', sayName: { console.log(this.name) } }
继承
这里的继承想要有个较为深刻的理解,首先需要介绍下原型链:
每个实例对象(object )都有一个私有属性(称之为 proto)指向它的原型对象(prototype)。该原型对象也有一个自己的原型对象 ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节
参考MDN
由于每个实例都会继承原型链上的原型对象上的属性或方法,当你使用某个实例上所没有的属性时,会依着原型链一层一层寻找,那么继承这个概念也就很清楚了,我们将原型对象给需要继承的对象不就行了吗?其实之间会有些问题
function father() {}
function son() {}
father.prototype.sayHello = function() {
console.log('hello')
}
son.prototype = father.prototype
let obj = new son()
obj.sayHello() // hello
//
father.prototype.sayHello = function() {
console.log('haha')
}
obj.sayHello() // haha
son.prototype.sayHello = function() {console.log('ddd')}
father.prototype.sayHello() //ddd
上面这种“继承”是有问题的,不仅互相影响而且当father修改原型时会直接影响到son,进行改进
function father() {}
function son() {}
father.prototype.sayHello = function() {
console.log('hello')
}
function wa() { }
wa.prototype = father.prototype
son.prototype = new wa()
son.prototype.constructor = son
son.prototype.sayHello = function() {console.log('sss')}
father.prototype.sayHello() //hello
ECMAScript 5 中引入了一个新方法:Object.create(). 可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty);
// undefined, 因为d没有继承Object.prototype
上面还是有点缺点,当 原型对象上有引用类型值得属性时
function a () {
this.obj = {
name: 'kangkang',
age: 3
}
}
function b() {
this.b = 'b'
}
b.prototype = new a()
var c = new b()
var d = new b()
c.obj.name='xixi'
console.log(d.obj.name) //xixi
这时创建的实例在每次获得构造函数的引用值属性时,获得的值相同,值保存的是引用类型的地址,自然是同一个引用值了,所以互相影响,解决方法
看下面代码
function fn (name) {
this.score = [90,98,99]
this.name = name
}
function a () {
fn.call(this,'kangkang')
this.age = 3
}
var c = new a()
var d = new a()
c.score[0] = 100
console.log(c.score) //[100, 98, 99]
console.log(d.score) //[90, 98, 99]
这种方法又缺少了原型上的继承,所以结合起来就是
function fn (name) {
this.score = [90,98,99]
this.name = name
}
fn.prototype.sayName = function() { console.log(this.name) }
function a () {
fn.call(this,'kangkang')
this.age = 3
}
a.prototype = new fn()
a.prototype.constructor = a
var c = new a()
var d = new a()
c.score[0] = 100
console.log(c.score) //[100, 98, 99]
console.log(d.score) //[90, 98, 99]
console.log(c.sayName()) // kangkang
类
ES6 实现了 class
,其中有 class
constructor
static
extends
super
这些关键字完整的实现“类”的功能, 它是语法糖,是基于上面所说的原型的概念实现的
- 定义类名 首字母大写
- constructor方法是一个特殊的方法,其用于创建和初始化使用class创建的一个对象,只能有一个
- 使用 super 关键字来调用一个父类的构造函数
- extends 关键字在类声明或类表达式中用于创建一个类作为另一个类的一个子类。
- static 关键字用来定义一个类的一个静态方法。调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
static
用法
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
console.log(Point.distance(p1, p2)); //7.0710678118654755
// 注意此处不是从实例中调用它的
拓展:
ES6属性简写和方法简写 参考阮一峰老师的博客
const o = {
method() {
return "Hello!";
}
};
// 等同于
const o = {
method: function() {
return "Hello!";
}
};
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
以上为查阅资料总结而得,如要详细准确还请自行查阅方能辨析有所得