IFE 2015_spring task0002 自学记录

JavaScript数据类型及语言基础

1. 判断arr是不是一个数组,返回一个bool值。

首先javascript有5大基本数据类型:Undefined,Null,Boolean,Number和String(双无BNS)

还有一个引用数据类型:Object,它包括以下三大类:

  1. Native Object(原生对象): ECMAScript本身自带的对象,是在脚本运行环境中程序员创建来使用的,包括:Object(基础类型)、Array、Date、Function、RegExp;另外还包括三个基本包装类型:Boolean、Number、String,有了它们三个我们可以将基本类型值当作对象来访问(使用他们的属性和方法)。
  2. Build-in Object(内置对象): JavaScript语言提供的不依赖于执行宿主的内建对象,如:Global、Math;内建对象都是Native Object。
  3. Host Object(宿主对象):JavaScript语言提供的任何依赖于宿主环境的对象,所有非Native Object的对象都是宿主对象,如:IE中的window,WScript中的wscript实例,以及任何用户创建的类。

所以我们判断的数组(Array)就是复杂数据类型Object中的其中一类,我们需要使用Object自带的方法来判断它——

Object.prototype.toString:取得对象的一个内部属性[[Class]],然后依据这个属性,返回一个类似于"[object Array]"的字符串作为结果。

function isArray (arr) {
  return Object.prototype.toString.call(arr) === '[object Array]';
}

不直接调用arr.toString,而用call的原因:虽然Array继承自Object,也会有toString方法,但是这个方法有可能会被改写(数组重写了该方法:arr.toString的效果相当于join()方法连接),Object.prototype能一定程度保证其“纯洁性”。

2. 判断fn是否为一个函数,返回一个bool值。

我个人认为使用typeof操作符即可,如果严密一点的话可以使用上述判断数组的方法。

function isFunction (fn) {
  return (typeof fn === 'function');
}
// or
function isFunction (fn) {
  return Object.prototype.toString.call(arr) === '[Object Function]';
}

3. 使用递归来实现一个深度克隆,可以复制一个目标对象,返回一个完整拷贝,被复制的对象类型会被限制为数字、字符串、布尔、日期、数组、Object对象。不会包含函数、正则对象等。

基本是考虑克隆对象时的三种情况——重新构造和遍历

function cloneObject (src) {
  // 对于基本数据类型,只要直接返回即可
  if (src == null || typeof src != 'object') {  
    return src;
  }

  // 对于对象分为以下三种情况

  // 对于Date等引用类型的数据,需要考虑调用构造函数重新构造,直接赋值依然会有引用问题(不是真正的clone引用变量)
  // 以Date为例
  if (src instanceof Date) {
    var clone = new Date(src.getDate());
    return clone;
  }

  // 对于数组,需要遍历,这样可以保证在在Array对象上扩展的属性也可以正确复制
  if (isArray(src)) {
    var clone = [];
    for (var i = 0; i < src.length; i++) {
      clone[i] = cloneObject(src[i]);
    }
    return clone;
  }

  // 对于其他object,同样也需要遍历
  if (src instanceof Object) {
    var clone = {};
    for (var key in src) {
      if (src.hasOwnProperty(key)) {  // 忽略掉继承属性,只取属于它本身的属性
        clone[key] = cloneObject(src[key]);
      }
    }
    return clone;
  }
}

4. 对数组进行去重操作,只考虑数组中元素为数字或字符串,返回一个去重后的数组。

// 普通实现
function uniqArray(arr) {
  if (arr.length < 2) return arr;
  for (var i = 0; i < arr.length - 1; i++) {
    for (var j = i + 1; j < arr.length; j++) {
      if (arr[i] == arr[j]) {
        arr.splice(j, 1);   // 数组的splice方法:删除数组中的某一个元素
      }
    }
  }
  return arr;
}

// 利用对象的keys方法进行筛选:把元素作为属性添加到对象中,重复的话自然不会有影响(类似桶排思想)
function uniqArray2(arr) {
    var obj = {};
    for (var i = 0, len = arr.length; i < len; i++) {
        obj[arr[i]] = true;
    }
    return Object.keys(obj);
}

5. 实现一个简单的trim函数,用于去除一个字符串,头部和尾部的空白字符。

通过循环,以及字符串的一些基本方法,分别扫描字符串str头部和尾部是否有连续的空白字符,并且删掉他们,最后返回一个完成去除的字符串。

function simpleTrim(str) {
  // 关键在于找到字符串中间非空的部分的下标值
  var head = 0, tail = str.length - 1;
  while(str[head] == ' ') head++;
  while(str[tail] == ' ') tail--;
  return str.substring(head, tail + 1);  // substring方法不包括tail下标
}

6. 通过正则表达式完成题5

首先正则表达式形如:/ .... /

然后里面有一些匹配符:\d 数字,\w 字母数字下划线,\s 空格

还有一些特殊符号:^ 开头,$ 结尾,| 或者

还有 / .... / 后面有时还会跟一个字母:g 全局,i 忽略大小写

function trim(str) {
  // 对字符串的正则表达式处理通常用到replace方法
  // str.replace(re, newSub):将字符串被re匹配到的部分,用newSub来代替。
  return str.replace(/^\s+|\s+$/g, '');
}

上面的正则表达式:以\s(即空格)开头或结尾,+号表示一个或多个。

// 判断是否为邮箱地址
function isEmail(emailStr) {
  return emailStr.search(/^[a-z0-9]([-_\.]?[a-z0-9]+)*@([-_]?[a-z0-9]+)+[\.][a-z]{2,7}([\.][a-z]{2})?$/i) !== -1;
}

// 判断是否为手机号
function isMobilePhone(phone) {
  phone = phone + '';
  return phone.search(/^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/) !== -1;
}

 

DOM

1. 题目:获取element相对于浏览器窗口的位置,返回一个对象{x, y}

以下是一些常用宽高度的属性:

宽度
元素所占宽度包含 内容区宽度 内边距padding 边框的宽度
简单来说:padding是一个元素所占大小的概念,margin是个定位的概念。

偏移量 offset
offsetLeft表达的是元素所占区域的左边缘到父元素的左边缘,即元素的左外边框到包含元素的左内边框的像素距离

页面的真实高度、宽度
document.documentElement.scrollHeight
document.documentElement.scrollWidth
document.documentElement 是一个只读属性,返回文档对象(document)的根元素(例如,HTML文档的 <html> 元素)。

可视区域的高度
document.documentElement.clientHeight
如果页面是一个竖向页面(一般而言),可视区域的宽度和页面的宽度是一样的,使用scrollWidth即可。另外还有window.innerHeight也可以求可视区域高度,但不兼容低版本IE浏览器。使用的时候可以用兼容写法:

var pageHeight = window.innerHeight || document.documentElement.clientHeight;
var pageWidth = window.innerWidth || document.documentElement.clientWidth;

元素的真实高度、宽度
offsetWidth、offsetHeight是一个经常使用的值,该值真实的反映了元素所占空间的大小,而width,height属性仅仅表达了内容区。

 

获取元素的绝对位置

网页元素的绝对位置,指该元素的左上角相对于整张网页左上角的坐标。

首先,每个元素都有offsetTop和offsetLeft属性,表示该元素的左上角与父容器(offsetParent对象)左上角的距离。所以,只需要将元素本身的offsetTop和offsetLeft分别和其所有父容器的offsetTop和offsetLeft相加(循环,往上遍历),就可以得到该元素的绝对坐标。

获取元素的相对位置

网页元素的相对位置,指该元素左上角相对于浏览器窗口左上角的坐标。

有了绝对位置以后,获得相对位置就很容易了,只要将绝对坐标减去页面的滚动条滚动的距离就可以了。滚动条滚动的垂直距离,是document对象的scrollTop属性;滚动条滚动的水平距离是document对象的scrollLeft属性。

题目所要求的就是获取元素的相对位置。

// 获取element相对于浏览器窗口的位置,返回一个对象{x, y}
function getPosition(element) {
  var x = 0;
  var y = 0;
  var currentElement = element; // 从目标元素开始往上遍历

  while (currentElement !== null) {
    x += currentElement.offsetLeft;
    y += currentElement.offsetTop;
    currentElement = currentElement.offsetParent;
  }

  // 为了兼容,documentElement、body这两个值总会有一个恒为0
  var scrollLeft = document.documentElement.scrollLeft + document.body.scrollLeft;
  var scrollTop = document.documentElement.scrollTop + document.body.scrollTop;

  x -= scrollLeft;
  y -= scrollTop;

  return {
    x: x,
    y: y
  };
}

2. 实现一个简单的query

// 实现一个简单的Query
function $(selector) {
  // 思路是取出第一个字符,然后分情况去获取不同的元素
  var element = null;
  var prefix = selector.charAt(0);
  switch(prefix) {
    case '#':
      element = document.getElementById(selector.substring(1));
      break;
    case '.':
      element = document.getElementsByClassName(selector.substring(1));
      break;
    default:
      element = document.getElementsByTagName(selector);
  }
  return element;
}

console.log($("#adom"));
console.log($("div"));
console.log($(".classa"));

 

事件

事件冒泡:事件开始由最具体的元素(具体目标)接收,然后逐级向上传播到较为不具体的节点,当我点击一个按钮,click事件不断往上冒泡(button→body→html→Document),相当于从下往上每个都被click了一次。

事件捕获:和冒泡相反,事件从不太具体的节点开始接收事件,而最具体的节点最后接收。

DOM2级事件:三个阶段:捕获→目标→冒泡。

事件处理函数:也就是常说的listener,这个函数有一些独到之处:

  1. function(e) {...} 参数e称为事件对象,是浏览器将event对象作为参数传进来的,在IE中event对象作为window对象的一个属性存在
  2. 在这个函数里,this值等于事件的目标元素

以前常用的 btn.onclick = function() {...} 的做法是DOM0级的指定事件处理程序的方式

DOM2级事件定义了两个方法:addEventListener和removeEventListener,三个参数分别是:要处理的事件名,事件处理函数,一个布尔值(true表示在捕获阶段调用事件处理程序,false则是冒泡阶段,默认为false)

事件处理函数 function(e) {...} 的参数e,即事件对象e,e.target表示的是事件的真正目标(比如上述加粗例子中的按钮),可以通过 e.target == this 来判断当前对象和事件的真正目标是否相同(避免冒泡带来的误差),或者使用e.stopPropagation()来阻止事件的传播。

// 给一个element绑定一个针对event事件的响应,响应函数为listener
function addEvent(element, event, listener) {
  element.addEventListener(event, listener);
}

// 移除element对象对于event事件发生时执行listener的响应
function removeEvent(element, event, listener) {
  element.removeEventListener(event, listener);
}

// 实现对click事件的绑定
function addClickEvent(element, listener) {
  addEvent(element, "click", listener);
}

// 实现对于按Enter键时的事件绑定
function addEnterEvent(element, listener) {
  // 键盘事件一般来说element是window
  addEvent(element, "keydown", function (e) {
    var event = e || window.event; // window.event是IE写法
    if (event.keyCode == 13) {
      // 这里要注意调用listener函数要将this绑定到element上,因为listener里的this是element,参数是event
      listener.call(element, event);
    }
  })
}

addClickEvent($(".classa")[0], function () {
  console.log(this);  // 元素".classa"
});

addEnterEvent(window, function () {});

 

事件代理(事件委托)

事件处理函数绑定在父元素上,当子元素发生事件时会冒泡到父元素,导致该事件处理函数触发,然后调用该事件处理函数的时候重新把this绑定回子元素上。

// 事件代理(事件委托)
// 实现对element中所有标签为tag的子元素绑定事件(优点:把listener绑定在父元素上,减少DOM访问次数和listener数量)
// 原理:事件冒泡
function delegateEvent(element, tag, eventName, listener) {
  $.on(element, eventName, function (e) {
    var event = e || window.event;
    var target = event.target || event.srcElement;
    if (target && target.tagName == tag.toUpperCase()) {  // element.tagName得到元素标签名(大写)
      listener.call(target, event);
    }
  });
}

 

posted @ 2016-02-25 21:39  寄生蠕虫  阅读(866)  评论(0编辑  收藏  举报