你不知道的JavaScript-上卷 读书笔记-part2

书评

豆瓣 这本书很适合初级前端开发者上升至中级前端开发者,很好的阐述了JavaScript的闭包,原型,类,编译,赋值的问题.而且这还是上卷,还会有中卷,下卷,等等之类的.我会从这本书里选取一些比较重要的内容放在这篇文章当中(实际上这本书全部内容都重要). let’s do it

对象

类型

JavaScript有六种主要类型

  • string
  • number
  • boolean
  • null
  • undefined
  • object

简单基本类型(string,number,boolean,null,undefined)本身并不是对象,null有时候会被当做一种对象类型,但其实这是一个语言本身的bug,typeof null 的时候会返回字符串”object”,实际上null本身是基本类型.JavaScript万物并不都是对象.JavaScript本身有许多特殊的对象子类型,我们可以称之为复杂基本类型.函数就是对象的一个子类型.数组也是对象的一种类型,具备一些额外的行为.

JavaScript内置对象

JavaScript还有一些对象子类型,通常被称为内置对象

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

在JavaScript中,这些内置函数可以当做构造函数.JavaScript在访问对象属性的时候会自动把字符串字面量转换为一个对象 比如

var str="hello" 
str.length;//转换为String对象 

之所以能够访问到属性和方法,是因为引擎把字面量转换为String对象,所以可以访问属性和方法.

内容

存储在对象容器内部的是这些属性的名称,他们就像指针(从技术角度来说就是引用)一样,指向这些值的真正存储位置.实际上这里要补充一些

var b={a:1}; 
var c=b.a; 
c=4; 
console.log(b);//{a:1} 
console.log(c);//4 

首先我们知道基本类型,string,number,boolean这些是单独开辟一个空间存储的 就算引用也不是说引用地址而是直接开辟一块新的空间,但是对象不同了

var s={a:233}; 
var vv=s; 
vv.a=555; 
console.log(vv.a);//555 
console.log(s.a);//555 

对象仅仅是引用,包括你在函数中传参也是一样的

var obj={a:1} 
function setObj(o){ 
   o.a=444; 
} 
setObj(obj) 
console.log(obj);//{a:444} 

在对象中,属性名永远都是字符串,如果你使用string(字面量)以外的其他值作为属性名,那它首先会被转换为一个字符串.即使是数字也不例外.虽然在数组下标中使用的的确是数组,但是在对象属性名中数字会被转换为字符串

属性与方法

从技术角度来说,函数永远不会”属于”一个对象,所以把对象内部引用的函数称为”方法”似乎有点不妥.就算是this也仅仅是在运行的时候根据调用位置动态绑定的.所以函数与对象的关系最多也就是简介关系.即使在对象中声明一个函数表达式,这个函数也不会”属于”这个对象——它们只是对于相同函数的多个引用.

var myObject={ 
   foo:function(){ 
       console.log("foo") 
   } 
}; 
var someFoo=myObject.foo; 
someFoo;//function foo(){} 
myObject.foo;//function foo(){} 

数组

以字符串的形式往数组中添加属性length是不会变的.但是属性会存在

var myArray=["hello",2,"world"]; 
myArray.bar="bar" 
myArray.length;//3 
myArray.bar;//bar 

对象的复制

通过上面我们可以看到,js的对象默认都是引用的,但是基本属性都是直接开辟内存不会去引用.来看这一段代码

var b={arr:[1,2,3,4,5],s:"str",bool:true} 
var c=b.arr; 
c.push(6); 
b;//{arr:[1,2,3,4,5,6],s:"str",bool:true} 
c;//[1,2,3,4,5,6] 

var z={}; 
for(var k in b){ 
  z[k]=b[k] 
} 
z.s="zStr"; 
z.bool=false; 

z.arr.push(7); 
z;//{arr:[1,2,3,4,5,6,7],s:"zStr",bool:false} 
c;//[1,2,3,4,5,6,7] 
b;//{arr:[1,2,3,4,5,6,7],s:"str",bool:true} 

但是如果我们直接访问对象中的属性,那么访问到的就是地址而不是引用了.再来看一段代码

var b={arr:[1,2,3,4,5],s:"str",bool:true} 
var over={}; 
for(var k in b){ 
if(Object.prototype.toString.call(b[k])=="[object Array]"){ 
       b[k].forEach(function(e,i){over[k]=[];over[k][i]=e}) 
   } 
} 
over.arr.push(666) 
over.arr;//arr:[1,2,3,4,5,666] 
b.arr;//arr:[1,2,3,4,5] 

还有一种复制方法,那就是利用JSON来解析出一个一模一样的对象,但是这样做必须要保证这段JSON文件是安全的.

var newObj=JSON.parse(JSON.stringify(b)) 
newObj.arr.push(7777); 
newObj.arr;//arr:[1,2,3,4,5,7777] 
b.arr;//arr:[1,2,3,4,5] 

存在性

我们可以在不访问属性值的情况下判断对象中是否存在这个属性

var myObject={ 
   a:2 
}; 

("a" in myObject);//true 
("b" in myObject);//false 

myObject.hasOwnProperty("a");//true 
myObject.hasOwnProperty("b");//false 

in操作符会检查属性是否在对象以及[[Prototype]]原型链中.相比之下,hasOwnProperty(…)只会检查属性是否在myObject对象中,不会检查[[prototype]]链

类/继承描述了一种代码的组织结构形式——一种在软件中对真实世界中问题领域建模方法.面向对象编程强调的是数据与操作数据行为本质上是互相关联的(当然,不同的数据有不同的行为),因此好的设计就是把数据以及和它相关的行为打包(或者说封装)起来,这在正式计算机科学中有时被称为数据结构.

举例来说,用来表示一个单词或者短语的一串字符通常被称为字符串,字符就是数据,但是你关心的往往不是数据是什么.而是可以对数据做什么.所有可以应用在这种数据上的行为(计算长度,添加数据,搜索)都被设计成String类的方法.所有字符串都是String类的一个实例,也就是说它是一个包裹(封装),包含字符数据和我们可以应用在数据上的函数.

“汽车”可以被看做”交通工具”的一种特例,后者是更广泛的类.我们可以在软件中定义一个Vehicle类和Car类来对这种关系进行建模.

Vehicle的定义可能是包含推进器(引擎),载入能力等等,这些都是Vehicle的行为.我们在Vehicle中定义的是(几乎)所有类型的交通工具(飞机,火车,汽车)都包含的东西.在我们的软件中,对不同交通工具重复定义”载入能力”是没有意义的.相反,我们只在Vehicle中定义一次,定义Car时,只要声明它继承(或者扩展)了Vehicle这个基础定义就行,Car的定义就是对通用Vehicle定义的特殊化.

虽然Vehicle和Car会定义相同的方法,但是在实例中的数据可能是不同的,比如每辆车独一无二的VIN(车辆识别号码)

类的另一个核心概念就是多态.这个概念是说父类的通用行为可以被子类用更特殊的行为重写.实际上相对多态性允许我们重写行为中引用基础行为类也是一种设计模式.一个类就是一张蓝图.为了获得真正可以交互的对象,我们必须按照类来建造(实例化)一个东西,这个东西通常被称为实例,有需要的话,我们可以直接在实例上调用方法并访问其所有的公共数据属性

写到这里,我突然想到了对自己某次开发实践中的经验补充,那次实践是做一个筛选栏(下拉栏)然后根据下拉的选项不同来获得不同的地图数据首先那些下拉框都是一个大类把它定义为PullDown 这个PullDown有一些prototype方法. PullDown.protorype.Onchange=function(){} 那么每一个下拉框都是一个子类 每一个子类都有这个Onchange方法.当子类变动的时候可以把this.val存储起来到子类的属性中 那么搜索按钮是一个单独类 因为它只有click事件不用去处理变动问题 当它点击了click事件,那么就遍历PullDown的子类获取到所有的val 然后通过搜索按钮类的PULLAJAX方法来获取结果 获取到结果后再把结果显示到地图显示类(showMapClass) 地图显示类也有一些方法 比如根据click传递过来的参数显示地图 中间的数据交换可以用PUB-SUB方式来获取数据. 如果当中牵扯到很多的数据交换 那么可以用中介者或者单独用个类来保存数据 遍历PullDown子类可以用一个工厂模式 每次新建一个子类的时候都保存到工厂模式的 allObject中 从这里我发现了一个问题就是 以后设计软件不能直接动手干了 要先分析然后再动手 如果就是几十行代码的一个文件就没有必要用类了..因为会带来很多不必要的代码 所以还是要分析。。 而分析这块..我还是处于空白状态。。。

JavaScript中函数无法(用标准,可靠的方法)真正的复制,所以只能复制对共享函数的引用(函数就是对象),如果你修改了共享函数对象,比如添加了一个属性,那么引用这个函数的所有变量/对象都将受到影响

原型

前段时间自己写了一篇文章 稍微分析了一下可以参考

JavaScript访问属性的时候会调用一个[[get]]方法,当这个方法在当前对象找不到该属性那么就会顺着[[prototype]]向上一级对象去找.用in操作符来检查属性在对象中是否存在时,同样会查找对象整条原型链(无论属性是否可枚举)

Object.prototype

所有普通的[[prototype]]链最终都会指向内置的Object.prototype所有我们才能在没有那些toString方法的实例上调用这个方法

JavaScript中的类

JavaScript只有对象.

类函数

function Foo(){ 

} 

var a=new Foo(); 

Object.getProtorype===Foo.Prototype;//true 

在面向类的语言中,类可以被复制多次,而在JavaScript中,没有类似复制的机制.你不能创建一个类的多个实例,只能创建多个对象,他们的[[prototype]]是同一个对象.但是在默认情况下并不会进行复制,因此这些对象之间并不会完全失去联系,它们是互相关联的.

new Foo()会生成一个新对象(这里称为a),这个新对象的内部链接[[prototype]]关联的是Foo.prototype对象.最后我们得到两个对象,它们之间相互关联.就这样,我们没有初始化一个类,实际上我们并没有从”类”中复制任何行为到一个对象中,只是让两个对象相互关联.实际上new Foo()这个函数调用实际上没有直接创建关联,这个关联只是一个意外的副作用,但是间接的完成了我们的目的:一个关联到其他对象的新对象

构造函数

function Foo(){} 

Foo.prototype.constructor===Foo;//true 

var a=new Foo(); 
a.constructor===Foo;//true 

Foo.prototype默认有一个公用并且不可枚举的属性.constructor,这个属性引用的是对象关联的函数.实际上a本身没有这个.constructor属性,虽然a.constructor的确指向Foo函数,但是这个属性并不表示a由Foo”构造”.实际上Foo和程序中的其他函数没有任何区别,函数本身不是构造函数,然而,当你在普通的函数调用前面加上new关键词后,就会把这个函数调用变成一个”构造函数调用”.实际上,new会劫持所有的普通函数并且构造对象的形式来调用它.换句话说,在JavaScript中对于构造函数的最准确解释是:所有带new的函数调用.函数不是构造函数,但是当且仅当使用new时,函数调用会变成”构造函数调用”

constructor

function Foo(){} 
Foo.prototype={};//创建一个新原型对象 
var a1=new Foo(); 
a1.constructor===Foo;//false 
a1.constructor===Object;//true 

a1本身没有这个.constructor属性.于是会委托到[[prototype]]上的Foo.prototype.但是这个对象也没有.constructor属性(不过默认Foo.prototype对象有这个属性),所以会继续委托,这次会委托给链顶端的Object.prototype这个对象有.constructor属性.即指向Object()函数要修复这个问题只需要在Foo.prototype加上这个属性即可

Object.defineProperty(Foo.prototype,"constructor",{ 
   enumerable:false,//不可枚举 
   writable:true,//可写 
   configurable:true, 
   value:Foo 
}) 

实际上对象的.constructor属性会默认指向一个函数,这个函数可以通过对象的.prototype引用.但是最好记住一点”constructor并不表示被构造”

原型继承

下面来写一段典型的原型风格继承

function Foo(name){ 
   this.name=name; 
} 
Foo.prototype.myName=function(){ 
   return this.name 
} 

function Bar(name,label){ 
   Foo.call(this,name) 
   this.label=label; 
} 

//我们创建了一个新的Bar.prototype对象并关联到Foo.prototype 
Bar.prototype=Object.create(Foo.prototype); 

//注意!现在没有Bar.prototype.constructor了 
//如果你需要这个属性的话可能需要手动修复一下它 

Bar.prototype.myLabel=function(){ 
   return this.label 
} 

var a=new Bar("a","obj a"); 

a.myName();//"a" 
a.myLabel();//"obj a" 

用 Object.create()会凭空创建一个”新”对象并把新对象内部的[[prototype]]关联到你指定的对象(本例中是Foo.prototype);

//和你想要的机制不一样 
Bar.prototype=Foo.prototype; 

//基本满足你的需求,但是可能会产生一些副作用 
Bar.prototype=new Foo(); 

Bar.prototype=Foo.prototype并不会创建一个关联到Bar.prototype的新对象它只是让Bar.prototype直接引用Foo.protorype对象,因此如果修改Bar.protorype属性如添加方法或者删除属性,都会直接修改Foo.prototype本身.Bar.protorype=new Foo()的确会创建一个关联到Bar.prototype的新对象.但是它使用了Foo()的”构造函数调用”,如果函数Foo有一些副作用(比如写日志,修改状态,注册到其他对象.给this添加数据属性..)就会影响到Bar()的后代.因此要创建一个合适的关联对象,我们必须使用Object.create()这样做的唯一缺点就是需要创建一个新对象,然后把旧对象抛弃掉,不能直接修改已有的默认对象.

检查”类”关系

instanceof判断

function Foo(){ 

} 
Foo.prototype.blah=..; 
var a=new Foo(); 

a instanceof Foo;//true 

这个方法只能处理对象(a)和函数(带.prototype引用的Foo)之间的关系.如果你想判断两个对象(a,b)直接是否通过[[prototype]]联系,只用instanceof无法实现.

isPrototypeOf

Foo.prototype.isPrototypeOf(a);//true 

比如判断c的原型链是否有b

b.isPrototypeOf(c) 

这个方法不需要使用函数(“类”)直接用b与c之间的对象引用来判断它们的关系.

getProtorype

ES5的标准方法

Object.getProtorype(a)===Foo.protorype;//true