Javascript 进阶概念(ES5)
以下简称 Javascript 为 JS,ECMAScript 为 ES
函数特指 function,不是广义上的函数
实例和对象指的都是通过函数new出的东西
问题
哪些值或对象是相等的,怎么判断
解决
equality tableJS中判断相等有两种方式,== 和 ===
灰色表示部分相等,也就是用 == 判断是相等的,而 === 判断是不等的,绿色表示全等,其余的不等
实际上用 == 判断时,会有一个值的强转,而用 === 判断时,首先会判断两遍的类型,也就是typeof运算符,所以如果确定等式两遍一定是相同的类型,就用==,反之则用===,具体的全等判断规则见文档
问题
Javascript中的对象和函数创建和继承是如何实现的
解决
对象的创建
如果在ES6中以OOP的概念去写的话,那这个问题的范围就不止是JS了。不过众所周知ES6中的class只是语法糖,具体实现还是原来那一套,那么先来看一下ES5官方文档中的概括:
一个对象是内置类型Object的实例,由一些属性来定义的,并且这些属性拥有独特的定义,比如是否可写等等。属性也是其他对象,方法(函数类型)或者基本类型(boolean number string Symbol undefined null)的容器。
当然,还有许多内置对象,用于各种方面以丰富JS的语义
上面是一些总结性的陈述,具体是怎么构建对象的,原型链又是什么,我们继续看官方文档
首先,我们研究通过原型创建的对象
对于这种对象,我们需要通过constructor来构造,然后再声明对象的属性。constructor是一个拥有"prototype"方法的函数,我们通过给函数传入参数来创造新的对象,例如new Date(2009,11)
创造了一个新的日期对象,如果不使用new关键字,那么相当于仅仅执行函数
通过原型创建的对象中,有一个对其constructor的函数的prototype属性的隐含的引用,我们叫做对象原型
constructor的prototype中,也会有这样的引用,指向下一个constructor的prototype,我们叫这个原型链,引用属性时,也会基于这个原型链从浅到深依次查找(原文英文非常绕,想起了GRE题目)
通过Object.create()创建
根据文档,该方法可以直接创建基于指定原型的对象
var a = {}, b = Object.create(a)
b.__proto__ === a //true
通过字面量(Object Literal)创建
var a={id:0}
a.__proto__ === Object.prototype
a.proto===Object.prototype
通过函数创建
var F = function(){}
var F0 = new F
函数的创建
常规函数
我们在chrome环境中做一个实验,创建一个空函数F,然后用这个函数构造一个对象F0,于是有
const F = function(){}
var F0 = new F
F0.constructor === F//true
F0.__proto__ === F.prototype//true
F.prototype.constructor === F//true
F.__proto__ === Function.prototype//true
F.prototype.__proto__ === Object.prototype//true
这里简单地解释一下F和F0的原型链,函数对其原型(F.prototype
)进行包装构造对象F0,F0.__proto__
指针指向函数原型(F.prototype
)
原型链上,Function >---> F >---> F0,Object >---> Function.prototype
那么,既然JS中的“类”仅仅是Function类型的实例对指定原型的包装,那么我们可以任意构造一个对象,将它作为原型,来构造新的对象吗?
显然可以:
var someObject = {
arr:[],
func(a){this.b=a},
b:0
}
var someFunction = function (){}
someFunction.prototype = someObject
var c1 = new someFunction, c2 = new someFunction
c1.__proto__===someObject //true
c1.__proto__ === c2.__proto__ //true
c1.b === c2.b//true,引用的都是原型属性
c1.func(1)
c1.b === c2.b//false
c1.hasOwnProperty('b')//true 修改成自身属性
//原型对象的属性最好都是基本类型
c1.arr.push(1)//1
c2.arr.length//1
问题来了,怎么实现c1和c2不共享arr呢,那么就要引入constructor
var someFunction = function (){
this.arr = []
}
someFunction.prototype = someObject
var c1 = new someFunction, c2 = new someFunction
c1.hasOwnProperty('arr')//true
c1.arr.push(1)
c2.arr.length//0
那么问题又来了,函数的constructor能不能是另一个函数
var someFunction = function (){}
someFunction.prototype = someObject
someFunction.prototype.constructor = function(){
this.arr=[]
}
var c1 = new someFunction, c2 = new someFunction
c1.hasOwnProperty('arr')//false
原因其实在于someFunction.prototype的确是函数绑定的原型没错,但是修改原型的constructor引用(原本指向函数本身),并没有修改someFunction函数本身,执行new someFunction
时,只会使用函数本身来构造,所以即使构造出的对象有c1.__proto__.constructor === someConstructor
,someConstructor也起不到任何构造函数的作用
立即执行函数
函数的定义和执行放在一起,可以理解为一个代码段,有两种方式,常见于各种js库中
;(function(){
...
}())
;(function(){
...
})()
通过Function定义
var F= new Function('a','b','return a+b')
F(1,2)//3
函数的继承
根据上面的分析可知,JS中的继承只是基于原型链的复制,继承的并不是函数而是对象,而且原型链顾名思义不支持多重继承(一个对象的原型指针只能指向另一个对象),我们可以从CryptoJS库中分析继承的实现
var Base = C_lib.Base = (function () {
return {
/**
* Creates a new object that inherits from this object.
*
* @param {Object} overrides Properties to copy into the new object.
*
* @return {Object} The new object.
*
* @static
*
* @example
*
* var MyType = CryptoJS.lib.Base.extend({
* field: 'value',
*
* method: function () {
* }
* });
*/
extend: function (overrides) {
// Spawn
var subtype = create(this);
// Augment
if (overrides) {
subtype.mixIn(overrides);
}
// Create default initializer
if (!subtype.hasOwnProperty('init') || this.init === subtype.init) {
subtype.init = function () {
subtype.$super.init.apply(this, arguments);
};
}
// Initializer's prototype is the subtype object
subtype.init.prototype = subtype;
// Reference supertype
subtype.$super = this;
return subtype;
},
/**
* Extends this object and runs the init method.
* Arguments to create() will be passed to init().
*
* @return {Object} The new object.
*
* @static
*
* @example
*
* var instance = MyType.create();
*/
create: function () {
var instance = this.extend();
instance.init.apply(instance, arguments);
return instance;
},
/**
* Initializes a newly created object.
* Override this method to add some logic when your objects are created.
*
* @example
*
* var MyType = CryptoJS.lib.Base.extend({
* init: function () {
* // ...
* }
* });
*/
init: function () {
},
/**
* Copies properties into this object.
*
* @param {Object} properties The properties to mix in.
*
* @example
*
* MyType.mixIn({
* field: 'value'
* });
*/
mixIn: function (properties) {
for (var propertyName in properties) {
if (properties.hasOwnProperty(propertyName)) {
this[propertyName] = properties[propertyName];
}
}
// IE won't copy toString using the loop above
if (properties.hasOwnProperty('toString')) {
this.toString = properties.toString;
}
},
/**
* Creates a copy of this object.
*
* @return {Object} The clone.
*
* @example
*
* var clone = instance.clone();
*/
clone: function () {
return this.init.prototype.extend(this);
}
};
}());
其中,C_lib可以看做空对象{}
,create就是Object.create(上面的polyfill过于繁琐,MDN上有简单的版本),我们可以看到这里使用一个立即执行函数包裹了Base对象,并给出了extend方法,可以返回一个继承于Base的对象var extended = Base.extend({})
,其关键在于extended本身是一个对象,但是extended.init是一个构造器,可以以这个对象为原型构造实例,生成的实例有指针可以指向原型对象和父对象。可以说完美实现了ES5的思想
var extended = Base.extend({mixinProp:1})
(new extended.init).mixinProp//1
(new extended.init).__proto__ === extended//true
(new extended.init).__proto__.$super === Base//true
Tips
- 不要用等于号判断你不知道类型的对象的理由:
[1]==true//true
[0]==true//false
- NaN不等于任何东西,包括自己
- Object.create(null) 可以创建一个全空对象,不在任何原型链上
- 因为null的二进制表达全部是0,而typeof会根据该值的前三位是不是0来判断是否是对象,所以null的类型是对象,这其实是一个bug,null是属于六种内置类型之一的,结果应该是"null"
typeof null //"object"
- 只要定义的不是匿名函数,那么函数就会自动获得name属性
var F = function() {};
F.name // "F"
var F = function FF() {};
F.name // "FF"
- 不要用Function对象定义函数,原因和不使用eval函数一样
- ES5无法完美继承某些内部类,比如Array,也就是说,用ES6写好后用babel转ES5仍然会出bug:https://babeljs.io/docs/en/learn/#ecmascript-2015-features-subclassable-built-ins
// ES5
function D() {
Array.apply(this, arguments);
}
D.prototype = Object.create(Array.prototype);
var d = new D();
d[0] = 42;
d.length; // 0
官方解释:
Partial support
Built-in subclassability should be evaluated on a case-by-case basis as classes such as HTMLElement can be subclassed while many such as Date, Array and Error cannot be due to ES5 engine limitations.
https://codeburst.io/various-ways-to-create-javascript-object-9563c6887a47
https://dmitripavlutin.com/6-ways-to-declare-javascript-functions/
http://www.ecma-international.org/ecma-262/6.0/index.html
https://www.geeksforgeeks.org/advanced-javascript-backend-basics/
https://www.css88.com/archives/7383
https://babeljs.io/docs/en/learn/#ecmascript-2015-features-subclassable-built-ins