ruirui's blog

ruirui's 备忘录


  • Home

  • Archives

Vue 动态数据绑定(三)-- 深层数据变化向上传播

Posted on 2017-09-05

任务说明地址

任务描述
这是“动态数据绑定”的第三题。在第二题的基础上,我们再多考虑一个问题:”深层次数据变化如何逐层往上传播”。举个例子。

let app2 = new Observer({
    name: {
        firstName: 'shaofeng',
        lastName: 'liang'
    },
    age: 25
});

app2.$watch('name', function (newName) {
    console.log('我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。')
});

app2.data.name.firstName = 'hahaha';
// 输出:我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。
app2.data.name.lastName = 'blablabla';
// 输出:我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。

观察到了吗?firstName 和 lastName 作为 name 的属性,其中任意一个发生变化,都会得出以下结论:”name 发生了变化。”这种机制符合”事件传播“机制,方向是从底层往上逐层传播到顶层。
这现象想必你们也见过,比如:“点击某一个DOM元素,相当于也其父元素和其所有祖先元素。”(当然,你可以手动禁止事件传播) 所以,这里的本质是:”浏览器内部实现了一个事件传播的机制”,你有信心自己实现一个吗?


所以重点是:深层次数据变化如何逐层往上传播

在任务二中,只能注册监听对象的第一层属性,深层属性的监听未完成。因为每一层的 event 都是独立的,深层对象属性触发的时候可以看出 this.events 里为空。所以要实现 event 之间的通信,使得可以向上传播。 一种方式: 每次进行 new Observer 的时候记录路径 set 时解析路径,虽然可以实现,但是也太残暴了,不符合向上传播的思想。

重点在于“冒泡”,让父级事件知道子级触发了事件,怎么实现事件冒泡呢,冒泡啊冒泡。也就是当触发子级事件时(也就是 set 方法里),能拿到父级对象,触发父级的 Event 对象的 emit 方法。所以思路就理清了,在创建对象的时候,记录父级对象,将父级对象传入子级中,再对属性路径进行整合解析,当触发set时进行冒泡,触发父级相应的事件(又是一个递归))。所以构造函数就需要多加两个参数,一个是路径 path,一个是父级对象 $parent。

最终代码(Demo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
function Observer(data, path, $parent) {
this.data = data;
this.$parent = (typeof $parent === 'undefined') ? null : $parent;
this.makeObserver(data, path);
this.eventsBus = new Event();
}

Observer.prototype.makeObserver = function(data, path) {
var self = this;
for (var i in data) {
if (data.hasOwnProperty(i)) {
var new_path = (typeof path === 'undefined') ? i : (path + '.' + i);
if (typeof data[i] === 'object') {
new Observer(data[i], new_path, self);
} else {
this.getset(i, data[i], new_path);
}
}
}
}


Observer.prototype.getset = function (i, value, path) {
let val = value;
let self = this;
Object.defineProperty(this.data, i, {
configurable: true,
enumerable: true,
get: function () {
console.log('你访问了' + i);
return val;
},
set: function (newval) {
self.bubble(self, path, val, newval); // 事件冒泡
if(typeof newval === 'object') {
new Observer(val);
}
console.log('你设置了' + i + ',新的值为' + newval);
val = newval;
}
})
}

// 往上层传递 沿着父对象往上传递
Observer.prototype.bubble = function (self, path, val, newval) {
if (self === null)
return;
self.eventsBus.emit(path, val, newval);
var parent_path = path.split('.').slice(0, -1).join('.'); // 拆出父级的路径
self.bubble(self.$parent, parent_path, val, newval); //关键点 递归触发父级的emit
}

function Event() {
this.events = {};
}
Event.prototype.on = function (attr, callback) {
if (this.events[attr]){
this.events[attr].push(callback);
} else {
this.events[attr] = [callback];
}
}
Event.prototype.emit = function (attr, val, newval) {
this.events[attr] && this.events[attr].forEach(function(item) {
item(newval);
})
}

Observer.prototype.$watch = function (attr,callback){
// 注册一个监听事件
this.eventsBus.on(attr, callback);
}

let app1 = new Observer({
age: 25,
name: {
firstName: 'rui',
lastName: 'sun'
}
})
app1.$watch('age', function(age) {
console.log(`我的年纪变了,现在已经是:${age}岁了`)
});

app1.$watch('name', function(newName) {
console.log('我的名字变了,新的值是' + newName);
});

console.log(app1.data.age = 100);
console.log(app1.data.name.lastName = 'ruirui');

Vue 动态数据绑定(二)-- 深度监听

Posted on 2017-09-01

任务说明地址

任务描述
这是“动态数据绑定”系列的第二题。在第一题的基础上,我们继续考虑以下难点:
如果传入参数对象是一个“比较深”的对象(也就是其属性值也可能是对象),那该怎么办呢?举个例子。

// 一个“比较深”的对象:某些属性的值也是一个对象
let obj = {
 a: 1,
 b: 2,
 c: {
     d: 3,
     e: 4
 }
}

如果设置新的值是一个对象的话,新设置的对象的属性是否能继续响应 getter 和 setter。举个例子。

let app1 = new Observer({
         name: 'youngwind',
         age: 25
 });
app1.data.name = {
         lastName: 'liang',
         firstName: 'shaofeng'
 };
app1.data.name.lastName;
 // 这里还需要输出 '你访问了 lastName '
app1.data.name.firstName = 'lalala';
 // 这里还需要输出 '你设置了firstName, 新的值为 lalala'

考虑传递回调函数。在实际应用中,当特定数据发生改变的时候,我们是希望做一些特定的事情的,而不是每一次都只能打印出一些信息。所以,我们需要支持传入回调函数的功能。举个例子。

let app1 = new Observer({
         name: 'youngwind',
         age: 25
 });
 // 你需要实现 $watch 这个 API
 app1.$watch('age', function(age) {
         console.log(`我的年纪变了,现在已经是:${age}岁了`)
 });
 app1.data.age = 100; // 输出:'我的年纪变了,现在已经是100岁了'

如果传入参数对象是一个“比较深”的对象(也就是其属性值也可能是对象),那该怎么办呢?

即如果传入的又是一个对象呢?需要将“深对象”也绑定 get、set 方法,想到用递归的方法,对属性进行判断,如果属性值是对象,则重新 new 一个 Observer 出来。即:

Observer.prototype.makeObserver = function(data) {
  for(var i in data) {
    if(data.hasOwnProperty(i)) {
      if(typeof data[i] === 'object') {
        new Observer(data[i]);
      }
      this.getset(i, data[i]);
    }
  }
}

如果设置新的值是一个对象的话,新设置的对象的属性是否能继续响应 getter 和 setter 呢?

例如:

let app = new Observer({
    basicInfo: {
        name: 'ruirui',
        age: 25
    },
    address: 'Beijing'
})
//要实现的结果如下
app.data.basicInfo = {like: 'movie'}//你设置了basicInfo,新的basicInfo为{like: 'movie'}
app.data.basicInfo.like //你访问了basicInfo,你访问了like

同上,需要在 set 中进行判断,如果是对象,重新 new 一个 Observer。

考虑传递回调函数。在实际应用中,当特定数据发生改变的时候,我们是希望做一些特定的事情,而不是每一次只能打印出来一些信息,所以,我们需要支持传入回调函数的功能。

对属性绑定回调函数,需要的时候触发。即:每次 $watch 一个属性相当于注册一个监听事件,每次属性发生改变(set)的时候触发该事件。采用发布-订阅模式,实现一个自定义事件,可以对一个属性注册多个事件,触发的时候依次调用。

function Event() {
  this.events = {};
}
Event.prototype.on = function (attr, callback) {
  if (this.events[attr]){
    this.events[attr].push(callback);
  } else {
    this.events[attr] = [callback];
  }
}
Event.prototype.emit = function (attr, newval) {
  console.log(this.events); // 每一层的event都是独立的,深层对象属性触发的时候可以看出this.events里为空。
  for(var item in this.events) {
    if(item === attr) {
      this.events[attr].forEach(function(item) {
        item(newval);
      })
    }
  }
}

这样的话,每次 new 一个 Observer 实例时,就 new 一个 Event 实例管理所有的事件。在 set 中触发事件
Demo

function Observer(data) {
  this.data = data;
  this.makeObserver(data);
  this.eventsBus = new Event();
}
Observer.prototype.makeObserver = function(data) {
  for(var i in data) {
    if(data.hasOwnProperty(i)) {
      if(typeof data[i] === 'object') {
        new Observer(data[i]);
      }
      this.getset(i, data[i]);
    }
  }
}
Observer.prototype.getset = function(i, value) {
  let val = value;
  let that = this;
  Object.defineProperty(this.data, i, {
    configurable: true,
    enumerable: true,
    get: function () {
      console.log('你访问了' + i);
      return val;
    },
    set: function (newval) {
      console.log('你设置了' + i + ',新的值为' + newval);
      if(typeof newval === 'object') {
        new Observer(newval);
      }
      // 触发
      that.eventsBus.emit(i, newval);
      val = newval;
    }
  })
}
Observer.prototype.$watch = function (attr,callback){
    // 注册一个监听事件
    this.eventsBus.on(attr, callback);
}

至此,完成了事件监听。

目前只能注册监听对象到第一层属性,深层对象属性的监听未完成,看下回分析。

Vue 动态数据绑定(一)-- Observer

Posted on 2017-08-28

任务说明地址

任务描述
这是“动态数据绑定”系列的第一题。
我之前经常使用 Vue,后来不满足于仅仅使用它,我想了解其内部实现原理,所以就尝试学习其源码,获益匪浅。所以,如果你跟我一样,希望挑战这高难度的事情,那就开启这一系列吧!
我们从最简单的开始。
其中,动态数据绑定就是 Vue 最为基础,最为有用的一个功能。这个系列将分成5部分,一步一步来理解和实现这一功能。
ok,我们从最简单的开始。给定任意一个对象,如何监听其属性的读取与变化?也就是说,如何知道程序访问了对象的哪个属性,又改变了哪个属性? 举个例子。

let app1 = new Observer({
    name: 'youngwind',
    age: 25
});
let app2 = new Observer({
    university: 'bupt',
    major: 'computer'
});
// 要实现的结果如下:
app1.data.name // 你访问了 name
app.data.age = 100;  // 你设置了 age,新的值为100
app2.data.university // 你访问了 university
app2.data.major = 'science'  // 你设置了major,新的值为 science

请实现这样的一个 Observer,要求如下:

  1. 传入参数只考虑对象,不考虑数组。
  2. new Observer 返回一个对象,其 data 属性要能够访问到传递进去的对象。
  3. 通过 data 访问属性和设置属性的时候,均能打印出右侧对应的信息。

监听属性的变化,有两种方式:

  1. 采用ES5中的 defineProperty,设置 get 和 set 函数,重新定义读取和赋值的方式。
  2. 采用ES6中的 proxy,对目标对象进行”拦截”。

方式一:

Object.defineProperty(obj, prop, descriptor) 定义对象的属性。 Object.defineProperty 可设置的属性如下:

  • configurable:能否使用 delete、能否修改属性特性、能否修改访问器属性,false 为不可重新定义。默认为true。
  • enumerable:对象属性是否可通过 for-in 循环遍历或在 Object.keys 中列举,默认为true。
  • writable:对象属性是否可修改,默认为true。
  • value:对象属性的默认值,默认值为 undefined。

注意: configurable, enumerable, writable 特性默认值根据对象定义方法不同而不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 直接在对象上定义属性,这些特性默认值为true
var obj = {};
obj.name = 'ruirui';
console.log(Object.getOwnPropertyDescriptor(obj, 'name'));
// Object {value: "ruirui", writable: true, enumerable: true, configurable: true}

// 调用Object.defineProperty()方法,不指定值的时候,默认为false
var obj = {};
Object.defineProperty(obj, 'name', {
value: 'ruirui'
});
console.log(Object.getOwnPropertyDescriptor(obj, 'name'))
// Object {value: "ruirui", writable: false, enumerable: false, configurable: false}


// 在本例中,可以定义configurable、enumerable,默认为false。 但是如果定义了set或get方法中的任何一个,就不能再设置writable,即使false也不可以。

接着获取对象属性:

方法一:使用 Object.keys(obj),该方法返回一个数组,数组里是 obj 可被枚举的所有属性,接着对数组进行 forEach 遍历。
方法二:使用 for in 获取所有属性,接着用 obj.hasOwnProperty(key) 对属性进行判断过滤。

接着就可以自定义get和set函数啦!

在这过程中犯了一个低级错误:get 函数 return data[key],导致 get 函数返回时又触发了 get 函数,陷入死循环。

最终代码(Demo):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Observer(data) {
this.data = data;
this.getset(data);
}
Observer.prototype.getset = function(data) {
for(var key in data) {
var val = data[key];
if(data.hasOwnProperty(key)) {
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
//writable: true, //这里不能定义此属性,报错:Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
get: function () {
console.log('你访问了' + key);
return val;
},
set: function (newval) {
console.log('你设置了' + val + ',新的值为' + newval);
val = newval;
}
})
}
}
}

方式二:

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

最终代码(Demo):

function Observer (data) {
  return new Proxy(data, {
    get: function (target, propKey) {
      if (propKey in target) {
        console.log('你访问了' + propKey);
        return target[propKey];
      } else {
        console.log("Property \"" + propKey + "\" does not exist.");
      }
    },
    set: function (target, propKey, value) {
      console.log('你设置了' + propKey + ',新的值为' + value);
      target[propKey] = value;
    }
  })
}

后续补充:defineProperty VS proxy:

vue 文档中提到:

由于 JavaScript 的限制, Vue 不能检测以下变动的数组:
当你利用索引直接设置一个项时,例如: vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如: vm.items.length = newLength

vue 不能检测到数组的变动,从而无法更新视图,vue 监听数据变动使用的上述 Object.defineProperty,如果传入的是一个数组,通过下标访问数组,下标作为属性,可以看到是能正常调用 get 和 set 方法的,是可以被监听的,但是新增一个元素,修改数组长度,以及删除元素都不会触发监听事件。

1
2
3
4
5
6
7
8
9
let data = ['1','2','3'];
let app1 = new Observer(data);
console.log(app1.data[1]); // 你访问了2
app1.data[1] = 3; // 你设置了3,新的值为3
app1.length = 5;
console.log(app1.data[4]); // undefined
app1.data[4] = 4;
console.log(app1.data[4]); // 4
app1.data[5] = 9;

所以,vue 中重写了对数组的监测。这是 defineProperty 的第一个缺陷,第二个缺陷是,只能劫持对象的属性,所以对深层对象的属性需要深度遍历。这就引出了 Proxy 的优点:

  • Proxy 可以直接监听对象而非属性。返回的是新对象,可以操作新的对象。
  • Proxy 可以直接监听数组的变化。

Vue3.0 也将放弃Object.defineProperty,改用性能更好的Proxy。

Javascript 排序算法

Posted on 2017-05-08

冒泡排序

(1)算法说明:是最慢的算法之一,也是最容易实现的排序算法。使用冒泡排序时,数据值会像气泡一样从数组的一端浮到另一端,算法每次比较相邻到元素,如果顺序错误就交换。
(2)javascript实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//如果某一次没有任何交换,则之后不需要再交换
function bubbleSort(arr){
var len = arr.length;
for(var i = 0; i < len;i++){
var isExchange = false;
for(var j = 0; j < len-i;j++){
if(arr[j] > arr[j+1]){
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
isExchange = true;
}
}
if(isExchange == false){
break;
}
}
return arr;
}
(3)算法分析
时间复杂度:O(n的平方)
空间复杂度:O(1)
稳定性:稳定

选择排序

(1)算法说明:从待排序的数据元素中选择最小(或最大)的元素,存放在序列的起始位置,直到全部待排元素排完。
(2)javascript实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function selectionSort(arr){
var len = arr.length;
var minIndex,temp;
for(var i = 0; i < len;i++){
minIndex = i;
for(var j = i; j < len;j++){
if(arr[minIndex] > arr[j]){
minIndex = j;
}
}
temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
return arr;
}

(3)算法分析
时间复杂度:O(n的平方)
空间复杂度:O(1)
稳定性:不稳定

插入排序

(1)算法说明:将一个数据插入到已经排好的有序的有序数据中。
(2)javascript实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
function insertSort(arr){
var len = arr.length;
for(var i = 1; i < len;i++){
var key = arr[i];
var j = i-1;
while(j>=0 && arr[j] > key){
arr[j+1] = arr[j];
j–;
}
arr[j+1] = key;
}
return arr;
}

(3)算法分析
时间复杂度:O(n的平方)
空间复杂度:O(1)
稳定性:稳定

归并排序

(1)算法说明:将一系列排好序的子序列合并成一个大的完整有序序列。
(2)javascript实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function mergeSort(arr){
var len = arr.length;
if(len < 2){
return arr;
}
var middle = Math.floor(len/2),
left = arr.slice(0,middle),
right = arr.slice(middle);
return merge(mergeSort(left),mergeSort(right));
}
function merge(leftArr,rightArr){
var result = [];
while(leftArr.length && rightArr.length){
if(leftArr[0] > rightArr[0]){
result.push(rightArr.shift())
}else{
result.push(leftArr.shift());
}
}
while(leftArr.length){
result.push(leftArr.shift());
}
while(rightArr.length){
result.push(rightArr.shift());
}
return result;
}

(3)算法分析
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:稳定

快速排序

(1)算法说明:快速排序是处理大数据集最快的排序算法之一,是一种分而治之的算法,通过递归的方法将数据依次分解为包含较小元素和较大元素的不同子序列,不断重复这个步骤直到所有数据都是有序的。
(2)算法步骤:

  • 在数据集之中,选择一个元素作为”基准”(pivot)。
  • 所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。
  • 对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
    (3)javascript实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function quickSort(arr){
    if(arr.length === 0){
    return [];
    };
    var pivot = arr[0],
    left = [],
    right = [];
    for(var i = 1; i < arr.length; i++){
    if(arr[i] < pivot){
    left.push(arr[i]);
    }else{
    right.push(arr[i]);
    }
    };
    return quickSort(left).concat([pivot],quickSort(right));
    }

(4)算法分析
时间复杂度:O(nlogn) 最差O(n方)
空间复杂度:O(1)
稳定性:不稳定

Javascript 事件

Posted on 2017-04-08

事件流

IE事件冒泡流:即事件最开始由最具体的元素接收,然后逐级向上传播至最不具体的那个节点(文档)。
事件捕获流:与事件冒泡流相反,由最不具体的元素接收,传至最具体的元素节点。

事件处理程序

1. HTML事件处理程序
缺点:html和js紧密耦合在一起,js代码和html代码都需要修改
2. DOM0级事件处理程序
方法:把一个函数赋值给一个事件的处理程序属性

1
2
3
4
5
6
7
var btn = document.getElementById('btn');
//指定事件处理程序
btn.onclick=function(){
alert("hello");
}
//取消时,将时间处理程序属性设置为null即可
btn.onclick = null;

作用域:DOM0事件处理程序被认为是元素的方法,所以其作用域为元素的作用域,即this引用当前元素。
处理阶段:事件处理程序会在事件流的冒泡阶段被处理。
调用情况:只能绑定一个事件处理函数,后面的会覆盖掉前面的
3. DOM2级事件处理程序
方法:添加事件addEventListener();移除事件removeEventlistener()
三个参数:要注册处理程序的事件类型(不包括前缀‘on’)、事件处理程序函数、布尔值(是否在捕获阶段处理事件,默认false)。

1
2
3
var btn = document.getElementById('btn');
btn.addEventListener('click',showMes,false);//不加on
btn.removeEventListener('click',showMes,false);

作用域:与DOM0级的一样,为元素的作用域,this引用当前元素。
处理阶段: DOM2级事件处理程序规定事件流的三个阶段:事件捕获阶段,处于目标阶段,事件冒泡阶段。注意第三个参数,以第三个参数区分捕获阶段处理还是冒泡阶段处理。目前事件捕获只能用于以addEventListener()注册且第三个参数是true的事件处理程序中。
调用情况:同一个事件处理程序可以绑定两次,一次用于事件捕获,一次用于事件冒泡;若绑定的是同一个事件处理程序,并且都为事件冒泡或事件捕获,则只能绑定一次;不同的事件处理函数可以绑定多个,按顺序触发。
4. IE事件处理程序
方法:添加事件attachEvent();移除事件detachEvent()
两个参数:事件类型(加上前缀‘on’)、处理程序函数(因为IE8以及更早的版本只支持事件冒泡,不支持事件捕获)

1
2
3
var btn = document.getElementById('btn');
btn.attachEvent('onclick',showMes);//加上on
btn.detachEvent('onclick',showMes);

作用域: this值为全局(window)对象。
调用情况: 允许相同的事件处理程序注册多次,当特定的事件类型发生时,注册函数的调用次数和注册次数一样(匿名函数);同一个事件处理函数只能绑定一次(非匿名函数)

事件对象

在触发DOM上的事件时都会产生一个事件对象event,它包含着所有和事件有关的信息,调用事件处理程序时会把事件对象作为一个参数(有一个例外)。
DOM中的事件对象属性

  • type属性:用于获取事件类型(click…) event.type
  • target属性:触发事件的目标元素,是不变的
  • stopPropagation()方法:阻止事件传播,可以在事件传播期间的任何时间调用。
  • preventDefault()方法:取消事件默认行为

IE中的事件对象属性

  • type属性:用于获取事件类型(click…) event.type
  • srcElement属性:触发事件的目标元素
  • cancleBubble属性:阻止事件传播,因为IE8之前版本不支持事件捕获,所以事件传播即为事件冒泡。设置为true表示阻止冒泡
  • returnValue属性:阻止事件默认行为,设置为false阻止事件默认行为

事件委托

事件的代理是靠事件的冒泡机制实现的。可以避免对特定的每个节点添加事件监听,将事件监听绑定到父元素上。
优点: 1.可以大量节省内存占用,减少事件注册 2.可以实现当新增子对象时无需再次对其绑定事件,对于动态内容尤为合适。

封装事件处理程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var eventUtil = {
addHandler:function (element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
}else if(element.attachEvent){
element.attachEvent('on'+type,handler);
}else{
element['on'+type]=handler;
}
},
removeHandler:function (element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){
element.detachEvent('on'+type,handler);
}else{
element['on'+type]=null;
}
},
delegateEvent:function(element,tag,eventName,listener){

eventUtil.addHandler(element,eventName,function(event){
var target = event.target || event.srcElement;
if(target.tagName.toLowerCase() == tag.toLowerCase()){
listener.call(target,event);
}
});
},
getEvent:function(event){
var event = event?window.event;
},
getType:function(event){
return event.type;
},
getElement:function(event){
return event.target || event.srcElement;
},
preventDefault:function(event){
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue=false;
}
},
stopPropagation:function(event){
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble=true;
}
}
}

Javascript 类型判断

Posted on 2017-03-20

JavaScript 具有七种内置数据类型,它们分别是:

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

javascript判断数据类型的方法: typeof、instanceof、constructor、object.prototype.toString

typeof

typeof 判断基础数据类型:

1
2
3
4
5
6
7
8
typeof undefined // "undefined"
typeof true // "boolean"
typeof 'string' // "string"
var a
typeof a // 'undefined'
typeof b // 'undefined' // 即使b未声明,也会返回undefined
// 特殊
typeof null === 'object'

typeof 判断复杂数据类型:

1
2
3
4
5
typeof [] // "object"
typeof {} // "object"
typeof Symbol('foo') // "symbol"
typeof function a() {} // "function"
typeof new Date() // "object"

typeof 返回类型的字符串值, 使用 typeof 判断基本类型时,不能判断 null 类型,判断复杂类型时,可以判断出 function 类型、symbol类型。

结论

typeof 通常用来检测一个对象是否定义,不能判断 Object 类型

instanceof

左操作符是对象,右操作符是函数或构造器,判断:右边的构造器中 prototype 属性是否出现在左边的对象的原型链上。

instanceof 的原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function instanceof(left, right) {
if (typeof left !== 'object') {
return false
}
let proto = left._proto_;
let prototype = right.prototype;
while(true) {
if(proto === prototype) {
return true
}
if(proto === null) {
return false
}
proto = proto._proto_
}
}

instanceof 作用

  • 检测一个对象是否是属于某种类型

    1
    2
    3
    function Foo(){}
    var foo = new Foo();
    console.log(foo instanceof Foo) //true
  • 在继承关系中判断一个实例是否是属于他的父类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Foo() {}
    function Bar() {}
    Bar.prototype = new Foo();
    var bar = new Bar();
    bar instanceof Bar; // true
    bar instanceof Foo; // true
    // 如果仅仅设置 Bar.prototype 为函数 Foo 本身,而不是 Foo 构造函数的一个实例
    Bar.prototype = Foo;
    new Bar() instanceof Foo; // false

instanceof 缺陷:

1
2
3
4
5
6
7
var str1 = 'hello';
console.log(str1 instanceof String); // false
var str2 = new String('jomsou');
console.log(str2 instanceof String); // true
var arr = [1, 2, 3];
arr instanceof Array; // true
arr instanceof Object; // true

结论

instanceof 不能判断基本类型,object 类型判断也有问题,但是可以准确的判断 Array 类型(instanceof 无法判断 iframe 的数组类型)

准确的判断数组的方法:

1
2
arr instanceof Array
Array.isArray() // Array.isArray() IE9以下的浏览器可能不兼容

constructor

使用 constructor 可以查看目标的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = [];
a.constructor // ƒ Array() { [native code] }
a.constructor == Array; // true

var b = {};
b.constructor // ƒ Object() { [native code] }
b.constructor == Object; // true

var foo = Symbol("foo")
foo.constructor // ƒ Symbol() { [native code] }

// 特殊
var foo = undefined
foo.constructor // VM241:2 Uncaught TypeError: Cannot read property 'constructor' of undefined at <anonymous>:2:5

var foo = null
foo.constructor // VM247:2 Uncaught TypeError: Cannot read property 'constructor' of null at <anonymous>:2:5

constructor 对 undefinded 和 null, 如果尝试读取 constructor 属性,会报错。constructor 返回的是构造函数本身,一般不用来进行类型判断。

object.prototype.toString

对象内部属性[[class]]的值,需要通过Object.prototype.toString()获取。检测一个对象的类型,这是唯一一个可依赖的方法。

  • 判断基本数据类型:

    1
    2
    3
    4
    5
    Object.prototype.toString.call(null);//"[object Null]";
    Object.prototype.toString.call(undefined);//"[object undefined]";
    Object.prototype.toString.call("abc");//"[object String]";
    Object.prototype.toString.call(123);//"[object Number]";
    Object.prototype.toString.call(true);//"[object Boolean]";
  • 判断原生引用类型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //函数类型 
    function fn(){console.log("test");}
    Object.prototype.toString.call(fn);//"[object Function]";
    //日期类型
    var date = new Date();
    Object.prototype.toString.call(date);//"[object Date]";
    //数组类型
    var arr = [1,2,3];
    Object.prototype.toString.call(arr);//”[object Array]”
    //正则表达式
    var reg = /[hbc]at/gi;
    Object.prototype.toString.call(reg);//”[object RegExp]”
    //自定义类型
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    var person = new Person("Rose", 18);
    Object.prototype.toString.call(arr); //”[object Object]”

Javascript 数据类型

Posted on 2017-03-06

五种原始类型(值类型):Undefined Null Boolean Number String Symbol(ECMScript6)
一种对象类型(引用类型):Object

undefined类型

  • 只有一个值:undefined
  • 使用var声明变量但未初始化,变量的值就为undefined
  • 对未初始化和未声明的变量执行typeof操作符都返回undefined值,即:
    1
    2
    3
    var message;
    alert(typeof message);//"undefined"
    alert(typeof age);//"undefined"

Null类型

  • 只有一个值:null
  • alert(null == undefined);//true

boolean类型

  • 两个值:true,false
  • ECMAScript所有类型都可以转换为布尔值,调用Boolean()
  • 有六个数据可以转换为false,分别为:null、undefined、0、-0、NaN、空字符串

Number类型

String类型

  • 将任何类型的值转换为字符串:String()函数,转换规则如下:
    1.如果值有toString()方法(null和undefined没有这个方法),则调用此方法。
    2.如果值为null,则返回”null”。
    3.如果值为undefined,则返回”undefined”。
  • 使用加减号操作符进行转换:
    num - 0//将字符串变为数字
    num + ‘’//将数字变成字符串

Object类型

  • var o = new Object();

null和undefined的区别

  • 相同点:两种数据类型都只有一个值,参与判断都返回false,都没有方法。
  • 不同点:
    1. null是关键字,undefined不是关键字。
    2. null是对象,undefined是windows的一个属性。
    3. 定义变量未初始化时默认值为undefined,如果为null表示已经初始化。
    4. typeof返回值不一样。
    5. 将undefined转换为数字,即Number(undefined),返回NAN。将null转换为数字,即Number(null),返回0。
1
2
3
4
typeof null; //object;
typeof undefined; //undefined
null === undefined; //false
null == undefined; //true 会执行类型转换
  • 用法:
    1. 在声明一个变量时可以不赋值,如果一定要赋值,赋为null。
    2. 检测某个值是否是未定义的,用(?=== undefined)。
    3. 检测某个值是否为空,用(?=== undefined)。

===和==区别

  • a===b
    判断等号两边的类型
    1.类型不同,返回false;
    2.类型相同:
    1
    2
    3
    4
    null === null  //true
    undefined === undefined //true
    NaN === NaN //false
    new Object === Object //false
  • a==b
    1.类型相同,同===
    2.类型不同,则类型转换之后比较。

值类型和引用类型的区别

值类型:number boolean null undefined
值类型在内存中占据固定大小的空间,保存在栈内存中;从一个变量向另一个变量赋值值类型的值,会创建这个值的一个副本。
引用类型:对象 数组 函数
引用类型的值是对象,保存在对内存中;从一个变量向另一个变量赋值引用类型的值,复制的其实是指针,两个变量最终都指向同一个对象。

1…34

ruirui

37 posts
7 tags
© 2022 ruirui
Powered by Hexo
|
Theme — NexT.Muse v6.0.0