IFE 2015_spring task0002 自学记录
JavaScript数据类型及语言基础
1. 判断arr是不是一个数组,返回一个bool值。
首先javascript有5大基本数据类型:Undefined,Null,Boolean,Number和String(双无BNS)
还有一个引用数据类型:Object,它包括以下三大类:
- Native Object(原生对象): ECMAScript本身自带的对象,是在脚本运行环境中程序员创建来使用的,包括:Object(基础类型)、Array、Date、Function、RegExp;另外还包括三个基本包装类型:Boolean、Number、String,有了它们三个我们可以将基本类型值当作对象来访问(使用他们的属性和方法)。
- Build-in Object(内置对象): JavaScript语言提供的不依赖于执行宿主的内建对象,如:Global、Math;内建对象都是Native Object。
- 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,这个函数有一些独到之处:
- function(e) {...} 参数e称为事件对象,是浏览器将event对象作为参数传进来的,在IE中event对象作为window对象的一个属性存在
- 在这个函数里,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); } }); }