JavaScript

原型

之前写过一个使用:javascript对象和原型 ,虽然在那个项目里这么用了,其实还是一知半解,这几天重新翻了下《你不知道的JavaScript 上卷》

首先,Javascript是面向对象的语言,它没有类;

翻出n久前做的一个笔记:

  1. 所有的函数对象都有一个prototype属性,这个属性引用了一个对象,就是原型(对象);
  1. 普通对象的内部_proto_指向它构造函数的prototype,原型对象也是普通对象的一种;

  2. 原型对象主要用于继承,js在创建对象时,都有一个_proto的内置属性,用于指向创建它的函数对象的原型对象prototype

  3. 原型和原型链是js实现继承的一种模型,原型链的形成靠_proto_,而不是prototype;

  4. 能修改prototype指向的对象,为prototype赋予一个不同的对象,可以实现一种简单的继承;

  5. 访问一个对象的原型可以使用ES5中的Object.getPrototype()方法,或ES6中的_proto_属性

然后在chrome浏览器做一个试验

  • 函数有一个特殊特性,所有的函数默认都会拥有一个prototype的公有且不可枚举的属性,它会指向另一个对象,通常称为这个函数的原型对象。就是说,User函数的原型可以通过User.prototype来访问

  • 打印了User.prototype可以看的原型对象默认有一个公有的属性,constructor,这个属性引用的是对象关联的函数,就是说这里 User.prototype.constructor === User;

  • 此外,可以看到通过构造函数调用new User( )创建的对象u,也有一个.constructor属性,指向“创建这个对象的函数”,也就是a.constructor ===User;

  • 构造函数:User函数其实是一个普通的函数,可是为什么它会认为是一个“构造函数”,原因是,在普通的函数前面加上new关键字后,就会把这个函数调用变成一个“构造函数调用”。new会劫持所有普通函数并用构造对象的形式来调用它。就是说函数不是构造函数,但是当且仅当使用new时,函数调用会变成“构造函数调用”

  • 普通对象的内部_proto_指向它构造函数的prototype,原型对象也是普通对象的一种,也就是说

    1
    2
    3
    u.__proto__===User.__proto__ //true
    u.__proto__===User.prototype //true
    u.prototype //undefined,原因,u并不是一个函数对象,它只是一个对象,var u = ....

我们给User.prototype设置了一个toString()函数,u是可以访问到的。

同样的,u.__proto__代表指向它构造函数的prototype,其实也是等于User.prototype.

那什么是原型链?如果一个原型对象的原型不为null,就是原型链,我们可以通过__proto__来遍历原型链(ES6)

那ES5的继承和ES6的继承有什么区别?

ES5的继承是通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。

ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this

具体的:ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。

ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。

如何判断一个变量是对象还是数组?

第一念头是使用typeof,但是是有问题的,因为数组也是对象的一种,这个问题的问法其实应该是,如何判断一个对象是数组

1
2
var b = [1,2,3];
typeof b;//"object"
  • instanceof操作符

    这个操作符是检测对象的原型链是否指向构造函数的prototype对象的。

    1
    2
    var arr = [1,2,3,1];   
    alert(arr instanceof Array); // true
  • 对象的constructor属性

    每个对象都有constructor的属性,指向“创建这个对象的函数”

    1
    arr.constructor == Array //true
  • Object.prototype.toString

    Object.prototype.toString的行为:首先,取得对象的一个内部属性[[Class]],然后依据这个属性,返回一个类似于”[object Array]”的字符串作为结果(看过ECMA标准的应该都知道,[[]]用来表示语言内部用到的、外部不可直接访问的属性,称为“内部属性”)。利用这 个方法,再配合call,我们可以取得任何对象的内部属性[[Class]],然后把类型检测转化为字符串比较,以达到我们的目的。

    1
    2
    3
    4
    5
    6
    function isArrayFn (o) {  
    return Object.prototype.toString.call(o) === '[object Array]';
    }
    var arr = [1,2,3,1];

    alert(isArrayFn(arr));// true

    call改变toString的this引用为待检测的对象,返回此对象的字符串表示,然后对比此字符串是否是’[object Array]’,以判断其是否是Array的实例.

  • Array.isArray()

    ECMAScript5将Array.isArray()正式引入JavaScript,目的就是准确地检测一个值是否为数组。

    IE9+、 Firefox 4+、Safari 5+、Opera 10.5+和Chrome都实现了这个方法。

    但是在IE8之前的版本是不支持的。

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    var arr = [1,2,3,1];
    var arr2 = [{ abac : 1, abc : 2 }];

    function isArrayFn(value){
    if (typeof Array.isArray === "function") {
    return Array.isArray(value);
    }else{
    return Object.prototype.toString.call(value) === "[object Array]";
    }
    }
    alert(isArrayFn(arr));// true

    typeof Array.isArray === “function” 来判断浏览器支不支持用isArray,不支持的话用Object.prototype.toString。因为Object.prototype.toString对识别内置对象时往往十分有用,但对于自定义对象就可能识别不出。

Promise

Promise是ES6为了解决异步编程所诞生的,主要用于ajax请求,回调函数,复杂操作判断。

PS:异步操作解决方案:PromiseGenerator、定时器、还有ES7的async

document.visibilityState && document.hidden

PageVisibility API是用来判断页面可见性,即浏览器标签页被隐藏或者显示的判断。

document.visibilityState:

  • visible:页面内容至少部分可见,即可能是已更非最小化窗口的可见标签页
  • hidden:页面内容对用户不可见,即可能是后台标签页或者最小化窗口等
  • prerender:网页内容被预渲染且用户不可见
  • unloaded:网页被卸载

一般我们使用document.hidden就可以满足需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function getHiddenProp(){
var prefixes = ['webkit','moz','ms','o'];

// if 'hidden' is natively supported just return it
if ('hidden' in document) return 'hidden';

// otherwise loop over all the known prefixes until we find one
for (var i = 0; i < prefixes.length; i++){
if ((prefixes[i] + 'Hidden') in document)
return prefixes[i] + 'Hidden';
}

// otherwise it's not supported
return null;
}

function isHidden() {
var prop = getHiddenProp();
if (!prop) return false;

return document[prop];
}

http://www.css88.com/archives/6103

此属性需要注意兼容性。

  • 做一个保存草稿的页面,每隔十分钟会自动保存一次,如果在多个页面中打开了同一页面,怎样确保往数据库里存的是用户想要保存的内容。

    sessionStorage + 修改时间戳,做异步写入,然后用事件节流的方式

querySelector和querySelectorAll

这个为H5新特性。

  • querySelector:返回指定元素节点的子树中匹配选择器的集合中的第一个元素,如果没有返回null
  • querySelectorAll:按文档顺序返回指定元素节点的子树中匹配选择器的元素集合,如果没有匹配返回空集合
1
2
3
4
5
6
7
8
9
10
11
12
<div id="test">
<div class="dialog">
<p>123</p>
<span>456</span>
<div>789</div>
<div class="text">452</div>
</div>
</div>

var test=document.querySelector('#test');
var subDivs = test.querySelectorAll('div');
var text = document.querySelectorAll('div[class=text]');

兼容性
IE8+(含),针对mobile更优化

有bug

[译]你所不了解的querySelector

Web相关

XSS

浅析前端安全之 XSS

看了上面那篇文章意识到一个很大的问题,因为我之前没有仔细想这方面的东西。但是我遇到过一次,表单中用户名以html形式渲染<h1>123</h1>,他的名字设置为类似这样,结果json传回来后渲染到页面变成123 标题样式。

解决我方法是,我在输入表单这一块,先encodeURIComponent解析,发给后台,然后对收到json的这个数据使用decodeURIComponent进行解码。

vue中不使用v-html之类,直接 输出,使用转义功能。

要慢慢把以前的项目规范起来,防止漏洞才行,以后也要注意这一方面,有空再和后端探讨一下。

跨域

首先说下我在实际项目中遇到的问题:

  • 一般都是Ajax请求的跨域问题,需要后端配合。
1
2
3
4
5
6
7
$.ajax({
url: 'xxx',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "onBack", // 自定义回调函数名
data: {}
});

​ jsonp缺点:只能实现get一种请求。

遇到的一个错误Uncaught SyntaxError: Unexpected token :

这是因为后台直接给我返回json,而我用jsonp请求,简单说下后台返回的格式:

1
2
// jsonStr:json格式的数据
return "jsonpCallback(" + jsonStr+ ");" ;
1
2
3
4
5
//jsonp
jsonpCallback({
"url": "1234",
"name": "abcd"
})
  • iframe跨域

为什么会遇到这个问题,因为有次项目有个手机绑定uid的功能,需要用到第三方的手机验证登陆模块。

iframe地址为:a.yy.com/…

我的页面地址为:b.yy.com/…

这个时候会产生跨域问题,需要在两个页面里加代码:

1
document.domain = 'yy.com';

今天看到的这篇文章基本把所有跨域和解决方法列得很全面了:

前端常见跨域解决方案(全)

Vue

MVVM

最近被一篇文章刷屏了:别了,jQuery

文末那段话真实醍醐灌耳:

“开发中使用库当然没问题,但前提是不要仅仅只知道怎么使用,更要知道它的工作原理。如果不能超越这些库,那在这些库变成你的拐杖之时,你也会随之“残废”。在使用某个库之前,一定要花时间学习掌握JavaScript和DOM。我们从一开头就强调“知其所以然”的重要性,告诉大家不能满足于仅仅“知其然”。如果使用了库,却不晓得其背后原理,无论对自己还是对产品都将是有害的。”

Ray建议,学习前端开发,本来应该是这个顺序:

  1. 学习JavaScript
  2. 学习Web API
  3. 学习jQuery(或其他框架/库)

但很多人实际上是从#3开始的,然后过了很久才开始#1和#2(甚至从来没想过#1和#2)。结果就是“只知其然,而不知其所以然”。

说来惭愧,我就是那个从#3开始的人,所以现在确实业务代码写了很多,工作似乎都能完成,但是只有自己知道,面试的时候就知道,基础不行。不过没事,总有一天可以行的。

好了,回到正文:MVVM:

MVVMPattern.png

Model–view–viewmodel (MVVM) is a software architectural pattern.

MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。

把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。

  • 常用的MVVM框架有:Angular,Backbone.js和我现在经常用的Vue.js

  • 单向/双向绑定:

    Vue为例,直白一点理解就是,单向绑定意思如下:

    1
    <p>{{data}}</p> //负责显示的是DOM节点可以用{{ }}来引用Model的属性

    双向绑定意思如下:

    1
    p><input v-model="email"></p> // 使用v-model指令,把这里的input和Model里的某个属性做双向绑定
  • 同步

    1
    2
    3
    4
    5
    6
    7
    8
    <ol>
    <li v-for="t in todos">
    <dl>
    <dt>{{ t.name }}</dt>
    <dd>{{ t.description }}</dd>
    </dl>
    </li>
    </ol>

    根据Modal里的todos变化,比如数组增加或减少,这里html会相应的进行DOM节点的增加或减少,完全由数据决定,开发者无需去关注html的DOM节点。

    Vue之所以能够监听Model状态的变化,是因为JavaScript语言本身提供了Proxy或者Object.observe()机制来监听对象状态的变化。但是,对于数组元素的赋值,却没有办法直接监听

  • MVVM的适用范围

    MVVM最大的优势是编写前端逻辑非常复杂的页面,尤其是需要大量DOM操作的逻辑,利用MVVM可以极大地简化前端页面的逻辑。

    然而对于以展示逻辑为主的页面,例如,新闻,博客、文档等,是不建议使用MVVM展示数据,因为这些页面需要被搜索引擎索引,而搜索引擎无法获取使用MVVM并通过API加载的数据

    所以,需要SEO(Search Engine Optimization)的页面,不能使用MVVM展示数据。不需要SEO的页面,如果前端逻辑复杂,就适合使用MVVM展示数据,例如,工具类页面,复杂的表单页面,用户登录后才能操作的页面等等。

你或许不知道Vue的这些小技巧

文章地址:你或许不知道Vue的这些小技巧 Vue 实践过程中的几个问题

  • Vue 数组/对象更新 视图不更新

    这篇文章作者给了个例子,其实还蛮常见的。虽然我暂时没有遇到这个问题…因为我比较常用里面的第三种方法:替换数组/对象…

    由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

    1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
    2. 当你修改数组的长度时,例如:vm.items.length = newLength

    官网文档:数组更新检测

    • Vue 包含一组观察数组的变异方法,会触发视图更新

    • 替换数组

    • 1
      2
      3
      this.$set(this.arr, 0, "OBKoro1"); // 改变数组
      this.$set(this.obj, "c", "OBKoro1"); // 改变对象
      this.arr.splice(newLength)

      当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用
      Object.defineProperty 把这些属性全部转为 getter/setter。

      Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。

      用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。这里需要注意的问题是浏览器控制台在打印数据对象时 getter/setter 的格式化并不同,所以你可能需要安装 vue-devtools 来获取更加友好的检查接口。

      每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

      vue中数组和对象更改后视图不刷新的问题

      总结:数据的改变必须能够触发set方法,否则无法响应数据变化。

  • 一段取值的v-for

    <div v-for="n in 5"><span>这里会被渲染5次,渲染模板</span> </div>

    v-if尽量不要与v-for在同一节点使用(性能):

    v-for 的优先级比 v-if 更高,如果它们处于同一节点的话,那么每一个循环都会运行一遍v-if。

  • Vue滚动行为

    这个问题涉及到官网文档的以下两篇

    HTML5 History 模式

    Vue Rouer 滚动行为

  • v-show和v-if

  1. v-if是删除/添加Dom标签,不占据文档位置,v-show切换css的display属性,控制显示隐藏,还会占据文档Ω位置。
  2. v-if会删除dom标签所以v-if性能消耗会高一些,需要频繁切换的话,使用v-show会好一点。