记录一下自己学习javascript的笔记和总结.
类的实现
new语义
正常我们使用new操作符创建对象的时候, 其实底层是怎么做的呢, 看下面的代码:
var Foo = function(xxx) { |
使用new就等价解释器帮我们做了这样子的事情
var Foo = function(xxx) { |
也就是说, 使用new的时候, 会创建一个对象, 切换到对象的执行环境初始化并返回. 注意的是, 该对象的prototype就是当前构造类(函数)的prototype. 这样子得到的对象foo是一个使用Foo类的prototype创建的实例.
类的实现
prototype进行实现就等价于类的实现. 有一点注意的是, 如果将prototype进行直接的赋值, 需要设定constructor属性. 因为如果我们将代码写成下面的形式, 系统会自动在prototype创建出来的时候, 帮我们如下设定Foo.prototype.constructor=Foo
, 而使用覆盖的话, 就没有这个设定了.
//自动设定出来constructor的关系 |
继承的实现方式
js对象的找属性的方式是, 如果自己身上有属性直接返回, 否则使用prototyp的对象进行查找. 所以如果我们将一个类的prototype设定为一个新的对象, 且这个对象prototype和父类是一致, 那么找不到的方法其实就可以到父类中查找.
var Base = function(xxx) { |
为什么不直设置Derived.prototype = Base.prototype
? 这样子设定的话, 对子类的prototype修改就会导致父类prototype也被错误的修改, 所以需要加入一个中间层, 这里就是一个新的对象. js里面规定, 如果自己设定了属性, 且prototype中也有, 那么prototype中的属性被隐藏. 所以这样子设定, 在Derived.prototype中加入新的属性, 完全是ok的, 因为不会影响到Base.prototype, 且新的属性会隐藏掉父类的属性.
参考文档
BOM
BOM表示浏览器对象模型(Browser Object Module), 使用BOM可以控制开发人员访问浏览器相关的内容, 比如操作窗口, 得到当前页面信息等等。
BOM模型以windows对象为依托, 表示浏览器窗口和页面可见区域. 同时, 非常需要留意的是, windows对象也是默认的ECMAScript中的Global对象, 所有的全局变量和函数都是它的属性, 且所有的原生构造函数都存在于他的命名空间下面.
比如常用的windows接口有几个:
- innerHeight/innerWidth/outerWidth/outerHeight表示窗口的信息
- open打开新的窗口
- setTimeout/setInterval用来设定定时器
- alert/confirm/prompt用来弹出对话框
location
对象比较特殊, 既可以访问于window.location
也可以访问于document.location
, 两者是完全一致的.
其中locatoin.href
对象表示当前页面完全的地址, 如果设置该参数, 就会修改当前窗口的地址(跳转), 比如设定location.href="http://xxxx.com"
调用location.href
切换地址会让浏览器认定当前是切换了场景, 如果不保留当前调用堆栈信息, 使用location.replace
方法.
也可以使用location.reload
来重新刷新当前的页面.
jQuery lib
jQuery is just a javascript framework
jQuery简化了我们操作DOM的方式, 其中最经典的$
符号就是表示jQuery本身, 本质上是一个函数, 也是一个入口点.
进行查询的一些方式, 和CSS的选择器是一样的
$('tag') |
还可以方便的进行相关元素的获取
$('tag').parent() |
- 使用
toggle
方法可以切换一个节点的可见性, 同理使用toggleClass
可以修改一个节点是否有一个类. - 使用
text
方法用来设置元素的内部文本数值. - 使用
append
用来加入一个节点到最后一个孩子节点上, 相反, 使用prepend
方法加入节点到第一个孩子节点上. - 对应的, 如果是兄弟之间的插入的话, 就是使用
before
和相反的after
使用css
方法可以设置css, 当然也可以得到css属性, 如果说明的是, 这里设置的css相当于是设置了style属性, 而不是修改了css文件, 所以你需要权衡一下是否应该直接用css实现, 而不是使用jQuery.
jQuery返回的对象还是可以使用each
方法对当前的的节点进行遍历:
$(.node).each(function(index) { |
加载脚本的实际也很重要, 一般, 可以将script放在head标签中, 这样子开始的时候就加载或者下载脚本, 但是这样子有个问题, 如果脚本运行的早, 可能DOM还没有创建出来, 那么操作DOM的代码就会无效.
但是如果将脚本放在body的后面, 那么下载的过程就会滞后, 也会导致整个页面加载速度变慢. 所以一种有效的方式是, 在document.ready
事件后处理相应的逻辑.
jQurty提供了相应的方法:
$(function() { |
jQuery监控事件
在chrome中提供了监控一个node事件的方式, 使用monitorEvents(node)
来监控一个节点的所有时间信息. (需要注意的是, 只可以在chrome的终端中使用)
使用on
方法可以监控节点的事件:
$('#my-input').on('keypress', function() { |
使用on
方法时候需要注意, 如果对多个元素设置on
方法的监控, 在回调函数中将无法使用$(this)
, 因为$(this)
得到的是所有的查询到的元素, 所以我们需要使用别的方式, 索性event对象可以有效解决
$( 'article' ).on( 'click', function( evt ) { |
别的几个有用的event数据信息有:
preventDefault()
屏蔽掉默认的处理行为, 比如click事件的点击效果.keyCode()
得到输入的key信息pageX
点击位置的坐标type
事件类型, 分析多个时间的时候有效
jQuery还有几个简写的方式, 比如keypress(cb)
就是等价于on('keypress, cb)
, 基本上所有基本事件都有对应的简写形式, 使用的时候需要留意, 怎么简单怎么来.
加入事件的时候可能存在一个问题, 如果加入时间的节点在开始时候还没有, 那么加入就无效怎么办. 这种情况很有可能发生, 比如给一个ul中的li加入事件, 但是后来又新添加了新的li项目, 这个项目因为在设置event之后调用, 所以没有设置成功.
为此, 使用Event Delegation
的概念, 将event设置到父节点上. on
方法的第二个参数用来设置当前时间关注着是自己的什么子节点.
// 这样子, 点击ul的时候, 会check一下是不是点击了自己的li子节点 |
使用严格模式
严格模式是为了规范js的使用方式, 将一些tricky和errorprone的地方都规约掉.
使用方式是在文件的开头写上"use strict";
, 或者是在函数开头第一句的地方输入相同的内容, 那么规约的就是函数作用域而已.
具体的说明可以参考文档, 总计几点大概是:
- 不予许定义全局的定义变量.
- 禁止使用with.
- eval和argument的时候更加规范. 大致是, eval不会将引入的代码放到全局, argument的参数修改不会改变argument的数值.
- 更加安全. 不会自动将this变为object对象, 不会提供arguments.callee等特性.
- 为未来ECMAScript铺路, 将来版本预留的关键字也不允许使用.
参考文档
Immediately-Invoked Function Expression(IIFE)
js里面函数是有执行环境的概念的, 所以有的时候, 我们需要将一些私有东西放在函数中进行包裹, 然后有希望立刻进行执行得到运行结果(很多时候也是会定义一个匿名的函数, 这样子更加简洁). 参考文档
但是js里面调用这种立刻运行函数的时候需要消息一些坑. 如果是函数声明的话, js中理解是一种语句, 是不可以直接运行的. 但是如果是函数表达式的话, 那么可以运行:
// error, 这是function declaration |
有没有办法绕开js判定一个函数逻辑意义的概念的方式呢? 有的, 就是使用()
. 因为在js中规定, 括号中不可以包含语句, 而只能是表达式. 所以在括号中调用函数, js引擎知道这是一个表达式, 所以调用ok.
// 两种都是ok的, 推荐使用上面一种 |
这种使用方式, 还可以用来解决一个经典的闭包延迟解析的问题.
// 解析的时候i都是最后的数值, 因为闭包调用的时候是分析当前i的数值, 而不是将i存到环境中 |
解决的方式, 就是想办法将i放入到执行环境中, 多一个间接层, 自然想到加入一个函数调用(包裹执行环境):
var elems = document.getElementsByTagName( 'a' ); |
可以将这种模式理解为, 使用函数立刻调用的方式创建出来一个新的执行环境, 这个执行环境会将i参数进行保存, 进而不同的循环运行中使用不同的运行环境, 也就是不同的i参数了.
还有一个有意思的实现模式, 叫做Module Pattern, 个人理解就是进行数据的private修饰. js里面是没有直接语言上的private类成员变量的概念. 所以我们思路也是加入一个中间层:
- 构造一个执行环境, 对外访问不到执行环境中的变量.
- 执行环境返回的对象中值有我们提供的接口, 而没有执行环境中的不对外暴露的接口.
- 使用函数构造一个执行环节, 相当于一个工厂方法, 但是立刻调用.
var count = (function() { |
MVC模式
MVC模式在web开发中用的比较多. 考虑一下, 我们的数据就是model, 可能从DB中得到, 可能是一些固定的值, 需要渲染成为html, 那么html相关的操作就是view, 但是如果用户需要操作渲染的elem进而改变数据, 就需要view和model进行交互, 这就产生了偶尔. 所以引入controller的这一层级.
原则上: model和view之前不可以产生之间的调用关系, 所有的接口应该使用controller进行中转和封装. 这样子结构更加灵活. 我们可以改变接口定义来改变view的效果.
标准实现
直接贴一下我实现的代码:
|
window.onload = function () { |
这里更多是实现view的接口, view的接口根据当前octopus(controller)
提供的接口计算出所有的需要显示的list数据. 而catView
就是根据当前currrentCat
的数据来渲染出当前需要显示的cat信息. 这里一个比较好的实现是:
- 用
init
方法进行初始化, 比如进行元素选择, 构造相应方法等逻辑, 然后调用render进行渲染. - 将所有的数据看成一个Object的List组织, 当前的数据就是一个指针数据的改变.
使用KnockOutJS实现MVVM
上面的实现还是有些麻烦, 比如要在octopus
中定义很多的函数来连接view和model的关系, 比如一个model数据变化了, 需要加入函数, 修改数据, 通知view进行重新渲染. 如果逻辑复杂, 需要写很多的这种方法.
可以使用KnockOutJS库来改进. 这个库主要的功能就是使用observer模式建立view和model的关系. 在html中给特定的标签加入特定的描述信息, 该信息和对应的ViewModel
(controller的变种, 反正就是连接用的)的数据建立关系, 一旦对象数据变化, 那么html就会重新渲染(框架帮我做了这些事情). 这样子的好处就是:
- 基本不用写view的代码了. 因为view的代码都嵌套到html中进行描述, nice! 本身html就是一种render的描述方式, 通过扩展和对应的数据建立关系.
- 数据的修改会通知对应观察者的html进行修改, 相当于自动进行re-render.
修改后的代码为:
|
window.onload = function () { |
代码明显简洁了很多, 个人总结的几个要点(目前该库掌握还处于基础阶段, 可能总结的范式不是非常准确):
- 在html中定义了需要监听改变的tag, 可以监听改变的变量使用
ko.observable
类型生成. - 将
Cat
抽象成一个对象, 其中的数据都是可以监听修改的, 这样子的好处是, 如果这个对象内容被变化, 那么所有成员数据如果是ko.observable
的, 都认为变化, 体现了整体动全局动的概念. 而单体属性, 比如clickCount
也设置为ko.observable
是为了可以单独控制这个变量的修改, 对特定的html产生变化. - 可以将
ko.observable
看做一个proxy, 就是一个变量的代理. 不同的就是接口层面上, 因为要知道数据修改, 只能使用调用函数的方式通过set语义进行修改. - 对于
ko.observableArray
类型的catList
对象就好理解了, 本身是一个需要监听改变的数据(可能加入新的cat), 然后其保存的数据就是普通的cat对象(并不是观察的对象), 只是一个集合而已. 而我们的currentCat
对象是一个指向当前的Cat
指针变量, 因为需要监听这个指针变化, 所以也是一个ko.observable
类型的变量.
AngularJS
更加强大的框架AngularJS, 提供了更加强大的MVC功能.
大致学习了一下, AngularJS和KnockOut挺相似, 都是使用扩展html的方式来实现view和数据的自动binding:
- view就是一个html, 使用template的方式来组合相应的变量数据.
- controller是一个用来和view组合使用的概念, 比如代码
<div ng-include="'views/cat_list.html'" ng-controller="CatControllerCtrl as cat"></div>
就可以将一个view和对应的controller进行组合, 所有view中的teamplate的变量获取都是从controller中进行获取. - service是隶属于controller中的, 更多的是表示纯数据的包装类, controller可以共用多个service, 这种组合关系一下就让controller变得强大, 因为不同的controller可以共用不同的service.
- module又是一个更加上层的概念, 所有的view, controller, service, routing可以构成一个组件, 这个组件可以共用, 也就是说我们的app是一个moudle, 同时我可以利用别的写好的moudle进行扩展.
- 感觉AngularJS强大的地方在于非常的灵活, template机制也非常强大(不需要想KnockOut那样子定义observable), 组合的使用方式非常便于扩展.
重新实现了一下CatList
的程序, 因为不是很熟悉, 所以实现比较低端, 只是一个概念.
<!-- cat_list.html view --> |
// cat_controller.js controller |
使用js来操作dom
html加载完成的时候, 会调用系统的window.onload
方法, 通过链式添加更多的函数到相应方法中:
function addLoadEvent(func) { |
这样子就可以多次调用当前方法来加载多个load函数.