零js基礎 | 程序設計 | 13 - 事件
<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">
使用try catch捕获错误
try {
var dd = document.getElemedntById('dd')
} catch (ex) {
alert(123)
}
addEventListener(),最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程
try {
btn.addEventListener('click', function() {
alert(this.id)
}, false)
} catch (ex) {
btn.attachEvent('onclick', function() {
alert(123)
})
}
removeEventListener(),要使用命名函数才能移除
btnfn = function() {
alert(this.id)
}
btn.addEventListener('click', btnfn, false)
btn.removeEventListener('click',btnfn,false)
attachEvent()与detachEvent(),1、this作用域为window; 2、执行顺序由后往前;
btn.attachEvent('onclick',function(){
alert(this) //特别注意,这里是window
})
btn.attachEvent("onclick", function(){
alert("Clicked"); //弹2
});
btn.attachEvent("onclick", function(){
alert("Hello world!"); //弹1
});
兼容方法:
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;
}
}
};
var handler = function() {
alert('abc')
}
EventUtil.addHandler(btn, 'click', handler)
EventUtil.removeHandler(btn, "click", handler);
事件对象
对象this 始终等于currentTarget 的值,而target 则只包含事件的实
际目标。如果直接将事件处理程序指定给了目标元素,则this、currentTarget 和target 包含相同
的值。来看下面的例子。
btn.onclick = function(event){
alert(event.currentTarget === this)
alert(event.target === this)
}
document.body.onclick = function(event) {
alert(event.currentTarget == document.body) //true
alert(this === document.body) //true
alert(event.target == document.getElementById('mybtn')) //true
}
使用event.type判断不同事件;
var handler = function(event) {
switch (event.type) {
case 'click':
alert('clicked')
break;
case 'mouseover':
alert('mouseovered')
break;
case 'mouseout':
alert('mouseouted');
break;
}
}
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
btn.onclick = function(event) {
alert('clicked')
event.stopPropagation()
}
document.body.onclick = function(event) {
alert('body clicked')
}
IE下的事件;
所以不能认为this 会始终等于事件目标。故而,最好还是使用event.srcElement 比较保
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(window.event.srcElement === this); //true
};
btn.attachEvent("onclick", function(event){
alert(event.srcElement === this); //false
});
cancelBubble 属性与DOM 中的stopPropagation()方法作用相同,都是用来停止事
件冒泡的。由于IE 不支持事件捕获,因而只能取消事件冒泡;但stopPropagatioin()可以同时取消
事件捕获和冒泡
所以综合起来的跨浏览器事件代码为:
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;
}
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
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;
}
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
//使用
btn.onclick = function(event) {
event = EventUtil.getEvent(event);
};
btn.onclick = function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
};
var link = document.getElementById("myLink");
link.onclick = function(event) {
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
};
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert("Clicked");
event = EventUtil.getEvent(event);
EventUtil.stopPropagation(event);
};
document.body.onclick = function(event) {
alert("Body clicked");
};
图片加载完后显示弹出
var image = document.getElementById('myimage')
EventUtil.addHandler(image,'load',function(event){
event = EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
})
EventUtil.addHandler(window,'load',function(){
var image = new Image();
EventUtil.addHandler(image,'load',function(event){
alert('Image Loaded')
})
image.src = 'smail.gif'
})
script外部地址加载完成
EventUtil.addHandler(window,'load',function(){
var _srcipt = document.createElement('script')
EventUtil.addHandler(_srcipt,'load',function(event){
alert('loaded')
})
_srcipt.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js'
document.body.appendChild(_srcipt)
})
css外部检测,低版本chrome/firefox可能不支持,未验证
EventUtil.addHandler(window, 'load', function() {
var _css = document.createElement('link')
_css.type = 'text/css'
_css.rel = 'stylesheet'
EventUtil.addHandler(_css, 'load', function(event) {
alert('loaded')
})
_css.href = 'https://assets-cdn.github.com/assets/frameworks-827a4e004aa05724993ea7616e4af53894825443811a270e2b9bce76e3453f19.css'
document.body.appendChild(_srcipt)
})
resize事件,推荐写法
EventUtil.addHandler(window, "resize", function(event) {
alert("Resized");
});
EventUtil.addHandler(window, "scroll", function(event) {
if (document.compatMode == "CSS1Compat") {
alert(document.documentElement.scrollTop);
} else {
alert(document.body.scrollTop);
}
});
焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及
document.activeElement 属性配合,可以知晓用户在页面上的行踪。有以下6 个焦点事件。
blur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
DOMFocusIn:在元素获得焦点时触发。这个事件与HTML 事件focus 等价,但它冒泡。只有
Opera 支持这个事件。DOM3 级事件废弃了DOMFocusIn,选择了focusin。
DOMFocusOut:在元素失去焦点时触发。这个事件是HTML 事件blur 的通用版本。只有Opera
支持这个事件。DOM3 级事件废弃了DOMFocusOut,选择了focusout。
focus:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
focusin:在元素获得焦点时触发。这个事件与HTML 事件focus 等价,但它冒泡。支持这个
事件的浏览器有IE5.5+、Safari 5.1+、Opera 11.5+和Chrome。
focusout:在元素失去焦点时触发。这个事件是HTML 事件blur 的通用版本。支持这个事件
的浏览器有IE5.5+、Safari 5.1+、Opera 11.5+和Chrome。
当焦点从页面中的一个元素移动到另一个元素,会依次触发下列事件:
(1) focusout 在失去焦点的元素上触发;
(2) focusin 在获得焦点的元素上触发;
(3) blur 在失去焦点的元素上触发;
(4) DOMFocusOut 在失去焦点的元素上触发;
(5) focus 在获得焦点的元素上触发;
(6) DOMFocusIn 在获得焦点的元素上触发。
事件都不冒泡
var _mybtn = document.getElementById('mybtn')
EventUtil.addHandler(_mybtn, 'focus', function() { //获得
alert(1)
})
EventUtil.addHandler(_mybtn, 'blur', function() {//失去
alert(2)
})
EventUtil.addHandler(_mybtn, 'focusin', function() {//获得
alert(1)
})
EventUtil.addHandler(_mybtn, 'focusout', function() {//失去
alert(2)
})
鼠标与滚轮事件
click/dblclick/mouseenter/mouseleave/mousemove/mouseout/mouseover/mouseup/
执行顺序:
(1) mousedown
(2) mouseup
(3) click
(4) mouseup
(5) dblclick
可以使用类似下列代码取得鼠标事件的客户端坐标信息:
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert("Client coordinates: " + event.clientX + "," + event.clientY);
});
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert("Page coordinates: " + event.pageX + "," + event.pageY);
});
在页面没有滚动的情况下,pageX 和pageY 的值与clientX 和clientY 的值相等。
滚动情况下:page 一定大于 client
EventUtil.addHandler(document.body, "click", function(event) {
event = EventUtil.getEvent(event);
console.log("Page coordinates: " + event.pageX + "," + event.pageY);
});
EventUtil.addHandler(document.body, "click", function(event) {
event = EventUtil.getEvent(event);
console.log("client coordinates: " + event.clientX + "," + event.clientY);
});
IE8不支持page,所以可用client + scroll 相加计算出
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event) {
event = EventUtil.getEvent(event);
var pageX = event.pageX,
pageY = event.pageY;
if (pageX === undefined) {
pageX = event.clientX + (document.body.scrollLeft ||
document.documentElement.scrollLeft);
}
if (pageY === undefined) {
pageY = event.clientY + (document.body.scrollTop ||
document.documentElement.scrollTop);
}
alert("Page coordinates: " + pageX + "," + pageY);
});
鼠标相对于整个屏幕坐标位置,screenX 和 screenY
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert("Screen coordinates: " + event.screenX + "," + event.screenY);
});
虽然鼠标事件主要是使用鼠标来触发的,但在按下鼠标时键盘上的某些键的状态也可以影响到所要
采取的操作。这些修改键就是Shift、Ctrl、Alt 和Meta(在Windows 键盘中是Windows 键,在苹果机中
是Cmd 键),它们经常被用来修改鼠标事件的行为。DOM 为此规定了4 个属性,表示这些修改键的状
态:shiftKey、ctrlKey、altKey 和metaKey。这些属性中包含的都是布尔值,如果相应的键被按
下了,则值为true,否则值为false。当某个鼠标事件发生时,通过检测这几个属性就可以确定用户
是否同时按下了其中的键。来看下面的
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event) {
event = EventUtil.getEvent(event);
var keys = new Array();
if (event.shiftKey) {
keys.push("shift");
}
if (event.ctrlKey) {
keys.push("ctrl");
}
if (event.altKey) {
keys.push("alt");
}
if (event.metaKey) {
keys.push("meta");
}
alert("Keys: " + keys.join(","));
});
DOM通过event 对象的relatedTarget 属性提供了相关元素的信
只对于mouseover和mouseout 事件才包含值,其它值是null
IE8及之前版本不支持relatedTarget
mouseover -->IE 的fromElement
mouseout --》IE 的toElement
继续完善
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;
}
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
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;
}
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
getRelatedTarget: function(event) {
if (event.relatedTarget) {
return event.relatedTarget;
} else if (event.toElement) {
return event.toElement;
} else if (event.fromElement) {
return event.fromElement;
} else {
return null;
}
}
};
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mouseout", function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var relatedTarget = EventUtil.getRelatedTarget(event);
alert("Moused out of " + target.tagName + " to " + relatedTarget.tagName);
//Moused out of DIV to BODY
鼠标按钮
DOM的button 属性可能有如下3 个值:0 表示主鼠标按钮,1 表示中间的鼠
标按钮(鼠标滚轮按钮),2 表示次鼠标按钮。在常规的设置中,主鼠标按钮就是鼠标左键,而次鼠标
按钮就是鼠标右
最常见的做法就是将IE 模型规范化为DOM 方式
添加getButton
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;
}
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
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;
}
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
getRelatedTarget: function(event) {
if (event.relatedTarget) {
return event.relatedTarget;
} else if (event.toElement) {
return event.toElement;
} else if (event.fromElement) {
return event.fromElement;
} else {
return null;
}
},
getButton: function(event) {
if (document.implementation.hasFeature("MouseEvents", "2.0")) {
return event.button;
} else {
switch (event.button) {
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4:
return 1;
}
}
}
};
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mousedown", function(event) {
event = EventUtil.getEvent(event);
alert(EventUtil.getButton(event)); //左0 中1 右2
});
鼠标滚轮:
mousewheel 事件时显示wheelDelta 的值,
ff:支持:DOMMouseScroll
EventUtil.addHandler(document, "mousewheel", function(event){
event = EventUtil.getEvent(event);
console.log(event.wheelDelta); //上120,下-120
});
//ff
EventUtil.addHandler(window, "DOMMouseScroll", function(event) {
event = EventUtil.getEvent(event); // 上-3 下3
alert(event.detail);
});
整合添加:
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;
}
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
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;
}
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
getRelatedTarget: function(event) {
if (event.relatedTarget) {
return event.relatedTarget;
} else if (event.toElement) {
return event.toElement;
} else if (event.fromElement) {
return event.fromElement;
} else {
return null;
}
},
getButton: function(event) {
if (document.implementation.hasFeature("MouseEvents", "2.0")) {
return event.button;
} else {
switch (event.button) {
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4:
return 1;
}
}
},
getWheelDelta: function(event) {
if (event.wheelDelta) {
return (event.wheelDelta);
} else {
return -event.detail * 40;
}
}
}
//使用
//handleMouseWheel()函数可以用作两个事件的处理程序(如果指定的事件不存在,则为该事件指定处
理程序的代码就会静默地失败)
function handleMouseWheel(event){
event = EventUtil.getEvent(event);
var delta = EventUtil.getWheelDelta(event);
alert(delta); //下-120,上120
}
EventUtil.addHandler(document, "mousewheel", handleMouseWheel);
EventUtil.addHandler(document, "DOMMouseScroll", handleMouseWheel);
键盘与文本
键盘事件:
keydown当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件
keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。
按下Esc 键也会触发这个事件。Safari 3.1 之前的版本也会在用户按下非字符键时触发keypress
事件。
keyup:当用户释放键盘上的键时触发。
文本事件:textInput,对keypress补充
按下字符:keydown--》keypress【前两个是文本变化之前】--》keyup
连续按:连接触发:keydown--》keypress
按下非字符:keydown --》keyup
也有shiftKey、ctrlKey、altKey 和metaKey[非IE] 属性
键码
在发生keydown 和keyup 事件时,event 对象的keyCode 属性中会包含一个代码
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keyup", function(event) {
event = EventUtil.getEvent(event);
alert(event.keyCode); //显示keyCode值
});
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;
}
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
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;
}
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
getRelatedTarget: function(event) {
if (event.relatedTarget) {
return event.relatedTarget;
} else if (event.toElement) {
return event.toElement;
} else if (event.fromElement) {
return event.fromElement;
} else {
return null;
}
},
getButton: function(event) {
if (document.implementation.hasFeature("MouseEvents", "2.0")) {
return event.button;
} else {
switch (event.button) {
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4:
return 1;
}
}
},
getWheelDelta: function(event) {
if (event.wheelDelta) {
return (event.wheelDelta);
} else {
return -event.detail * 40;
}
},
getCharCode: function(event) {
if (typeof event.charCode == "number") {
return event.charCode;
} else {
return event.keyCode;
}
}
}
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event) {
event = EventUtil.getEvent(event);
alert(EventUtil.getCharCode(event));//值出字符编码
});
之后可以使用String.fromCharCode(number),再转为正常字符;
所有非字符
特殊情况: ff o 分号键keyCode = 59 【ASCII分号编码】; ie safari分号键 = 186;【按键编码】
由于存在跨浏览器问题,因此本书不推荐使用key、keyIdentifier 或char。
textInput事件
替代keypress 的textInput 事件的行为稍有不同。
区别一:就是任何可以获得焦点的元素都可以触发keypress 事件,但只有可编辑区域才能触发textInput事件。
区别二:textInput 事件只会在用户按下能够输入实际字符的键时才会被触发,
而keypress事件则在按下那些能够影响文本显示的键时也会触发(例如退格键)。
由于textInput 事件主要考虑的是字符,因此它的event 对象中还包含一个data 属性,这个属
性的值就是用户输入的字符(而非字符编码)。换句话说,用户在没有按上档键的情况下按下了S 键,
data 的值就是"s",而如果在按住上档键时按下该键,data 的值就是"S"。
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "textInput", function(event){
event = EventUtil.getEvent(event);
alert(event.data); //按下什么输出什么
});
HTML5事件
- contextmenu 事件显示菜单,支持contextmenu 事件的浏览器有IE、Firefox、Safari、Chrome 和Opera 11+。
EventUtil.addHandler(window, "load", function(event) {
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "contextmenu", function(event) {
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
var menu = document.getElementById("myMenu");
menu.style.left = event.clientX + "px";
menu.style.top = event.clientY + "px";
menu.style.visibility = "visible";
});
EventUtil.addHandler(document, "click", function(event) {
document.getElementById("myMenu").style.visibility = "hidden";
});
});
- beforeunload 事件,是为了让开发人员有可能在页面卸载前阻止这一操作,为了显示这个弹出对话框,必须将event.returnValue 的值设置为要显示给用户的字符串(对IE 及Fiefox 而言),同时作为函数的值返回
[
returnValue Boolean 读/写 默认值为true,但将其设置为false就可以取消事件的默认行为(与
DOM中的preventDefault()方法的作用相同)
]
EventUtil.addHandler(window, "beforeunload", function(event) {
event = EventUtil.getEvent(event);
var message = "I'm really going to miss you if you go.";
event.returnValue = message;
return message;
});
- DOMContentLoaded 事件 DOM加载完成后触发;
要处理DOMContentLoaded 事件,可以为document 或window 添加相应的事件处理程序(尽管
这个事件会冒泡到window,但它的目标实际上是document)。来看下面的例子。
注:这个事件始终都会在load 事件之前触发
EventUtil.addHandler(document, "DOMContentLoaded", function(event){
alert("Content loaded");
});
不支持的浏览器使用:
setTimeout(function(){
//在此添加事件处理程序
}, 0);
- readystatechange 事件
支持readystatechange 事件的每个对象都有一个readyState 属性
uninitialized(未初始化):对象存在但尚未初始化。
loading(正在加载):对象正在加载数据。
loaded(加载完毕):对象加载数据完成。
interactive(交互):可以操作对象了,但还没有完全加载。
complete(完成):对象已经加载完毕。
不支持则会跳过,所以一般readystatechange事件经常会少于4次;而readyState则总是不连续;
EventUtil.addHandler(document, "readystatechange", function(event) {
if (document.readyState == "interactive") {
alert("Content loaded");
}
});
同时检测交互和完成阶
EventUtil.addHandler(document, "readystatechange", function(event) {
if (document.readyState == "interactive" || document.readyState == "complete") {
EventUtil.removeHandler(document, "readystatechange", arguments.callee);
alert("Content loaded");
}
});
对于上面的代码来说,当readystatechange 事件触发时,会检测document.readyState 的值,
看当前是否已经进入交互阶段或完成阶段。如果是,则移除相应的事件处理程序以免在其他阶段再执行。
注意,由于事件处理程序使用的是匿名函数,因此这里使用了arguments.callee 来引用该函数。然
后,会显示一个警告框,说明内容已经加载完毕。这样编写代码可以达到与使用DOMContentLoaded
十分相近的效
下面展示了一段加载外部JavaScript/css 文件的代码
//js
EventUtil.addHandler(window, "load", function() {
var script = document.createElement("script");
EventUtil.addHandler(script, "readystatechange", function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (target.readyState == "loaded" || target.readyState == "complete") {
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("Script Loaded");
}
});
script.src = "example.js";
document.body.appendChild(script);
});
//css
EventUtil.addHandler(window, "load", function() {
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
EventUtil.addHandler(script, "readystatechange", function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (target.readyState == "loaded" || target.readyState == "complete") {
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("CSS Loaded");
}
});
link.href = "example.css";
document.getElementsByTagName("head")[0].appendChild(link);
});
- pageshow 和pagehide 事件
(略)
- hashchange 事件
必须要把hashchange 事件处理程序添加给window 对象,然后URL 参数列表只要变化就会调用
它。此时的event 对象应该额外包含两个属性:oldURL 和newURL。这两个属性分别保存着参数列表
变化前后的完整URL。例如:
EventUtil.addHandler(window, "hashchange", function(event){
alert("Old URL: " + event.oldURL + "\nNew URL: " + event.newURL);
}); //前后变化,完整url
EventUtil.addHandler(window, "hashchange", function(event){
alert("Current hash: " + location.hash);
});//当前 #后字符
检测是否支持
var isSupported = ("onhashchange" in window) && (document.documentMode ===
undefined || document.documentMode > 7);
13.4.8 设备事件
- orientationchange 事件
苹果公司为移动Safari 中添加了orientationchange 事件,以便开发人员能够确定用户何时将设
备由横向查看模式切换为纵向查看模式。移动Safari 的window.orientation 属性中可能包含3 个值:
0 表示肖像模式,90 表示向左旋转的横向模式(“主屏幕”按钮在右侧),-90 表示向右旋转的横向模
式(“主屏幕”按钮在左侧)。相关文档中还提到一个值,即180 表示iPhone 头朝下;但这种模式至今
尚未得到支持。图13-10 展示了window.orientation 的每个值的含义
只要用户改变了设备的查看模式,就会触发orientationchange 事件。此时的event 对象不包
含任何有价值的信息,因为唯一相关的信息可以通过window.orientation 访问到。下面是使用这个
事件的典型示例
EventUtil.addHandler(window, "load", function(event) {
var div = document.getElementById("myDiv");
div.innerHTML = "Current orientation is " + window.orientation;
//改变时触发
EventUtil.addHandler(window, "orientationchange", function(event) {
div.innerHTML = "Current orientation is " + window.orientation;
});
});
//横屏
window.orientation == 90 || window.orientation == -90
//竖屏
window.orientation == 0 || window.orientation == 180
//粗略检测,并不准,ie都返回false
alert(document.implementation.hasFeature("orientationchange", "2.0"))
- deviceorientation 事件
- 事件的意图是告诉开发人员设备在空间中朝向哪儿,而不是如何移动;
设备在三维空间中是靠x、y 和z 轴来定位的 三个值都是0
x轴: 左 --》 右;
y轴: 下 --》 上;
z轴: 后 --》 前;
事件对象包含以下5 个属性。
alpha:在围绕z 轴旋转时(即左右旋转时),y 轴的度数差;是一个介于0 到360 之间的浮点数。
beta:在围绕x 轴旋转时(即前后旋转时),z 轴的度数差;是一个介于180 到180 之间的浮点数。
gamma:在围绕y 轴旋转时(即扭转设备时),z 轴的度数差;是一个介于90 到90 之间的浮点数。
absolute:布尔值,表示设备是否返回一个绝对值。
compassCalibrated:布尔值,表示设备的指南针是否校准过。
13.4.9 触摸与手势事件
touchstart/touchmove/touchend/touchcancel
每個event對象都有以下常見屬性;
bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey 和metaKey。
touches:表示当前跟踪的触摸操作的Touch 对象的数组。
targetTouchs:特定于事件目标的Touch 对象的数组。
changeTouches:表示自上次触摸以来发生了什么改变的Touch 对象的数组。
每个Touch 对象包含下列属性。
clientX:触摸目标在视口中的x 坐标。
clientY:触摸目标在视口中的y 坐标。
identifier:标识触摸的唯一ID。
pageX:触摸目标在页面中的x 坐标。
pageY:触摸目标在页面中的y 坐标。
screenX:触摸目标在屏幕中的x 坐标。
screenY:触摸目标在屏幕中的y 坐标。
target:触摸的DOM 节点目标。
使用这些属性可以跟踪用户对屏幕的
function handleTouchEvent(event) {
if (event.touches.length == 1) {
var _output = document.getElementById('output')
switch (event.type) {
case 'touchstart':
_output.innerHTML = 'Touch started' + event.touches[0].clientX + ',' + event.touches[0].clientY
break;
case 'touchend':
_output.innerHTML += '<br> Touche ended' + event.changedTouches[0].clientX + ':' +
event.changedTouches[0].clientY + ';';
break;
case 'touchmove':
event.preventDefault();
_output.innerHTML += '<br> Touch move' +
event.changedTouches[0].clientX + ':' +
event.changedTouches[0].clientY + ';';
break;
}
}
}
EventUtil.addHandler(document, 'touchstart', handleTouchEvent)
EventUtil.addHandler(document, 'touchend', handleTouchEvent)
EventUtil.addHandler(document, 'touchmove', handleTouchEvent)
當touchstart發生: 觸摸信息輸出到
中 當touchmove發生:取消默認行為,阻止滾動,輸出變化信息
當touchend發生時:輸出有關觸摸操作信息,在touchend發生時:touches集合中就沒有任何信息,因為不存在活動觸摸操作,此時必須使用changeTouches集合發生順序如下:
(1) touchstart
(2) mouseover
(3) mousemove(一次)
(4) mousedown
(5) mouseup
(6) click
(7) touchend
- 手势事件
當兩個手指觸摸時,就會產生手勢,手勢通常會改變顯示項大小,或旋轉顯示項,三個事件
gesturestart:当一个手指已经按在屏幕上而另一个手指又触摸屏幕时触发。
gesturechange:当触摸屏幕的任何一个手指的位置发生变化时触发。
gestureend:当任何一个手指从屏幕上面移开时触发。当一个手指在屏幕时,触发touchstart
另一个手指放在屏幕,先触发gesturestart事件,随后触发该手指的touchstart
如果一个或两个手指在屏幕移动,触发gesturechange事件,但只要有一手指移开,触发gestureend事件,
最后再触发该手指的touchend事件;每个event对象包含::
bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、
ctrlKey 和metaKey、还有两个额外属性:rotation、scalerotation: 表示手指变化引起的旋转角度, -值逆时针,+ 顺时针(该值从0开始)
scale: 表示两个手指距离变化情况,如内收缩会缩短距离,从1开始;function handleTouchEvent(event) { var _output = document.getElementById('output') switch (event.type) { case 'gesturestart': _output.innerHTML = 'Gesture started rotation = ' + event.rotation + ', scale' + event.scale; break; case 'gestureend': _output.innerHTML += '<br> Gesture end rotation = ' + event.rotation + ', scale' + event.scale; break; case 'gesturechange': event.preventDefault(); _output.innerHTML += '<br> Gesture change rotation = ' + event.rotation + ', scale' + event.scale; break; } } EventUtil.addHandler(document, 'gesturestart', handleTouchEvent) EventUtil.addHandler(document, 'gestureend', handleTouchEvent) EventUtil.addHandler(document, 'gesturechange', handleTouchEvent)
13.5.1 事件委托
var list = document.getElementById('myLinks') EventUtil.addHandler(list, 'click', function(event) { //父级绑定click event = EventUtil.getEvent(event) var target = EventUtil.getTarget(event) switch (target.id) { //检测目标元素属性 case 'doSomething': document.title = 'i cahnge the document is ttile' break; case 'goSomewhere': location.href = 'http://www.x.com'; break; case 'sayHi': alert('hi') break; } })
var btn = document.getElementById('myBtn') btn.onclick = function(){ document.getElementById('myDiv').innerHTML = 'processing' //按钮被替换了,但click事件还在 } //所以 var btn = document.getElementById('myBtn') btn.onclick = function(){ btn.onclick = null; //先移除 document.getElementById('myDiv').innerHTML = 'processing' //按钮被替换了,但click事件还在 }
13.6.1 DOM中的事件模拟
- 模拟鼠标事件
创建新的鼠标事件对象并为其指定必要的信息,就可以模拟鼠标事件。创建鼠标事件对象的方法是为createEvent()传入字符串"MouseEvents"。返回的对象有一个名为initMouseEvent()方法,
用于指定与该鼠标事件有关的信息。这个方法接收15 个参数,分别与鼠标事件中每个典型的属性一一
对应;这些参数的含义如下。对应;这些参数的含义如下。
type(字符串):表示要触发的事件类型,例如"click"。
bubbles(布尔值):表示事件是否应该冒泡。为精确地模拟鼠标事件,应该把这个参数设置为
true。
cancelable(布尔值):表示事件是否可以取消。为精确地模拟鼠标事件,应该把这个参数设
置为true。
view(AbstractView):与事件关联的视图。这个参数几乎总是要设置为document.defaultView。
detail(整数):与事件有关的详细信息。这个值一般只有事件处理程序使用,但通常都设置为0。
screenX(整数):事件相对于屏幕的X 坐标。
screenY(整数):事件相对于屏幕的Y 坐标。
clientX(整数):事件相对于视口的X 坐标。
clientY(整数):事件想对于视口的Y 坐标。
ctrlKey(布尔值):表示是否按下了Ctrl 键。默认值为false。
altKey(布尔值):表示是否按下了Alt 键。默认值为false。
shiftKey(布尔值):表示是否按下了Shift 键。默认值为false。
metaKey(布尔值):表示是否按下了Meta 键。默认值为false。
button(整数):表示按下了哪一个鼠标键。默认值为0。
relatedTarget(对象):表示与事件相关的对象。这个参数只在模拟mouseover 或mouseout
时使用。var btn = document.getElementById('myBtn') //创建事件对象 var event = document.createEvent('mouseEvents') //初始化 event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); //触发 btn.dispatchEvent(event)
- 模拟键盘事件
一、DOM3 级规定,调用createEvent()并传入"KeyboardEvent"就可以创建一个键盘事件。返回的事件对象会包含一个initKeyEvent()方法,这个方法接收下列参数
type(字符串):表示要触发的事件类型,如"keydown"。
bubbles(布尔值):表示事件是否应该冒泡。为精确模拟鼠标事件,应该设置为true。
cancelable(布尔值):表示事件是否可以取消。为精确模拟鼠标事件,应该设置为true。
view (AbstractView ):与事件关联的视图。这个参数几乎总是要设置为document.
defaultView。
key(布尔值):表示按下的键的键码。
location(整数):表示按下了哪里的键。0 表示默认的主键盘,1 表示左,2 表示右,3 表示
数字键盘,4 表示移动设备(即虚拟键盘),5 表示手柄。
modifiers(字符串):空格分隔的修改键列表,如"Shift"。
repeat(整数):在一行中按了这个键多少次。DOM3级不提倡使用keypress 事件,因此只能利用这种技术来模拟keydown 和keyup 事件。
var textbox = document.getElementById("myTextbox"), event; //以DOM3 级方式创建事件对象 if (document.implementation.hasFeature("KeyboardEvents", "3.0")) { event = document.createEvent("KeyboardEvent"); //初始化事件对象 event.initKeyboardEvent("keydown", true, true, document.defaultView, "a", 0, "Shift", 0); } //触发事件 textbox.dispatchEvent(event);
这个例子模拟的是按住Shift 的同时又按下A 键。在使用document.createEvent
("KeyboardEvent")之前,应该先检测浏览器是否支持DOM3 级事件;其他浏览器返回一个非标准的
KeyboardEvent 对象。二、在Firefox 中,调用createEvent()并传入"KeyEvents"就可以创建一个键盘事件。返回的事件
对象会包含一个initKeyEvent()方法,这个方法接受下列10 个参数。 type(字符串):表示要触发的事件类型,如"keydown"。
bubbles(布尔值):表示事件是否应该冒泡。为精确模拟鼠标事件,应该设置为true。
cancelable(布尔值):表示事件是否可以取消。为精确模拟鼠标事件,应该设置为true。
view(AbstractView):与事件关联的视图。这个参数几乎总是要设置为document.default-
View。
ctrlKey(布尔值):表示是否按下了Ctrl 键。默认值为false。
altKey(布尔值):表示是否按下了Alt 键。默认值为false。
shiftKey(布尔值):表示是否按下了Shift 键。默认值为false。
metaKey(布尔值):表示是否按下了Meta 键。默认值为false。
keyCode(整数):被按下或释放的键的键码。这个参数对keydown 和keyup 事件有用,默认
值为0。
charCode(整数):通过按键生成的字符的ASCII 编码。这个参数对keypress 事件有用,默
认值为0。//只适用于Firefox var textbox = document.getElementById("myTextbox") //创建事件对象 var event = document.createEvent("KeyEvents"); //初始化事件对象 event.initKeyEvent("keypress", true, true, document.defaultView, false, false, false, false, 65, 65); //触发事件 textbox.dispatchEvent(event);
三、其它浏览器
首先创建了一个通用事件,然后调用initEvent()对其进行初始化,最后又为其添加了
键盘事件的具体信息。在此必须要使用通用事var textbox = document.getElementById("myTextbox"); //创建事件对象 var event = document.createEvent("Events"); //初始化事件对象 event.initEvent(type, bubbles, cancelable); event.view = document.defaultView; event.altKey = false; event.ctrlKey = false; event.shiftKey = false; event.metaKey = false; event.keyCode = 65; event.charCode = 65; //触发事件 textbox.dispatchEvent(event);
- 模拟其他事件
模拟变动事件, 可以使用createEvent("MutationEvents") 创建一个包含initMutationEvent() 方法的变动事件对象。这个方法接受的参数包括: type 、bubbles 、
cancelable、relatedNode、preValue、newValue、attrName 和attrChange。下面来看一个模
拟变动事件的例子。var event = document.createEvent("MutationEvents"); event.initMutationEvent("DOMNodeInserted", true, false, someNode, "","","",0); targ et.dispatchEvent(event);
以上代码模拟了DOMNodeInserted
- 自定义DOM 事件
创建新的自定义事件,可以调用createEvent("CustomEvent")。返回的对象有一个名为initCustomEvent()的方法
var div = document.getElementById("myDiv"), event; EventUtil.addHandler(div, "myevent", function(event) { alert("DIV: " + event.detail); }); EventUtil.addHandler(document, "myevent", function(event) { alert("DOCUMENT: " + event.detail); }); if (document.implementation.hasFeature("CustomEvents", "3.0")) { event = document.createEvent("CustomEvent"); event.initCustomEvent("myevent", true, false, "Hello world!"); div.dispatchEvent(event); }
创建了一个冒泡事件"myevent"。而event.detail 的值被设置成了一个简单的字符串,
然后在元素和document 上侦听这个事件。因为initCustomEvent()方法已经指定这个事件应
该冒泡,所以浏览器会负责将事件向上冒泡到document。支持自定义DOM事件的浏览器有IE9+和Firefox 6+。
13.6.2 IE中的事件模拟
IE8之前的版本
思路相似:先创建event 对象,然后为其指定相应的信息,然后再使用该对象来触发事件
调用document.createEventObject()方法可以在IE 中创建event 对象。但与DOM方式不同的是,这个方法不接受参数,结果会返回一个通用的event 对象
最后一步就是在目标上调用fireEvent()方法fireEvent(事件处理程序的名称,event对象)
在调用fireEvent()方法时,会自动为event 对象添加srcElement 和type 属性
其他属性则都是必须通过手工添加的。换句话说,模拟任
何IE 支持的事件都采用相同的模式。例如,下面的代码模拟了在一个按钮上触发click 事件过程var btn = document.getElementById("myBtn"); //创建事件对象 var event = document.createEventObject(); //初始化事件对象 event.screenX = 100; event.screenY = 0; event.clientX = 0; event.clientY = 0; event.ctrlKey = false; event.altKey = false; event.shiftKey = false; event.button = 0; //触发事件 btn.fireEvent("onclick", event); //触发keypress事件 var textbox = document.getElementById("myTextbox"); //创建事件对象 var event = document.createEventObject(); //初始化事件对象 event.altKey = false; event.ctrlKey = false; event.shiftKey = false; event.keyCode = 65; //触发事件 textbox.fireEvent("onkeypress", event);
零js基礎 | 程序設計 | 12 - DOM2和DOM3
January 18, 2014 var supportsDOM2Core = document.implementation.hasFeature("Core", "2.0"); var supportsDOM3Core = document.implementation.hasFeature("Core", "3.0"); var supportsDOM2HTML = document.implementation.hasFeature("HTML", "2.0"); var supportsDOM2Views = document.implementation.hasFeature("Views", "2.0"); var supportsDOM2XML = document.implementation.hasFeature("XML", "2.0");
混合svg写法
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Example XHTML page</title> </head> <body> <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100" style="width:100%; height:100%"> <rect x="0" y="0" width="100" height="100" style="fill:red" /> </svg> </body> </html>
在DOM2 级中,Node 类型包含下列特定于命名空间的属性。
localName:不带命名空间前缀的节点名称。
namespaceURI:命名空间URI 或者(在未指定的情况下是)null。
prefix:命名空间前缀或者(在未指定的情况下是)null。nodeName 等于prefix+":"+ localName
<html>元素來說
localName:html
tagName:html
namespaceURI:http://www.w3.org/1999/xhtml
prefix:null<s:svg>元素
localName:svg
tagName:s:svg
namespaceURI:http://www.w3.org/2000/svg
prefix:sDOM3 级在此基础上更进一步,又引入了下列与命名空间有关的方法。
isDefaultNamespace(namespaceURI):在指定的namespaceURI 是当前节点的默认命名空
间的情况下返回true。
lookupNamespaceURI(prefix):返回给定prefix 的命名空间。
lookupPrefix(namespaceURI):返回给定namespaceURI 的前缀。Document变化
只有在文档中存在两个或多个命名空间时,这些与命名空间有关的方法才是必需的。
//创建一个新的SVG 元素 var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); //创建一个属于某个命名空间的新特性 var att = document.createAttributeNS("http://www.somewhere.com", "random"); //取得所有XHTML 元素 var elems = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "*");
Element变化
getAttributeNS(namespaceURI,localName):取得属于命名空间namespaceURI 且名为
localName 的特性。getAttributeNodeNS(namespaceURI,localName)
getElementsByTagNameNS(namespaceURI, tagName)
hasAttributeNS(namespaceURI,localName)
removeAttriubteNS(namespaceURI,localName)
setAttributeNS(namespaceURI,qualifiedName,value)
setAttributeNodeNS(attNode)除了第一个参数之外,这些方法与DOM1 级中相关方法的作用相同;第一个参数始终都是一个命
名空间URI。NamedNodeMap变化
getNamedItemNS(namespaceURI,localName)取得属于命名空间namespaceURI 且名为
localName 的项。
removeNamedItemNS(namespaceURI,localName)其它变化:
- DocumentType 类型的变化 类型新增了3 个属性:publicId、systemId 和internalSubset。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
中的:publicId == "-//W3C//DTD HTML 4.01//EN",
systemId =="http://www.w3.org/TR/html4/strict.dtd"alert(document.doctype.publicId); alert(document.doctype.systemId);
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" [<!ELEMENT name (#PCDATA)>] >
document.doctype.internalSubset //"<!ELEMENT name (#PCDATA)>"
- Document 类型的变化
importNode()- Node 类型的变化
isSupported()与DOM1级为document.implementation 引入的hasFeature()方法类似,isSupported()方法用于确定当前节点具有什么能力
if (document.body.isSupported("HTML", "2.0")){ //执行只有"DOM2 级HTML"才支持的操作 }
DOM3 额外引入数据:setUserData(要设置的键,实际的数据(可以是任何数据类型),处理函数)
var value = document.body.getUserData("name"); //创建div var div = document.createElement("div"); //添加数据 div.setUserData("name", "Nicholas", function(operation, key, value, src, dest) { if (operation == 1) { dest.setUserData(key, value, function() {}); } }); //使用cloneNode复制时,会调用处理函数,从而将数据自动复制到副本 var newDiv = div.cloneNode(true); //调用getUserData()时,返回与原始结点相同的值 alert(newDiv.getUserData("name")); //"Nicholas"
- 框架的变化
var iframe = document.getElementById("myIframe"); var iframeDoc = iframe.contentDocument; //在IE8 以前的版本中无效
兼容
对象,可以使用下列代码。 var iframe = document.getElementById("myIframe"); var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
cssText
在写入模式下,赋给cssText 的值会重写整个style 特性的值;也就是
说,以前通过style 特性指定的样式信息都将丢失。例如,如果通过style 特性为元素设置了边框,
然后再以不包含边框的规则重写cssText,那么就会抹去元素上的边框。下面是使用cssText 属性的var div = document.getElementsByTagName('div')[0] div.style.cssText = 'width:25px;height:100px;background-color:red' console.log(div.style.cssText) //width:25px;height:100px;background-color:red for (var i = 0; i < div.style.length; i++) { alert(div.style[i]) //width/height/backgorund-color alert(div.style.item(i)) console.log(div.style.getPropertyValue(div.style[i])) //25px 100px red }
getPropertyValue()方法取得的始终都是CSS 属性值的字符串表
getPropertyCSSValue()它返回一个包含两个属性的CSSValue 对象,这两个属性分,别是:cssText 和cssValueType。div.style.removeProperty('width') //移除
getComputedStyle() 要取得樣式的元素與一個為元素字條串如:after,第二個參數可以為null;
無論樣式表還是行間樣式;計算後的值
var myDiv = document.getElementById("myDiv"); var computedStyle = document.defaultView.getComputedStyle(myDiv, null); alert(computedStyle.backgroundColor); // "red" alert(computedStyle.width); // "100px" alert(computedStyle.height); // "200px" alert(computedStyle.border); // 在某些浏览器中是"1px solid black"
IE下
var myDiv = document.getElementById("myDiv"); var computedStyle = myDiv.currentStyle; alert(computedStyle.backgroundColor); //"red" alert(computedStyle.width); //"100px" alert(computedStyle.height); //"200px" alert(computedStyle.border); //undefined
綜合
function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr] } else { return getComputedStyle(obj, false)[attr] } };
function getStyleSheet(element){ return element.sheet || element.styleSheet; } //取得第一个<link/>元素引入的样式表 var link = document.getElementsByTagName("link")[0]; var sheet = getStyleSheet(link);
var sheet = document.styleSheets[0]; var rules = sheet.cssRules || sheet.rules; //取得规则列表 var rule = rules[0]; //取得第一条规则 alert(rule.selectorText); //"div.box" alert(rule.style.cssText); //完整的CSS 代码 alert(rule.style.backgroundColor); //"blue" alert(rule.style.width); //"100px" alert(rule.style.height); //"200px"
<style>.box{width:100px;}</style> ----sheet = document.styleSheets[0]; <link rel="stylesheet" href="style.css">----sheet = document.styleSheets[1]; <style>.box{height}</style> --sheet = document.styleSheets[2]; var rules = sheet.cssRules || sheet.rules; //取得规则列表 為每一個sheet裏的每一條;{}裏算一條
必须要注意的是,以这种方式修改规则会影响页面中适用于该规则的所有元素。换句话说,如果有两个带有box 类的
元素,那么这两个元素都会应用修改后的样式。var sheet = document.styleSheets[0]; var rules = sheet.cssRules || sheet.rules; //取得规则列表 var rule = rules[0]; //取得第一条规则 rule.style.backgroundColor = "yellow"
function getTop(o) { var oTop = o.offsetTop; var current = o.offsetParent; while (current !== null) { oTop += current.offsetTop; current = current.offsetParent; } return oTop; } alert(getTop(box))
所以视口
function getViewport() { if (document.compatMode == "BackCompat") { return { width: document.body.clientWidth, height: document.body.clientHeight }; } else { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }; } } console.log(getViewport())
scrollHeight/scrollWidth/scrollLeft/scrollTop
有些元素(例如<html>元素),即使没有执行任何代码也能自动地添加滚动条;但另外一些元素,则需要通过CSS 的
overflow 属性进行设置才能滚动
scrollWidth 和scrollHeight 主要用于确定元素内容的实际大小无滚动条时:scrollWidth = clientWidth
实际页面大小:document.documentElement.scrollHeight
视口大小:document.documentElement.clientHeight
无滚动条时:
IE7 chromedocument.documentElement.scrollHeight(页面占用元素大小) < document.documentElement.clientHeight(视口大小)
IE8 /IE9 /IE10 /IE11 firfox
document.documentElement.scrollHeight == document.documentElement.clientHeight
getBoundingClientRect()返回矩形,ie8(2.2),ie9(0,0)
,right 和left 的差值与offsetWidth 的值相等,而bottom 和top 的差值与offsetHeight
相等。而且,left 和top 属性大致等于使用本章前面定义的getElementLeft()和getElementTop()
函数取得的值function getBoundingClientRect(element) { var scrollTop = document.documentElement.scrollTop; var scrollLeft = document.documentElement.scrollLeft; if (element.getBoundingClientRect) { if (typeof arguments.callee.offset != "number") { var temp = document.createElement("div"); temp.style.cssText = "position:absolute;left:0;top:0;"; document.body.appendChild(temp); arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop; document.body.removeChild(temp); temp = null; } var rect = element.getBoundingClientRect(); var offset = arguments.callee.offset; return { left: rect.left + offset, right: rect.right + offset, top: rect.top + offset, bottom: rect.bottom + offset }; } else { var actualLeft = getElementLeft(element); var actualTop = getElementTop(element); return { left: actualLeft - scrollLeft, right: actualLeft + element.offsetWidth - scrollLeft, top: actualTop - scrollTop, bottom: actualTop + element.offsetHeight - scrollTop } } } alert(getBoundingClientRect(div).left) alert(getBoundingClientRect(div).right) alert(div.offsetWidth) ` DOM2 级遍历和范围 检测是否有DOM2级能力(IE8返回false)
var supportsTraversals = document.implementation.hasFeature("Traversal", "2.0");
var supportsNodeIterator = (typeof document.createNodeIterator == "function");
var supportsTreeWalker = (typeof document.createTreeWalker == "function");从document 开始依序向前,向下遍历,到开始根节点为止。NodeIterator 和TreeWalker 都以这种方式执行遍历。 createNodeIterator(root[起点],whatToShow[哪些节点数字代码],filter[NodeFilter对象,一个表示接受或拒绝的特定节点],entityReferenceExpansion[布尔值,表示是否扩展实体引用]) DOM2-----------------------------待放
零js基礎 | 程序設計 | 11 - DOM扩展
January 17, 2014 DOM扩展
var div = document.querySelector('#div') //返回NodeList實例 var div = document.querySelectorAll('.divClass') console.log(div.item(0)) console.log(div[0]) console.log(div)
matchesSelector(CSS選擇符),如果匹配 true,否則 false
if (matchesSelector(div[0], '#div.divClass')) { alert(true) } //兼容性 function matchesSelector(element, selector) { if (element.matchesSelector) { return element.matchesSelector(selector); } else if (element.msMatchesSelector) { return element.msMatchesSelector(selector); } else if (element.mozMatchesSelector) { return element.mozMatchesSelector(selector); } else if (element.webkitMatchesSelector) { return element.webkitMatchesSelector(selector); } else { throw new Error("Not supported."); } }
支持Element Traversal 规范的浏览器有IE 9+、Firefox 3.5+、Safari 4+、Chrome 和Opera 10+。
childElementCount:返回子元素(不包括文本节点和注释)的个数。
firstElementChild:指向第一个子元素;firstChild 的元素版。
lastElementChild:指向最后一个子元素;lastChild 的元素版。
previousElementSibling:指向前一个同辈元素;previousSibling 的元素版。
nextElementSibling:指向后一个同辈元素;nextSibling 的元素版。二、困扰你我的NodeList与HTMLCollection
相同点:1. 类数组。有length属性,可以用下标索引来访问其中的元素,但没有Array的slice等方法;
2. 只读。无法增删其中的元素;
3. 实时同步DOM树的变化。若DOM树有新元素加入,该类型的对象也会将新元素包含进来;
4. 可通过下标数字类型索引获取集合中指定位置的元素;
5. 可通过item({String | Number} 索引)方法获取集合中指定位置的元素,若通过索引找不到元素,则以第一个元素作为返回值。
不同点(主要表现在HTMLCollection比NodeList能力更强大):
1. HTMLCollection对象可通过namedItem({String} id或name)获取首个匹配的元素,若没有则返回null;
2. HTMLCollection对象可通过点方式获取第个id或name匹配的元素,若没有则返回undefined。
各浏览器选择器返回类型差别:
console.log(div instanceof NodeList) //true //帶有.divClass.username的元素 var div = document.getElementsByClassName('divClass username') console.log(div) console.log(div instanceof HTMLCollection) //true;
傳統刪class
var classNames = div.className.split(/\s+/); var pos = -1; for(var i=0;i<classNames.length;i++){ if(classNames[i] == 'user'){ pos = i; break; } } classNames.splice(i,1); div.className = classNames.join(' ');
classList,IE10+,chrome,firefox
div[0].classList.remove('username') div[0].classList.add('current') div[0].classList.toggle('divClass') if(div[0].classList.contains('password') && div[0].classList.contains('disabled')){ alert(true) }
焦点管理
var btn = document.getElementById('myButton') btn.focus() alert(document.activeElement == btn) //true //文档加载完成时,document.activeElement保存的是document.body引用 //确定文档是否获得了焦点 alert(document.hasFocus()) //true
HTMLDocument的变化
1、readyState 两个值 loading:正在加载;complete:已经完成if(document.readyState == 'complete'){ }
2、document.compatMode 的
值等于"CSS1Compat",而在混杂模式下,document.compatMode 的值等于"BackCompat"。if (document.compatMode == "CSS1Compat") { alert("Standards mode"); } else { alert("Quirks mode"); }
3、head属性
Chrome 和Safari 5。var head = document.head || document.getElementsByTagName("head")[0];
字符集属性
alert(document.charset); //"UTF-16" document.charset = "UTF-8";
defaultCharset,表示根据默认浏览器及操作系统的设置,当前文档默认的字符集
应该是什么if (document.charset != document.defaultCharset){ alert("Custom character set being used."); }
自定义数据属性
data-,通过dataset访问 ,dataset是DOMStringMap的一个实例,每个data-name形式的属性都会有一个对应的属性,
比如,自定义属性是data-myname,
那映射中对应的属性就是myname)<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>
var div = document.getElementById('myDiv') //获取 var appId = div.dataset.appId; var myName = div.dataset.myname; //设置 div.dataset.appId = 123456 div.dataset.myname = 'elmok' if(div.dataset.myname){ alert('hello ' + div.dataset.myname) }
插入标记
innerHTML不同浏览器返回的值大小写可能不同
outerHTML返回包括外层
insertAdjacentHTML()两个参数
"beforebegin",在当前元素之前插入一个紧邻的同辈元素;
"afterbegin",在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素;
"beforeend",在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素;
"afterend",在当前元素之后插入一个紧邻的同辈元素。<div id="content"></div>
//作为前一个同辈元素插入 con.insertAdjacentHTML("beforebegin", "<p>beforebegin!</p>"); //作为第一个子元素插入 con.insertAdjacentHTML("afterbegin", "<p>afterbegin!</p>"); //作为最后一个子元素插入 con.insertAdjacentHTML("beforeend", "<p>beforeend!</p>"); //作为后一个同辈元素插入 con.insertAdjacentHTML("afterend", "<p>afterend!</p>");
之后
<p>beforebegin!</p> <div id="content"> <p>afterbegin!</p> <p>beforeend!</p> </div> <p>afterend!</p>
在使用innerHTML、
outerHTML 属性和insertAdjacentHTML()方法时,最好先手工删除要被替换的元素的所有事件处理
程序和JavaScript 对象属性for (var i=0, len=values.length; i < len; i++){ ul.innerHTML += "<li>" + values[i] + "</li>"; //要避免这种频繁操作!! } //正确做法 var itemsHtml = ""; ...
scrollIntoView(),滚动标准作法
如果给这个方法传入true 作为参数,或者不传入任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐;
如果传入false 作为参数,调用元素会尽可能全部
出现在视口中,(可能的话,调用元素的底部会与视口顶部平齐。)不过顶部不一定平齐只有调用才会出现在视口中,类似于a标签本页定位的效果
var btn = document.getElementById('btn') var div = document.getElementById('content') btn.onclick = function(){ div.scrollIntoView(true) }
IE “文档模式”(document mode) 文档模式决定了你可以使用哪个级别的 CSS
要强制浏览器以某种模式渲染页面,可以使用 HTTP头部信息 X-UA-Compatible,或通过等价的 <meta>标签来设置://<meta http-equiv="X-UA-Compatible" content="IE=IEVersion"> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7"> var mode = document.documentMode; alert(mode) // 11/10/9/8/7/5....
children属性
。IE8及更早版本的 children 属性中也会包含注释节点,但 IE9之后的版本则 只返回元素节点contains()方法.
经常需要知道某个节点是不是另一个节点的后代alert(document.documentElement.contains(document.body)); //true
- innerText 属性
outerText 属性 //包括父级也替换滚动
scrollIntoView()
scrollIntoViewIfNeeded(alignCenter):只在当前元素在视口中不可见的情况下,才滚
动浏览器窗口或容器元素,最终让它可见。如果当前元素在视口中可见,这个方法什么也不做。
如果将可选的alignCenter 参数设置为true,则表示尽量将元素显示在视口中部(垂直方向)。
Safari 和Chrome 实现了这个方法。
scrollByLines(lineCount):将元素的内容滚动指定的行高,lineCount 值可以是正值,
也可以是负值。Safari 和Chrome 实现了这个方法。
scrollByPages(pageCount):将元素的内容滚动指定的页面高度,具体高度由元素的高度决
定。Safari 和Chrome 实现了这个方法。
希望大家要注意的是,scrollIntoView()和scrollIntoViewIfNeeded()的作用对象是元素的
容器,而scrollByLines()和scrollByPages()影响的则是元素自身。下面还是来看几个示例吧。//将页面主体滚动5 行 document.body.scrollByLines(5); //在当前元素不可见的时候,让它进入浏览器的视口 document.images[0].scrollIntoViewIfNeeded(); //将页面主体往回滚动1 页 document.body.scrollByPages(-1);
由于scrollIntoView()是唯一一个所有浏览器都支持的方法,因此还是这个方法最常用。
零js基礎 | 程序設計 | 10 - DOM
January 15, 2014 Node.nodeType == 1 元素节点
Node.nodeType == 2 属性节点
Node.nodeType == 3 文本节点<head> <meta charset="UTF-8"> <title></title> </head>
var h = document.getElementsByTagName('head')[0] // h.nodeType == 1 && console.log('this is an elem') h.nodeType == 1 && console.log(h.nodeName) //HEAD //每个节点都有一个childNodes 属性,其中保存着一个NodeList 对象。即类数组对象 console.log(h.childNodes[0]) console.log(h.childNodes[1]) console.log(h.childNodes[2]) console.log(h.childNodes[3]) console.log(h.childNodes[4]) console.log(h.parentNode) //5 console.log(h.childNodes.length)
把NodeTypelist转为数组
function convertToArray(nodes) { var array = null; try { array = Array.prototype.slice.call(nodes, 0); //针对非IE 浏览器 } catch (ex) { array = new Array(); for (var i = 0, len = nodes.length; i < len; i++) { array.push(nodes[i]); } } return array; }
每个Node都有一个parentNode属性,该属性指向文档树中的父节点;
nodeName //HEAD
parentNode //Html文档结构
childNodes //Nodelists节点集合
previousSibling //前一个
nextSibling //后一个如果没有子节点,那么firstChild 和lastChild 的值均为null
hasChildNodes(),这个方法在节点包含一或多个子节点的情况下返回true;应该说,这是比查询childNodes
列表的length 属性更简单的方法。h.hasChildNodes()
ownerDocument,所有节点的共同属性,指向整个文档节点
console.log(h.ownerDocument)
操作节点
末尾:
appendChild()相当于向childNodes列表尾部添加一个节点,,如果是已有节点,则相当于剪切;中间:
insertBefore(),向childNodes列表放在特定的位置上
insertBefore(要插入的节点,作为参照的节点) 【插入之后按顺序前后排:要插入的节点,作为参照的节点】插入节点后,被插
入的节点会变成参照节点的前一个同胞节点(previousSibling),returnedNode = someNode.insertBefore(newNode, null); alert(newNode == someNode.lastChild); //true //插入后成为第一个子节点 var returnedNode = someNode.insertBefore(newNode, someNode.firstChild); alert(returnedNode == newNode); //true alert(newNode == someNode.firstChild); //true //插入到最后一个子节点前面 returnedNode = someNode.insertBefore(newNode, someNode.lastChild); alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true
替换:
replaceChild(要插入的节点,要替换的节点)
//替换第一个子节点 var returnedNode = someNode.replaceChild(newNode, someNode.firstChild); //替换最后一个子节点 returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
移除:
removeChild(要移除的节点)前面介绍的四个方法操作的都是某个节点的子节点,也就是说,要使用这几个方法必须先取得父节
点(使用parentNode 属性)。另外,并不是所有类型的节点都有子节点,如果在不支持子节点的节点
上调用了这些方法,将会导致错误发生。共有的节点方法:
cloneNode(true/false),需要再使用 appendChild()/insertBefore()/replaceChild()
normalize() /这个方法唯一的作用就是处理文档树中的文本节点
Document类型 nodeType 的值为9;
nodeName 的值为"#document";
nodeValue 的值为null;
parentNode 的值为null;
ownerDocument 的值为 null;
Document 节点的子节点可以是DocumentType、Element、ProcessingInstruction
或Comment,内置访问子节点的快捷方式:
document.documentElement属性document.documentElement == document.childNodes[0] == document.firstChild ==
document.body 属性,直接指向<body>元素;
另一个子节点可能是:(浏览器支持有限)
var doctype = document.doctype; //取得对<!DOCTYPE>的引用
文档信息
document对象属性:
//取得文档标题 var originalTitle = document.title; //设置文档标题 document.title = "New page title"; //取得完整的URL var url = document.URL; //取得域名 var domain = document.domain; //取得来源页面的URL var referrer = document.referrer;
IE7下,name與ID相同的不同元素使用doc..ById會返回name的元素
alert(document.getElementById('myelem').nodeName)
不要讓表單字段的name與其他ID相同
var imgs = document.getElementsByTagName('img') var imgsname = imgs.namedItem('myimgName')
取得單選按鈕【常用】 getElementByName('color');
特殊集合
document.anchors:包括name屬性的a元素
document.links:包含所有帶href特性的a元素
document.images:包含所img元素
document.forms:包含所form元素一個element類型:
console.log(box.nodeType) //1 console.log(box.nodeName) //DIV console.log(box.nodeValue) //null console.log(box.parentNode) //<body......body> console.log(box.childNodes) //[elem..text..com...] console.log(box.tagName) //DIV tagName == nodeName
HTML元素,可读可写
alert(div.id); //"myDiv"" alert(div.className); //"bd" alert(div.title); //"Body text" alert(div.lang); //"en" alert(div.dir); //"ltr"
取得特性
alert(div.getAttribute("id")); //"myDiv" alert(div.getAttribute("class")); //"bd" alert(div.getAttribute("title")); //"Body text" alert(div.getAttribute("lang")); //"en" alert(div.getAttribute("dir")); //"ltr"
不存在返回null;
自定义属性应该加上data-前缀以便验证
var div = document.getElementById('myDiv') alert(div.my_special_attribute); //undefined(IE<8 弹 hello ,其它弹undefined)
设置属性:
box.setAttribute('style') //如果不写出错 box.setAttribute('style','')//正确自定义属性不能简单地使用.来设置
box.mycolor = 'red'; //使用.设置 alert(box.getAttribute('mycolor')) //null;(IE9<可弹出) //setAttributte配套使用; box.setAttribute('myspace','300px') alert(box.getAttribute('myspace'))
div.removeAttribute("class");完全清除,彻底删除元素特性;
- attributes 属性
Element 类型是使用attributes 属性的唯一一个DOM 节点类型。attributes 属性中包含一个NamedNodeMap,与NodeList 类似,也是一个“动态”的集合。元素的每一个特性都由一个Attr 节
点表示,每个节点都保存在NamedNodeMap 对象中。NamedNodeMap 对象拥有下列方法
遍历元素特性序列化
function outputAttributes(element) { var pairs = new Array(), attrName, attrValue, i, len; for (i = 0, len = element.attributes.length; i < len; i++) { attrName = element.attributes[i].nodeName; attrValue = element.attributes[i].nodeValue; if (element.attributes[i].specified) { pairs.push(attrName + "=\"" + attrValue + "\""); } } return pairs.join(" "); } console.log(outputAttributes(box)) // id="box" style="font-size:14px;color:#000" class="boxclass"
创建元素
``javascript
var div = document.body.appendChild('div') div.id = mydiv document.body.appendChild(div)
创建文本
var box = document.getElementById('box') if(box.childNodes[0].nodeType == 3 ){ alert(123) } alert(box.childNodes[0].nodeValue) alert(box.childNodes[0].data)
Text类型
var e = document.createElement('div') e.className = 'message' var atnode = document.createTextNode('fuck ') e.appendChild(atnode) var atnode2 = document.createTextNode('world') e.appendChild(atnode2) alert(e.childNodes.length) //2 个节点 e.normalize() //规范化成1个(合并) alert(e.childNodes.length) //1 alert(e.firstChild.nodeValue) //fuck world
分割 splitText()
var e = document.createElement('div') e.className = 'message' var Tnode = document.createTextNode('hello world') e.appendChild(Tnode) document.body.appendChild(e); var newNode = e.firstChild.splitText(2) alert(e.firstChild.nodeValue) //he 从2的位置开始分割
comment类型 注释类型 nodeType == 8 CDATASection类型 XML 文档,表示CDATA区域 nodeType == 4 DocumentType类型 nodeType == 10 DocumentFragment类型 nodeType == 11; Attr类型 存在于元素的attributes属性中的节点,nodeType == 11; ?? 创建动态脚本:
function loadScript(url) {
var script = document.createElement('script') script.type = 'text/javascript' script.src = url document.body.appendChild(script)
}
loadScript('main.js')代码执行
function loadScriptString(code) { var script = document.createElement('script') script.type = 'text/javascript' try { script.appendChild(document.createTextNode(code)) } catch (ex) { script.text = code; } document.body.appendChild(script) } loadScriptString("function sayHi(){alert('hi')}")`
其实效果跟 eval("function sayName(){alert('name')}") 一样; 动态样式
function loadStyles(url) { var link = document.createElement('link') link.rel = 'stylesheet' link.type = 'text/css' link.href = url var head = document.getElementsByTagName('head')[0] head.appendChild(link) } loadStyles('style.css')
function loadStyleString(css) {
var style = document.createElement('style') style.type = 'text/css' try { style.appendChild(document.createTextNode(css)) } catch (ex) { style.style.cssText = css; } document.getElementsByTagName('head')[0].appendChild(style);
}
loadStyleString('body{background:#1F78BF}')
操作表格
cell 11 cell 22 cell 33 cell 44 var table = document.createElement('table') table.border = 1; table.width = '100%'
//tbody
var tbody = document.createElement('tbody') table.appendChild(tbody)
//第一行
tbody.insertRow(0) tbody.rows[0].insertCell(0); tbody.rows[0].cells[0].appendChild(document.createTextNode('cell 11')) tbody.rows[0].insertCell(1); tbody.rows[0].cells[1].appendChild(document.createTextNode('cell 22'))
//第二行
tbody.insertRow(1) tbody.rows[1].insertCell(0); tbody.rows[1].cells[0].appendChild(document.createTextNode('cell 33')) tbody.rows[1].insertCell(1); tbody.rows[1].cells[1].appendChild(document.createTextNode('cell 44')) document.body.appendChild(table)
**HTMLCollection、NodeList以及NamedNodeMap这三个集合都是“动态的”,每当文档发生变化时,他们都会更新。他们将始终保持这最新、最准确的消息。且看以下程序:**
var divs = document.getElementsByTagName('div'), i, div; for (i = 0; i < divs.length; i++) { div = document.createElement('div') document.body.appendChild(div) } //以上代码,第一次取得所有div 元素的HTMLCollection,下次取得会更新,造成死循环
//正确使用
var divs = document.getElementsByTagName('div'), i, len, div; for (i = 0, len = divs.length; i < len; i++) { div = document.createElement('div') document.body.appendChild(div) }
<hr /> DOM元素的特性(Attribute) 和 属性(Property) Attribute特性: dom节点自带的属性,例如html中常用的id、class、title、align等:
//赋值:之后立即体现到DOM ,如果是标准特性,也会关联到相对应的属性值 ;
div1.setAttribute('class', 'a'); div1.setAttribute('title1', 'c');
Property属性: 而Property是这个DOM元素作为对象,其附加的内容,例如childNodes、firstChild等:
//取值,用.
var id = div1.id;
var attrs = div1.attributes;//上面代码中的div1.attributes是取的attributes这一属性,取出来保存到attrs变量中,attrs就成了一个NamedNodeList类型的对象,里面存储了若干个Attr类型。
//赋值 :对属性Property可以赋任何类型的值,而对特性Attribute只能赋值字符串!
div1.className = 'a';<hr > NodeList v.s. HTMLCollection Node及对应集合NodeList Element(继承Node)及对应集合HTMLCollection Document(继承Node)
零js基礎 | 程序設計 | 9 - 客户端检测
January 13, 2014 直接把函数作为属性访问会导致JavaScript 错误。使用typeof 操作符会更靠谱一点,但IE
对typeof xhr.open 会返回"unknown",在浏览器环境下测试任何对象的某个特性是否
存在,要使用下面这个函数。function isHostMethod(object, property) { var t = typeof object[property]; return t == 'function' || (!!(t == 'object' && object[property])) || t == 'unknown'; } result = isHostMethod(xhr, "open"); //true result = isHostMethod(xhr, "foo"); //false
能力检测,不是浏览器检测
检测某个或某几个特性并不能够确定浏览器。下面给出的这段代码(或与之差不多的代码)可以在
许多网站中看到,这种“浏览器检测”代码就是错误地依赖能力检测的典型示例。//错误!还不够具体 var isFirefox = !!(navigator.vendor && navigator.vendorSub); //错误!假设过头了 var isIE = !!(document.all && document.uniqueID);
一次性检测
//确定浏览器是否支持Netscape 风格的插件 var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length); //确定浏览器是否具有DOM1 级规定的能力 var hasDOM1 = !!(document.getElementById && document.
在实际开发中,应该将能力检测作为确定下一步解决方案的依据,而不是用它来 判断用户使用的是什么浏览器。
完整用户代理字条串检测脚本代码
var client = function() { //呈现引擎 var engine = { ie: 0, gecko: 0, webkit: 0, khtml: 0, opera: 0, //完整的版本号 ver: null }; //浏览器 var browser = { //主要浏览器 ie: 0, firefox: 0, safari: 0, konq: 0, opera: 0, chrome: 0, //具体的版本号 ver: null }; //平台、设备和操作系统 var system = { win: false, mac: false, x11: false, //移动设备 iphone: false, ipod: false, ipad: false, ios: false, android: false, nokiaN: false, winMobile: false, //游戏系统 wii: false, ps: false }; //检测呈现引擎和浏览器 var ua = navigator.userAgent; if (window.opera) { engine.ver = browser.ver = window.opera.version(); engine.opera = browser.opera = parseFloat(engine.ver); } else if (/AppleWebKit\/(\S+)/.test(ua)) { engine.ver = RegExp["$1"]; engine.webkit = parseFloat(engine.ver); //确定是Chrome 还是Safari if (/Chrome\/(\S+)/.test(ua)) { browser.ver = RegExp["$1"]; browser.chrome = parseFloat(browser.ver); } else if (/Version\/(\S+)/.test(ua)) { browser.ver = RegExp["$1"]; browser.safari = parseFloat(browser.ver); } else { //近似地确定版本号 var safariVersion = 1; if (engine.webkit < 100) { safariVersion = 1; } else if (engine.webkit < 312) { safariVersion = 1.2; } else if (engine.webkit < 412) { safariVersion = 1.3; } else { safariVersion = 2; } browser.safari = browser.ver = safariVersion; } } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) { engine.ver = browser.ver = RegExp["$1"]; engine.khtml = browser.konq = parseFloat(engine.ver); } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)) { engine.ver = RegExp["$1"]; engine.gecko = parseFloat(engine.ver); //确定是不是Firefox if (/Firefox\/(\S+)/.test(ua)) { browser.ver = RegExp["$1"]; browser.firefox = parseFloat(browser.ver); } } else if (/MSIE ([^;]+)/.test(ua)) { engine.ver = browser.ver = RegExp["$1"]; engine.ie = browser.ie = parseFloat(engine.ver); } //检测浏览器 browser.ie = engine.ie; browser.opera = engine.opera; //检测平台 var p = navigator.platform; system.win = p.indexOf("Win") == 0; system.mac = p.indexOf("Mac") == 0; system.x11 = (p == "X11") || (p.indexOf("Linux") == 0); //检测Windows 操作系统 if (system.win) { if (/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)) { if (RegExp["$1"] == "NT") { switch (RegExp["$2"]) { case "5.0": system.win = "2000"; break; case "5.1": system.win = "XP"; break; case "6.0": system.win = "Vista"; break; case "6.1": system.win = "7"; break; default: system.win = "NT"; break; } } else if (RegExp["$1"] == "9x") { system.win = "ME"; } else { system.win = RegExp["$1"]; } } } system.iphone = ua.indexOf("iPhone") > -1; system.ipod = ua.indexOf("iPod") > -1; system.ipad = ua.indexOf("iPad") > -1; system.nokiaN = ua.indexOf("NokiaN") > -1; //windows mobile if (system.win == "CE") { system.winMobile = system.win; } else if (system.win == "Ph") { if (/Windows Phone OS (\d+.\d+)/.test(ua)) {; system.win = "Phone"; system.winMobile = parseFloat(RegExp["$1"]); } } //检测iOS 版本 if (system.mac && ua.indexOf("Mobile") > -1) { if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)) { system.ios = parseFloat(RegExp.$1.replace("_", ".")); } else { system.ios = 2; //不能真正检测出来,所以只能猜测 } } //检测Android 版本 if (/Android (\d+\.\d+)/.test(ua)) { system.android = parseFloat(RegExp.$1); } //游戏系统 system.wii = ua.indexOf("Wii") > -1; system.ps = /playstation/i.test(ua); //返回这些对象 return { engine: engine, browser: browser, system: system }; }() console.log(client.engine) console.log(client.browser) console.log(client.system)
零js基礎 | 程序設計 | 8 - BOM
January 12, 2014 BOM核心
控制窗口、框架和弹出窗口
利用location 对象中的页面信息
使用navigator对象了解浏览器;全局变量不能通过delete 操作符删除,而直接在window 对象上的定义的属性可以
var age = 29; window.color = "red"; //在IE < 9 时抛出错误,在其他所有浏览器中都返回false delete window.age; //在IE < 9 时抛出错误,在其他所有浏览器中都返回true delete window.color; //returns true alert(window.age); //29 alert(window.color); //undefined
//这里会抛出错误,因为oldValue 未定义 var newValue = oldValue; //这里不会抛出错误,因为这是一次属性查询 //newValue 的值是undefined var newValue = window.oldValue;
window.frames[0]或者window.frames["topFrame"]来引用上方的框架。不过,恐怕你最好使用
top 而非window 来引用这些框架(例如,通过top.frames[0])
与top 相对的另一个window 对象是parent。顾名思义,parent(父)对象始终指向当前框架的
直接上层框架。在某些情况下,parent 有可能等于top;但在没有框架的情况下,parent 一定等于
top(此时它们都等于window)。ff支持 screenX 和screenY
var leftPos = (typeof window.screenLeft == 'number') ? window.screenLeft : window.screenX; var topPos = (typeof window.screenTop == 'number') ? window.screenTop : window.screenY;
//将窗口移动到屏幕左上角 window.moveTo(0,0); //将窗向下移动100 像素 window.moveBy(0,100); //将窗口移动到(200,300) window.moveTo(200,300); //将窗口向左移动50 像素 window.moveBy(-50,0);
所有浏览器都有:innerWidth/innerHeight/outerWidth/outerHeight
IE9+/Safari/Firefox: outerWidth/outerHeight返回浏览器本身尺寸
Opera:outerWidth/outerHeight:表示单个标签页对应的浏览器窗口大小;而innerWidth/innerHeight表示该容器中页面视图的大小 - 边框宽度;
chrome:outerWidth/outerHeight与 innerWidth/innerHeight返回相同的值,即viewport大小而非浏览器窗口大小var pageWidth = window.innerWidth, pageHeight = window.innerHeight; if (typeof pageWidth != "number") { if (document.compatMode == "CSS1Compat") { pageWidth = document.documentElement.clientWidth; pageHeight = document.documentElement.clientHeight; } else { pageWidth = document.body.clientWidth; pageHeight = document.body.clientHeight; } }
compatMode 检查是否处于标准模式;
使用resizeTo()和resizeBy()方法可以调整浏览器窗口的大小。这两个方法都接收两个
参数,其中resizeTo()接收浏览器窗口的新宽度和新高度,而resizeBy()接收新窗口与原窗口的宽
度和高度之差。来看下面的例子。
//调整到100×100
window.resizeTo(100, 100);
//调整到200×150
window.resizeBy(100, 50);
//调整到 300×300
window.resizeTo(300, 300);window.open(要加载的URL,窗口目标,一个特性字条串,是否当前页面打开)
,所以< a href="http://www.wrox.com" target="topFrame"></a> ==== window.open("http://www.wrox.com/", "topFrame");
window.close()只适应于window.open()打开的窗口;弹出窗口打调用top.close();
创建的window 对象有一个opener 属性,其中保存着打开它的原始窗口对象
var wroxWin = window.open('http://www.w.com','w','height=400,width=400,top:10,left=10,resizable = yes') console.log(wroxWin.opener = window) //true
chrome中设置opener属性为null,表示在单独进程中独立运行;
var wroxWin = window.open("http://www.wrox.com/","wroxWindow", "height=400,width=400,top=10,left=10,resizable=yes"); wroxWin.opener = null;
检测是否被屏蔽弹出
var blocked = false; try { var wroxWin = window.open("http://www.wrox.com", "_blank"); if (wroxWin == null) { blocked = true; } } catch (ex) { blocked = true; } if (blocked) { alert("The popup was blocked!"); } }
返回布尔值;
if (confirm("Are you sure?")) { alert("I'm so glad you're sure! "); } else { alert("I'm sorry to hear you're not sure. "); }
返回false或字符串
var result = prompt("What is your name? ", ""); if (result !== null) { alert("Welcome, " + result); }
localtion对象
既是window对象,也是document对象;
window.location == document.location 引用同一个对象 ;
查询字符串参数
function getQueryStringArgs() { //取得查询字符串并去掉开头的问号 var qs = (location.search.length > 0 ? location.search.substring(1) : ""), //保存数据的对象 args = {}, //取得每一项 items = qs.length ? qs.split("&") : [], item = null, name = null, value = null, //在for 循环中使用 i = 0, len = items.length; //逐个将每一项添加到args 对象中 for (i = 0; i < len; i++) { item = items[i].split("="); name = decodeURIComponent(item[0]); value = decodeURIComponent(item[1]); if (name.length) { args[name] = value; } } return args; }
//假设查询字符串是?q=javascript&num=10
var args = getQueryStringArgs();
alert(args["q"]); //"javascript"
alert(args["num"]); //"10"跳转新窗口:
location.assign('http://www.baidu.com') window.location = 'http://www.baidu.com' location.href = 'http://www.baidu.com'
//假设初始URL 为http://www.wrox.com/WileyCDA/ //将URL 修改为"http://www.wrox.com/WileyCDA/#section1" location.hash = "#section1"; //将URL 修改为"http://www.wrox.com/WileyCDA/?q=javascript" location.search = "?q=javascript"; //将URL 修改为"http://www.yahoo.com/WileyCDA/" location.hostname = "www.yahoo.com"; //将URL 修改为"http://www.yahoo.com/mydir/" location.pathname = "mydir"; //将URL 修改为"http://www.yahoo.com:8080/WileyCDA/" location.port = 8080;
禁止返回后退页面
window.location.replace('http://baidu.com')
重新加载 reload()
location.reload(); //重新加载(有可能从缓存中加载) location.reload(true); //重新加载(从服务器重新加载)
navigator
页面在IE7提示升级if(window.ActiveXObject) { var browser=navigator.appName var b_version=navigator.appVersion var version=b_version.split(";"); var trim_Version=version[1].replace(/[ ]/g,""); if(browser=="Microsoft Internet Explorer" && trim_Version=="MSIE6.0" || trim_Version=="MSIE7.0" ) { $(".ie7andie6").show(); $(".contentnone").hide(); } }
跳到手机网站
var a=navigator.userAgent; if(a.indexOf("Android")!=-1 || a.indexOf("iPhone")!=-1 || a.indexOf("iPad")!=-1 ){ //跳转到手机网站 }
检测浏览器中是否安装了特定的插件是一种最常见的检测例程。对于非IE 浏览器,可以使用
plugins 数组来达到这个目的。该数组中的每一项都包含下列属性。
name:插件的名字。
description:插件的描述。
filename:插件的文件名。
length:插件所处理的MIME 类型数量。function hasPlugin(name) { name = name.toLowerCase(); for (var i = 0; i < navigator.plugins.length; i++) { if (navigator.plugins[i].name.toLowerCase().indexOf(name) > -1) { return true; } } return false; } //检测Flash alert(hasPlugin("Flash")); //检测QuickTime alert(hasPlugin("QuickTime")); }
这个hasPlugin()函数接受一个参数:要检测的插件名
IE插件专家
//检测IE 中的插件 function hasIEPlugin(name) { try { new ActiveXObject(name); return true; } catch (ex) { return false; } } //检测Flash alert(hasIEPlugin("ShockwaveFlash.ShockwaveFlash")); //检测QuickTime alert(hasIEPlugin("QuickTime.QuickTime"));
推荐是下面的方法:
//检测所有浏览器中的Flash function hasFlash() { var result = hasPlugin("Flash"); if (!result) { result = hasIEPlugin("ShockwaveFlash.ShockwaveFlash"); } return result; } //检测所有浏览器中的QuickTime function hasQuickTime() { var result = hasPlugin("QuickTime"); if (!result) { result = hasIEPlugin("QuickTime.QuickTime"); } return result; } //检测Flash alert(hasFlash()); //检测QuickTime alert(hasQuickTime());
screen 对象
JavaScript 中有几个对象在编程中用处不大,而screen 对象就是其中之一。screen 对象基本上只
用来表明客户端的能力,其中包括浏览器窗口外部的显示器的信息,如像素宽度和高度等。每个浏览器
中的screen 对象都包含着各不相同的属性,下表列出了所有属性及支持相应属性的浏览器。history 对象
//后退一页 history.go(-1); //前进一页 history.go(1); //前进两页 history.go(2); //跳转到最近的wrox.com 页面 history.go("wrox.com"); //跳转到最近的nczonline.net 页面 history.back(); //前进一页 history.forward();
history 对象还有一个length 属性
if (history.length == 0){ //这应该是用户打开窗口后的第一个页面 }
虽然history 并不常用,但在创建自定义的“后退”和“前进”按钮,以及检测当前页面是不是
用户历史记录中的第一个页面时,还是必须使用它。零js基礎 | 程序設計 | 7 - 函数表达式
January 11, 2014 name属性获取函数名:IE不支持
function fn1(name, age) {} console.log(fn1.name) //fn1
创建了一个名为f()的命名函数表达式,然后将它赋值给变量factorial
'use strict' //兼容严格模式,arguments.callee()不支持严格模式; var fa = (function f(num) { if (num < 1) { return 1 } else { return num * f(num - 1) } }) var fac = fa fa = null fac(8)
闭包是指有权访问另一个函数作用域中的变量的函数
函数调用时,执行环境:1、,作用域链1--全局变量;2、作用域链2--compare()的活动对象argument,v1,v2
function comapre(v1,v2){ if(v1<v2){return -1} else if(v1>v2){return 1} else{return 0;} } var result = comapre(5,10) //调用时,创建一个包含 argument,v1,v2的活动对象;
compare()调用---》创建执行环境--》复制函数的[[Scope]]属性--》用来构建起执行环境的作用域链 --》此后,活动对象(arguments与参数)--》推入执行环境作用链前端 在compare()的执行环境--》作用域链包含两个变量对象:本地活动对象(arguments与参数和全局变量对象compare与result 作用域链本质是一个指向变量对象的指针列表,只引用不实际包含; 函数访问一个变量时,会从作用域链接链中搜索具有相应名字的变量,执行完后销毁(即局部变量无法在全局中访问的原因)
闭包
function createComparisonFunction(propertyName) { return function(object1, object2) { var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } }; }
内部的函数将包含外部函数的活动对象添加到它的作用域链中 【function(o....),的propertyName(活动对象)添加到作用域链中】
var compare = createComparisonFunction("name"); var result = compare({ name: "Nicholas" }, { name: "Greg" });
执行以上代码,return函数返回后,作用域链 == 包含 createComparisonFunction()活动对象和全局变量对象;
createComparisonFunction()函数执行完后,活动对象不会被销毁,执行环境作用域链被销毁,因为return函数仍引用这个活动对象,直到return函数被销毁后,createComparisonFunction()活动对象才被销毁;//创建函数 var compareNames = createComparisonFunction("name"); //调用函数 var result = compareNames({ name: "Nicholas" }, { name: "Greg" }); //解除对匿名函数的引用(以便释放内存) compareNames = null; 首先,创建的比较函数被保
createComparisonFunction的执行环境【有两个】,1、createComparisonFunction的活动对象--》arguments,propertyName; 即'name'
2、(作用域链也是链到这两个)--》全局变量对象(createComparison,Function)result
匿名函数的执行环境[有三个]--》0:闭包的活动对象:arguments[{'name':'nic'},{'name':'grey'}]; object1{'name':'nic'};object2:{'name':'grey'}
1、createComparisonFunction执行环境的作用域0:createComparisonFunction的活动对象:arguments,propertyName; 即'name';
2、createComparisonFunction执行环境作用域1:全局变量对象(createComparison,Function)result所以,即匿名函数的执行环境(作用域链)包含上级函数的作用域链;
JavaScript中的作用域链的机制引出了一个副作用,即闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象
function createFunction() { var result = []; for (var i = 0; i < 10; i++) { result[i] = function() { return i; } } return result; } var arr = createFunction() for (var i = 0; i < arr.length; i++) { console.log(arr[i]()) //10,i变量只取最后一个值 }
改写一:
function createFunction() { var result = []; for (var i = 0; i < 10; i++) { result[i] = (function(num) { return num })(i) } return result; } var arr = createFunction() for (var i = 0; i < arr.length; i++) { console.log(arr[i]) }
改写二:
function createFunction() { var result = []; for (var i = 0; i < 10; i++) { result[i] = function(num) { return function(){ return num }; }(i) } return result; } var arr = createFunction() for (var i = 0; i < arr.length; i++) { console.log(arr[i]()) }
在重写了前面的createFunctions()函数后,每个函数就会返回各自不同的索引值了。在这个版
本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋
给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我
们传入了变量i。由于函数参数是按值传递的,所以就会将变量i 的当前值复制给参数num。而在这个
匿名函数内部,又创建并返回了一个访问num 的闭包。这样一来,result 数组中的每个函数都有自己
num 变量的一个副本,因此就可以返回各自不同的数值了。this对象
var name = 'the window' var Object = { name:'my object', getNameFun:function(){ return function(){ return this.name //这里this-->windows } } } console.log(Object.getNameFun()()) //the window,为什么不是my object?
var name = 'the window' var Object = { name:'my object', getNameFun:function(){ var _this = this; return function(){ return _this.name } } } console.log(Object.getNameFun()()) //my object
返回第一个例子;
var name = 'the window' var Object = { name:'my object', getNameFun:function(){ //活动对象都会自动获取两个特殊的变量:this和arguments return function(){ //活动对象都会自动获取两个特殊的变量:this和arguments Object.getNameFun()()调用时,立即返回 return this.name //这里this-->windows } } } console.log(Object.getNameFun()()) //the window,为什么不是my object?
每个函数在调用时,其活动对象都会自动获取两个特殊的变量:this和arguments。内部函数在搜索这两个变量时,只会搜到其活动对象为止,因此永远不肯能访问到外部函数中的这两个变量。不过,把外部作用域中的this对象保存在一个闭包能够访问的变量里,就可以放闭包访问该对象了。[this相当于两层function(),每一层都有this与arguments,最里层this指向window,上一层指向本身的object]
内存泄漏
function assHandler(){ var elem = document.getElementById('someElement') elem.onclick = function(){ //匿名函数保存着对assHandler()的活动对象引用, console.log(elem.id)//因此就会导致无法减少elem的引用数,只要存在,elem引用数至少也是1,所占内存永远不会被回收,把这里提出去; } }
改进:
function assHandler(){ var elem = document.getElementById('someElement') var id = elem.id //保存为副本 elem.onclick = function(){ console.log(id) } elem = null;//即使闭包不直接引用element,包含函数的活动对象中也 仍然会保存一个引用,必要把element 变量设置为null。 }
块级作用域
function outputNum(count){ for(var i=0;i<count;i++){ console.log(i) } var i; console.log(i) //也是10 } outputNum(10)
(function() { //someCode. }();
var someFn = function(){ //someCode. } someFn()
是否可以直接
function() { }() //出错
是因为JavaScript 将function 关键字当作一个函数声明的开始,而函
数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,
只要像下面这样给它加上一对圆括号即可。无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:
function outNum(count){ (function(){ for(var i=i;i<count;i++){ console.log(i) } })() console.log(i) //抛出错误 } outNum(5)
所以,可以使用下面方式;
(function(){ var now = new Date() if(now.getMonth() == 0 && now.getDate() == 1){ console.log('happy new year') } })()
把上面这段代码放在全局作用域中,可以用来确定哪一天是1 月1 日;如果到了这一天,就会向用
户显示一条祝贺新年的消息。其中的变量now 现在是匿名函数中的局部变量,而我们不必在全局作用域
中创建它私有变量
function MyObject(){ //私有变量我私有函数 var privateValue = 10; function privateFunction(){ return false; } //特权方法 this.publicMethod = function(){ privateValue++; return privateFunction(); } }
这个模式在构造函数内部定义了所有私有变量和函数。然后,又继续创建了能够访问这些私有成员
的特权方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的
所有变量和函数function Person(name){ this.getName = function(){ //使用this,避免外部对getName的调用,只能通过Person.getName() return name; } this.setName = function(value){ name = value; } } var per1 = new Person('nic') console.log(per1.getName()) per1.setName('grey') console.log(per1.getName())
以上代码的构造函数中定义了两个特权方法:getName()和setName(),这两个方法都可以在构
造函数外部使用,因为每次调用构造函数都会重新创建这两个方法,构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方
法就可以避免这个问题。//构造函数模式实现私有变量(通过闭包实现) function Person(nameStr){ var name = nameStr;//私有变量 this.age = 15;//公有变量 this.getName = function(){ return name; }; this.setName = function(value){ name = value; }; this.getInfo = function(){ console.debug(name + " " +this.age); }; } var p1 = new Person('Lebron'); var p2 = new Person('Paul'); p1.getInfo(); p2.getInfo(); p1.setName('kobe Bryant'); p1.getInfo(); p2.getInfo();
但是这种构造器模式每次调用时都会创建函数对象,耗费资源,函数不可以共享。
静态私有变量
私有作用域中定义私有变量或函数 (function(){ //私有变量、函数 var privateValue = 10; function privateFun(){ return false; } //构造 MyObject = function(){ //使用函数表达工来构造函数 //因为函数声明只能创建局部函数;并不是我们想要的,出于一样原因,也没有在MyObject前加var } //公有、特权方法 MyObject.prototype.publicMethod = function(){ privateValue ++ ; return privateFun() } })()
,就在于私有变量和函数是由实例共享的。由于
特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法,作为一个闭包,总是
保存着对包含作用域的引用(function(){ var name = '' //静态私有,被所有实例所共享 Person = function(value){ //默认为全局变量了 ,可以被外部访问 name = value; } Person.prototype.getName = function(){ return name; } Person.prototype.setName = function(value){ name =value; } })() var per1 = new Person('nic') console.log(per1.getName()) //nic per1.setName('grey') console.log(per1.getName()) //grey var per2 = new Person('mich') console.log(per1.getName()) //mich console.log(per2.getName()) //mich //被影响了
道格拉斯所说的模块模式(module
pattern)则是为单例创建私有变量和特权方法,所谓单例(singleton),指的就是只有一个实例的对象。
按照惯例,JavaScript 是以对象字面量的方式来创建单例对象的。先来看看不需要实现对象内属性私有化的对象该如何定义??
var singleton = { name: 'value', method: function() { //..... } }
需要对象内属性私有化的定义;
(function() { var singleton = function() { //私有变量和私有函数 var privateValue = 10; function privateFun() { return false; } //公有、特权方法和属性; return { //返回对象的匿名函数 publicProperty: true, publicMethod: function() { privateValue++; return privateFun(); } } } })()
首先声明了一个私有的components
数组,并向数组中添加了一个BaseComponent 的新实例(在这里不需要关心BaseComponent 的代码,我
们只是用它来展示初始化操作)。而返回对象的getComponentCount()和registerComponent()方法,都
是有权访问数组components 的特权方法。前者只是返回已注册的组件数目,后者用于注册新组件。function BaseComponent(){ } console.debug("====================="); var application = function(){//定义的匿名函数立即执行 返回一个匿名的对象字面量 //私有变量和函数 var components = new Array(); //初始化操作 components.push(new BaseComponent()); //公共 return {//返回对象字面量方式定义的对象 以这种方式开放访问接口 getComponentCount : function(){ return components.length; }, registerComponent : function(component){ if( typeof component == 'object'){ components.push(component); } } }; }(); console.debug(application.getComponentCount());
改进增强型模块模式;进一步增强的模块模式是指在返回对象志强对其增强功能,这种增强的模块模式适合那些单例必须是某种类型的实例。就像下面这样
function BaseComponent(){ } function OtherComponent(){ } var application = function(){ //private variables and functions var components = new Array(); //initialization components.push(new BaseComponent()); //create a local copy of application var app = new BaseComponent(); //public interface app.getComponentCount = function(){ return components.length; }; app.registerComponent = function(component){ if (typeof component == "object"){ components.push(component); } }; //return it return app; }(); alert(application instanceof BaseComponent); application.registerComponent(new OtherComponent()); alert(application.getComponentCount()); //2
零js基礎 | 程序設計 | 1 - 6 - 基础
January 8, 2014 5.5.4 函數內部屬性 特殊對象:arguments和 this
arguments下的callee屬性,arguments.calle();指針,指向arguments對象的函數function factorial(num) { if (num < 1) { return 1 } else { return num * factorial(num - 1) } } function factorial2(num) { if (num < 1) { return 1 } else { return num * arguments.callee(num - 1) } }
this對象
window.color = 'red' var o = { color: 'blue' } function sayColor() { console.log(this.color); } sayColor() //red o.sayColor = sayColor; o.sayColor() //blue
函數對象屬性outer(),保存著調用當前函數的函數引用;
function outer(){ var i; inner() } function inner(){ var i=0,b=1,c=3; console.log(inner.caller) } outer()
5.5.5函數屬性和方法
函數的length表示參數的個數
function sayName(a,b){ // } console.log(sayName.length) //2
每個函數都有非繼承方法:apply(),call();,設置函數體內this對象的值;
apply(運行函數的作用域同this,參數組【可以是Array實例,也可以是arguments對象】)
call(運行函數的作用域同this,傳遞的參數一個個列出來)function sum(n1, n2) { return n1 + n2; } function callsum1(n1, n2) { return sum.apply(this, arguments) } function callsum2(n1, n2) { return sum.apply(this, [n1, n2]) } console.log(callsum1(10, 10)) ///////////////////////////////////////////////////// function sum(n1, n2) { return n1 + n2; } function callsum(n1, n2) { return sum.call(this, n1, n2) } console.log(callsum(10, 10))
真正的用處:
window.color = 'red' var o = { color: 'blue' } function sayColor() { console.log(this.color); } sayColor(this) // red sayColor.call(window) //red sayColor.call(o) //blue
bind()方法會創建函數實例,this值會被綁定到傳給bind()函數的值;
window.color = 'red' var o = { color: 'blue' } function sayColor() { console.log(this.color) } var objsaycolor = sayColor.bind(o) //即創建了objsaycolor()函數,其中他的this = o 對象 objsaycolor(); //blue
5.6基本包裝類型 Boolean/Number/String
var s1 = 'some txt' var s2 = s1.substring(2);
為什麼基本類型調用對象方法
//創建String實例 var s1 = new String('some txt') //為實例調用方法 var s2 = s1.substring(2) //銷毀實例 s1 = null;
方法
var num = 10; console.log(num.toString(2)) //1010; console.log(num.toFixed(2)) //10.00 console.log(num.toExponential(1)) //1.0e+1 console.log(n.toPrecision(1)); var n = 99; console.log(n.toPrecision(2)); // 1e+2 console.log(n.toPrecision(3)); //99.0 console.log(n.toPrecision(4)); //99.00 表示以多少位显示,范围为 1- 21
不修改字符串本身,返回基本字符串值,
concat()/slice()/substr()/substring()
slice()负与长度相加
substr()、substring()负置为0var vS = 'fuckword' console.log(vS.length); //8 console.log(vS.charAt(1)) //u console.log(vS.charCodeAt(1)) //117 console.log(vS[1]) //u var rvS = vS.concat('aaaa','bbb') console.log(rvS) //fuckwordaaaabbb
位置方法:indexOf()/lastIndexOf();
var strindex = 'jaspfjqijdfjsfnfcjspfqmjfpwpfjmadsljkfmpqiergbrufjnapskdjfmpa'; var position = [] var pos = strindex.indexOf('f'); while (pos > -1){ position.push(pos) pos = strindex.indexOf('f',pos+1) }; console.log(position);
trim()去空格;
strindex.toLocaleLowerCase()
strindex.toLocaleUpperCase()match()/search()
var txt = 'cat bat eat fat' var pattern = /.at/ //与 pattern.exec(txt); var matches = txt.match(pattern) console.log(matches.index) //0 console.log(matches[0]) //at 返回匹配 console.log(pattern.lastIndex) //0
var txt = 'cat bat eat fat' var pattern = /at/ var matchs = txt.search(pattern) console.log(matchs) //1 at在字符串第一次出现位置1
$n表达匹配第n个子字符串 n : 1-9 nn: 10 -99
var txt = 'cat bat eat fat' var pattern = /at/ var retxt = txt.replace(/(.at)/g,'word$1') console.log(retxt) //wordcat wordbat wordeat wordfat
只有一个匹配:replace(//,function(模式匹配,模式匹配项在字符串中的位置,原始字符串 ){ })
多个匹配:replace(//,function(模式匹配,第一捕获组匹配项,第二捕获组匹配项....,模式匹配项在字符串中的位置,原始字符串 ){ })function reppacehtml(str) { return str.replace(/[<>]/g, function(match, pos, priginalText) { switch (match) { case "<": return ''; case ">": return '' } }) } console.log(reppacehtml('<p>heooooooo</p>'))
split(字符串或正则,数组大小)
var colorText = "red,blue,green,yellow"; var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"] var colors2 = colorText.split(",", 2); //["red", "blue"] var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""]
localeCompare()方法;
function determineOrder(value) { var result = stringValue.localeCompare(value); if (result < 0) { alert("The string 'yellow' comes before the string '" + value + "'."); } else if (result > 0) { alert("The string 'yellow' comes after the string '" + value + "'."); } else { alert("The string 'yellow' is equal to the string '" + value + "'."); } } determineOrder("brick"); determineOrder("yellow"); determineOrder("zoo");
fromCharCode()方法;String构造函数本身的静态方法,接收一个或多个字符编码,转成字符串;
String.fromCharCode(104,101)5.7.1 Global对象
encodeURI()整个URI,只有空格会换成20%,其它都原封不动;
encodeURIComponent()主要用于对URI中的某一段;发现任何非标准都转换;encodeURI('http://baidu.com/google com/') //"http://baidu.com/google%20com/" encodeURIComponent('http://baidu.com/google com/') //"http%3A%2F%2Fbaidu.com%2Fgoogle%20com%2F"
decodeURI()
decodeURIComponent()eval()方法,解析器;传入的参数当语句来解析;
eval('alert("hi")') = alert("hi")
eval("function sayHi() { alert('hi'); }"); //注意这里有双引号,是一个字符串
sayHi(); //hi取得global对象方法:
var global = function(){ return this; }() console.log(global)
Math.ceil()/Math.floor()/Math.round()/Math.random()
//介乎于lv与uv之间的数(包括lv/uv) function selectFrom(minV,maxV){ var cV = maxV - minV + 1; return Math.floor(Math.random() * cV + minV); } var num = selectFrom(2,10); console.log(num)
EAMAScript 5 定义了描述这些属性特征的各类特性,包括数据属性和访问器属性。
- [[Configurable]]:表示是否能通过delete删除
- [[Enumerable]]:表示能否用for-in循环返回。
- [[writable]]:表示能否修改属性的值。
- [[Value]]:包含这个属性的数据值。默认值为undefined。
var person = {}; Object.defineProperty(person, "name", { writable: false, value: "Nicholas" }); alert(person.name); //"Nicholas" person.name = "Greg"; alert(person.name); //"Nicholas"
已经设置为不能configurable为false,即不能删除,再次设置将抛出错误
var person = {}; Object.defineProperty(person, "name", { configurable: false, value: "Nicholas" }); //抛出错误 Object.defineProperty(person, "name", { configurable: true, value: "Nicholas" });
访问器属性;
访问person.name,实际调用getter()函数,写放时调用setter()函数;
Object.defineProperty( )方法,该方法包含三个参数:属性所在的对象,属性的名字,描述符对象;var person = { _name: 'elmok', //默认 _age: 31, //默认 _tel: 123456 //默认 } Object.defineProperty(person, 'name', { get: function() { return this._name; } }) Object.defineProperty(person, 'age', { set: function(newage) { this._age = newage; } }) console.log(person.name) //elmok person.name = 'EE' //尝试修改 console.log(person.name) //elmok 不可修改 console.log(person.age) //不可读属性undefined person.age = '26' console.log(person._age) //26,可修改,直接读取_age才可看到已改 console.log(person.tel) //修改 person.tel = 888888; console.log(person.tel) //888888 等价另一种写法 Object.defineProperty(person,{ name:{ get:function(){ return this._name; } } })
//
工厂模式function cperson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.syaName = function(){ console.log(this.name); } return o; }
构造函数模式
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ console.log(this.name) } //this.sayName = new Function('console.log(this.name)') } console.log(per1.constructor == Person) //true console.log(per1 instanceof Object) //true console.log(per1 instanceof Person) //true
调用
//构造调用 var per1 = new Person('nicholas',29,'software Engineer') //普通函数 Person('Geay',27,'Doctor') window.sayName(); //另一个对象作用域中调用 var o = new Object() Person.call(o,'Geay',25,'Doctor') o.sayName()
问题:
console.log( per1.sayName == per2.sayName ) //false 创建多次了
如果定义在外面
function Person(name,age){ this.name = name; this.age = age; this.sayName = sayName; } function sayName(){ console.log(this.name) } var per1 = new Person('nicholas',29,'software Engineer') var per2 = new Person('grey',28,'Doctor') console.log( per1.sayName == per2.sayName ) //true;
所以是原型 prototype(原型)属性,指针,指向一个对象
对象的用途:包含可以由特定类型的所有实例共享的属性和方法;
通过创建的那种对象的 ‘ 原型对象 ’创建新函数--》创建prototype属性 --》 prototype属性指向函数的原型对象
原型对象自动获得constuctor,指向--》prototype所有的函数
Person.prototype 《-- Person.prototype.constructor
Per1.[[prototype]] --> Person.prototype 实例属性仅仅指向Person.prototype
Per2.[[prototype]] --> Person.prototype确定与实例的[[prototype]]是否有这种关系;isPrototypeOf()
alert(Person.prototype.isPrototypeOf(per1)); //true
ECMAScript 5 Object.getPrototypeOf[per1] == Person.prototype //左边返回对象的原型
alert(Object.getPrototypeOf(person1).name); //"Nicholas"解析器会问:per1有sayName吗?答:无,则再搜索prototype,答:有;
如果外属性有值,则会覆盖prototype的值;
function Person() {} Person.prototype.name = 'nic' Person.prototype.age = 29; Person.prototype.sayName = function() { console.log(this.name); } var per1 = new Person() var per2 = new Person(); per1.name = 'grey' // delete per1.name 可以删除实例属性,用回原型 console.log(per1.name) alert(person1.hasOwnProperty("name")); //false
hasOwnProperty()方法,可检测一个属性是存在实例中,还是原型中,来自原型返回false,来自自身属性返回true; 如以上;所以可以确定什么时候访问的是实例属性,什么时候访问的是原型属性
所以 in的方法返回true,而 hasOwnProperty返回 false,则表明在原型
function hasPrototypeProperty(obj,name){ return !obj.hasOwnProperty(name) && (name in object) }
Object.keys()返回所有可枚举实例属性;--数组;
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function() { alert(this.name); }; var keys = Object.keys(Person.prototype); alert(keys); //"name,age,job,sayName" var p1 = new Person(); p1.name = "Rob"; p1.age = 31; var p1keys = Object.keys(p1); alert(p1keys); //"name,age"
function Person(){} Person.prototype = { //自动创建的 constructor被重写 name:'nic', age:29, job:'softw', sayName:function(){ console.log(this.name) } } var per1 = new Person() console.log(per1.constructor == Person) //false constructor属性prototype下的属性,所以访问是实例.的形式,就像访问 .name一样
func..... Person.prototype = { constructor:Person, //重设 } 重设之后会导致constructor的[[Enumerable]]设置为true; 所以 Object.defineProperty(Person.prototype,'constructor',{ enumerable:false, value:Person })
可以先创建实例,再创建原型,但如果重点整个原型,以{}的形式写,则情况不一样,会切断构造函数与最初原型的联系,记住:实例的指针仅指向原型,而不是指向构造函数;
情况一:
function Person(){} //1、定义构造 var f = new Person() //2、定义实例 Person.prototype.sayHi = function(){ //3、定义原型 console.log('hi') } f.sayHi() //原型在实例之后定义也可访问到;即任何位置;
情况二:
function Person(){} //1、定义构造 var f = new Person() //2、定义实例 Person.prototype = { //3、重写原型 constructor:Person, name:'nic', age:29, job:'soft', sayName:function(){ console.log(this.name) } } f.sayName() //error,f因为引用的是最初的原型 //而Person()引用的是重写的原型
常用的方法都是定义在其构造函数的原型上
console.log(typeof Array.prototype.sort) //function
原型的问题:
function Person(){} Person.prototype = { constructor:Person, friends:['shelby','court'], sayName:function(){ console.log(this.name) } } var per1 = new Person() var per2 = new Person() per1.friends.push('van') console.log(per2.friends) //["shelby", "court", "van"] 只修改1,2也被影响了;
使用组合,可变写在构造里,不可改即共享部分写在原型里
function Person(name,age){ this.name = name; this.age = age; this.list = ['111','2222'] } Person.prototype = { constructor:Person, sayName : function (){ console.log(this.name) } } var per1= new Person('elmo1',29) var per2= new Person('elmo2',30) per1.name = 'elmo8' per1.list.push('333') console.log(per1.name) //elmo8 console.log(per2.name) //elmo2 console.log(per1.list) //["111", "2222", "333"] console.log(per2.list) //["111", "2222"]
动态原型 (只有sayName()不存在时,才加到原型)
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; if(typeof this.syaName != 'function'){ Person.prototype.sayName = function(){ console.log(this.name) } } } var f = new Person('nic',29,'sf') f.sayName()
寄生式,即未原型时
function Person(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.syaName = function (){ console.log(this.name) } return o; } var f = new Person('nic',29,'sf') f.sayName(); //nic
寄生式
返回的对象或构造函数之间没有任何关系,因此不能依赖 instanceof来确定function SpecitalArray(){ var values = new Array(); values.push.apply(values,arguments); values.toPipedString = function(){ return this.join('|') } return values; } var colors = new SpecitalArray('red','blue','green') console.log(colors.toPipedString()); console.log(colors instanceof SpecitalArray)
稳妥构造函数模式
无公共属性,不引用this new
function Person(name,age,job){ va o = new Object() o.sayName = function(){ console.log(name) } return o } var f = Person('nic',29,'sf') f.sayName()
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function (){ return this.property; } function SubType(){ this.subproperty = false; } SubType.prototype = new SuperType() SubType.prototype.getSubvalue = function(){ return this.subproperty; } var instance = new SubType(); //重写,所以instance.constructor-->SuperType instance.getSuperValue(); //搜索实例--》搜索SubType.prototype--》搜索SuperType.prototype-->最后一步才找到方法; console.log(instance instanceof Object) console.log(instance instanceof SuperType) console.log(instance instanceof SubType)
继承
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //继承了SuperType SubType.prototype = new SuperType(); //添加新方法 SubType.prototype.getSubValue = function (){ return this.subproperty; }; //重写超类型中的方法 SubType.prototype.getSuperValue = function (){ return false; }; var instance = new SubType(); alert(instance.getSuperValue()); //false
原型链的问题;
function SuperType() { this.color = ['red', 'blue', 'green'] } function SubType() { } SubType.prototype = new SuperType() var in1 = new SubType() in1.color.push('black') console.log(in1.color) //["red", "blue", "green", "black"] var in2 = new SubType() console.log(in2.color) //["red", "blue", "green", "black"] 被影响到
借用构造函数
function SuperType(name){ this.name = name } function SubType(){ SuperType.call(this,'nic') //可以传递参数 this.age = 29; } var in1 = new SubType() console.log(in1.name) //nic console.log(in1.age);//29
组合继承--伪经典继承;
function SuperType(name) { this.name = name; this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function() { console.log(this.name) } function SubType(name, age) { SuperType.call(this, name) this.age = age; } SubType.prototype = new SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age) } var in1 = new SubType('nic', 29) in1.colors.push('black') console.log(in1.colors) //["red", "blue", "green", "black"] in1.sayName() //nic in1.sayAge() //29 var in2 = new SubType('grey', 27) console.log(in2.colors); //["red", "blue", "green"] in2.sayName() //grey in2.sayAge() //27
原型式继承
function object(o) { function F() {} F.prototype = o; return new F(); //内部复制一次再返回,相当于浅复制 ; } var person = { name: 'nic', friends: ['shll', 'count', 'van'] } var per1 = object(person) per1.name = 'grey' per1.friends.push('rob') var per2 = object(person) per2.name = 'linda' per2.friends.push('barbie') console.log(person.friends) //["shll", "count", "van", "rob", "barbie"]
Object.create(新对象原型的对象,新对像定义额外属性的对象)
var person = { name: 'nic', friends: ['shll', 'count', 'van'] } var per1 = Object.create(person,{ name:{ value:'grey' } }) console.log(per1.name)
寄生式继承
function createAnother(original){ var clone = Object(original); //创建一个新对象 clone.sayHi = function(){ //某种方式增强这个对象 console.log('hi') } return clone; //最后再返回增强后的对象 }
最有效有方式:寄生组合式继承
组合继承的问题
function SuperType(name){ this.name = name; this.colors = ['red','blue','green'] } SuperType.prototype.sayName = function(){ console.log(this.name) } function SubType(name,age){ SuperType.call(this,name); //第二次调用 SuperType,这一次又在新对象上创建实例属性 name,colors,于是这两个属性就屏蔽了第一次调用时原型中的同名属性; this.age = age; } SubType.prototype = new SuperType(); //第一次调用 SuperType Sub.prototype会得到两个属性 name colors; SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ console.log(this.age) }
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
inheritPrototype(子类型构造函数,超类型构造函数),用于原型复制;
function inheritPrototype(subType,superType){ var prototype = object(superType.prototype) //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 }
1、创建superType.prototype函数副本 2、为创建的副本添加constructor属性 3、将副本赋值给subType的原型
function inheritPrototype(subType,superType){ var prototype = Object(subType.prototype) //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 } function SuperType(name){ this.name = name; this.colors = ['red','blue','green'] } SuperType.prototype.sayName = function(){ console.log(this.sayName) } function SubType(name,age){ SuperType.call(this,name) this.age = age; } inheritPrototype(SubType,SuperType); //改写 SubType.prototype.sayAge = function(){ console.log(this.age) }; var per1 = new SubType('nnn',30) console.log(per1.colors) //['red','blue','green'] console.log(per1.sayAge()) //30
这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.
prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用
instanceof 和isPrototypeOf()ECMAScript 支持面向对象(OO)编程,但不使用类或者接口。对象可以在代码执行过程中创建和
增强,因此具有动态性而非严格定义的实体。在没有类的情况下,可以采用下列模式创建对象。
工厂模式,使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来
被构造函数模式所取代。
构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用new 操作符。不
过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。由于函数可以不局
限于任何对象(即与对象具有松散耦合的特点),因此没有理由不在多个对象间共享函数。
原型模式,使用构造函数的prototype 属性来指定那些应该共享的属性和方法。组合使用构造
函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。
JavaScript 主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函
数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。
原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。解决这个问题的技术是借
用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的
属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用
原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
此外,还存在下列可供选择的继承模式。
原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅
复制。而复制得到的副本还可以得到进一步改造。
寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强
对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问
题,可以将这个模式与组合继承一起使用。
寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。知乎-作词人把自己最好的作品给了谁?
January 7, 2014
<style>
p{color:#000000}
</style>
又出了这个题目,我在底下从另一个角度回答了这个问题。
另外,说明一下这个答案为什么不更新了。因为自打写了这个答案,被无数公众号,微博和自媒体转载。我只是一个小透明作者,我唯一做到抵抗抄袭的方法,就是沉默。
所以,以后有关于港乐的回答,我都会贴到这个答案里面。大家从这个答案里面出去看吧。这篇答案我还没打算真正完结,下面再说说林若宁和小克。林夕和黄伟文也会接着聊。
再次感谢大家捧场。
林夕篇
说到作词人必说林夕。林夕作词那么多,本身性子又谦和,所以作词三千定有些词是用以应酬的行货,比如那首著名的《北京欢迎你》。(同是宣传曲,大家可以去听听陈咏谦的《同舟之情》,高低可见。)再例如最近给王菲填的《匆匆那年》。
但是有很多歌,他是用心去写的。这些歌词自然就给他最喜欢的歌手(们)了。下面我简要地讲讲,只是见识浅薄,若有纰漏烦请指出,不胜感激。1,张国荣:我就是我 是颜色不一样的烟火
我之前一直想这个问题,华语歌坛谁本里气质与林夕最贴。想了很久之后,得出结果是,男是张国荣,女是王菲。张国荣的歌里,林夕作词的数不胜数。譬如追(手机作答,书名号太麻烦,以下所有歌名皆不加书名号),偷情,怪你过分美丽,梦死醉生,最冷一天,陪你倒数,当然还有那首我。经典之作可以说涵盖了张国荣复出后的整个演唱生涯。张国荣逝世之后,林夕亦得过抑郁症,也曾怪罪自己当时写词太过抑郁,没能好好开导张。但是我觉得林夕和张国荣内核上是一类人。他们敏感,追求完美,雌雄同体。近年来,林夕钻研佛理,我想当年的抑郁他已在佛理中得到化解之道了。另:我最喜欢林夕写给张国荣的歌,是梦到内河。
2 王菲:忘记天地,仿佛也想不起自己
我严格意义上来说不算菲迷,好想邀请我一个豆瓣上的朋友来帮我说王菲的问题啊。我对王菲的理解还不如他十分之一。
但是,我也可以下个定论:若无林夕,则绝对无今日的王菲。
王菲在我心中最好的一张专辑当属《寓言》。当然也有人认为是《唱游》和《只爱陌生人》。《寓言》由林夕包碟,而在另外两张专辑里,林夕的名字也是频频出现的。再想想那些被认为是具有鲜明王菲特色的歌曲,几乎都出自于林夕之手。就好比我们提到周杰伦就定然会想到方文山,提到王菲第一个想到的定然也是林夕。在王菲最优秀的那几张专辑里,林夕仿佛是和她紧紧相随的一个烙印。王菲的时代离不开林夕在背后的默默付出。而整个华语乐坛,气质上与林夕最为贴切的女歌手,除王菲我不做第二人想。
或者用林夕自己的话来说:王菲是我没名分的妻子。
3 杨千嬅:我想哭你可不可以暂时别要睡
今天我们来说说杨千嬅。杨千嬅是一个有福气的歌手,香港两大作词人都对她非常青睐。林夕曾经 说过杨千嬅是他自己身上的一块肉。林夕的那句“原来我非不快乐,只我一人未发觉”就是出自杨千嬅的歌曲《再见二丁目》。林夕更为她量身打造过《姊妹》,《烈女》,《飞女正传》,《少女的祈祷》等歌曲。在这些歌里面,杨千嬅,这个面对爱情倔强,爱情运有些差的港女形象就被塑造出来了。而杨千嬅的声线本就是有些硬,非常合适这些歌。*但光凭借林夕为杨千嬅作词多,并不能说明杨千嬅在林夕心中的位置。那么哪里能看出来林夕对杨千嬅实在是喜欢呢?
我有两个理由。*
理由之一,是杨千嬅的一首歌,叫:《假如让我说下去》。
据说某日刮台风,林夕半夜睡不着,打给一位密友,满腹心事想和密友说,但是密友却很困了,回答了一句:你睡不着那就去写歌吧。于是便有了这首歌。我把歌词贴上来:
任我想 我最多想一觉睡去
期待你 也至少劝我别劳累
但我把谈情的气力转赠谁
跟你电话之中讲再会再会谁
暴雨天我至少想讲挂念你
然后你 你最多会笑着回避
避到底 明明不筋竭都力疲
就当我还未放松自己
我想哭 你可不可以暂时别要睡
陪着我 像最初相识我当时未怕累
但如果 但如果说下去
或者
傻得我 彼此怎能爱下去
暴雨中 我到底怎么要害怕
难道你 无台风会决定留下
但我想 如楼底这夜倒下来
就算临别亦有通电话
我怕死 你可不可以暂时别要睡
陪着我 让我可以不靠安眠药进睡
但如果 但如果说下去
亦无非逼你一句话:
如今跟某位同居
我的天 你可不可以暂时让我睡
忘掉爱 尚有多少工作失眠亦有罪
但如果 但如果怨下去
或者
傻得我 通宵找谁接下去这首歌里流露出的感情之浓烈,在林夕所有词之中也是很少见的。关键是它夹带了林夕个人的小心思。这样一首对林夕而言很重要的歌,就这么给了杨千嬅。
原因之二还是一首歌,就像林夕为陈奕迅写《大个女》,借歌词之口,说出对陈奕迅女儿的期盼,林夕亦有一首歌送给杨千嬅的孩子,那就是《亲》。感兴趣的朋友可以去搜搜看。
所以杨千嬅在林夕心中地位之高低,从这两首歌可见一斑。4陈奕迅:毫无代价唱最幸福的歌
好了,终于到陈奕迅了,作为陈奕迅资深黑(粉)。这个部分我想说的还蛮多的,一点点来。从最开始的《与我常在》,到最近的《任我行》。陈奕迅这个歌手,也是林夕倾注心血最多的歌手之一。而林夕在陈奕迅心中的地位恐怕也不低————到现在为止,陈奕迅每次演唱会必唱《与我常在》。而相较上文的几位歌手,林夕对陈奕迅更多的是一种前辈对后辈的提携和喜爱之情。
说到林夕和陈奕迅就不得不提另外一个人————陈辉阳。夕阳组合的化学反应在陈奕迅身上体现的淋漓尽致,无论是《K歌之王》,《人来人往》,还是《黑夜不再来》,陈奕迅都完美演绎了出来。我注意到,近几年林夕为陈奕迅写歌不再是偏向于情歌,从《和平饭店》,《黑择明》,到《六月飞霜》,《任我行》,这些歌或抨击或劝导,都不再只是爱情了。而也看到林夕对陈奕迅的期望值越来越高,不再希望他是那个唱苦情歌的普通歌手,而是希望借他之口,为整个社会发声。
大家也注意到了,从昨天开始,我每一位歌手名字后面,都会附上一句林夕的歌词,既作为代表亦是我从林夕为他们写的众多词中最能代表的一句。王菲是《约定》,张国荣是《我》,杨千嬅是《假如让我说下去》,那么陈奕迅的这句“毫无代价唱最幸福的歌”也是《我的快乐时代》里的一句词。
据说陈奕迅当初拿到《我的快乐时代》这首歌的时候并不理解这句歌词的意思,曾经拿它来问林夕是什么意思。林夕说你只管唱,后来你就明白了。我想随着陈奕迅从当年的毛头小子,到现在一呼百应的E神。在舞台上有太多不尽如人意的事情了。就好比陈奕迅在演唱会上吼了那么多首《浮夸》,唱《十年》唱到想呕,他还能“毫无代价唱最幸福的歌”吗?他自己也知道,在舞台上唱自己想唱实在是太难。
但是作为一位老歌迷看到虽然他现在也去担任选秀评委,也曾扮作过大橙子在舞台上唱果粒橙的主题曲,演唱会开的这么频繁,但是每张专辑都在寻求新突破还是挺欣慰的。(除了最近几张国语专辑实在不成样子。)所以请诸位再看看林夕写给陈奕迅的那句歌词,还有下半句。这里面包含了林夕这位词人在看尽了那么多歌手浮沉之后,对陈奕迅的一句真挚祝福:
毫无代价唱最幸福的歌,愿我可。
5麦浚龙:一熄灯一切被纪念
让我整理下呼吸,终于到了这位歌手了。我听麦浚龙的时间其实并不长,但是为什么要把他排在这个名单里面呢? 因为假如听过他近几年的专辑的话,就该知道他代表了香港现今港乐制作的最高水准。
为什么会这样?论外形还不如陈奕迅,唱功很差,甚至还有过黑历史。
因为丫有钱啊!!!!
有钱到什么程度呢?他能够让陈珊妮和林夕,王双骏和黄伟文为他包碟。至于要什么歌?反正他不在乎市场,概念专辑=自杀专辑?那又怎么样?!他照样能出,而且出的质量非常高。
从《天生地梦》开始,林夕给他的词的水准绝对都是超一流水准。各位试想,有这么一个人让你不顾虑市场,想写什么词写什么,你是不是会把原本不敢写,不让写的词一股脑写出来?
我贴两首我很喜欢的歌词,没听过的可以去听听看。
《生死疲劳》
曲:徐继宗 词:林夕 编:王双骏
唱:麦浚龙要赤脚往返铁道忘掉路轨 是谁造
要背上知识上路遗下月色 在计数
要擦到细胞发热燃亮蜡烛 等待被看到
烧得疲劳 追得疲劳 等得疲劳过度
要满载钢盔捍卫犹如活于 大和号
要吻到碎花绣在床上梦中 有更好
要跳上钢丝散步拿捏幸福的力度 到跌倒
生于疲劳 玩得疲劳 死于疲劳也好
*舍命对赌
赌得狠过凶徒 梦中都带刀
活得空似匈奴 一世未停步
沿途力气踏沉乐土看不到
朝夕倒数
快得超过跑道 直插入云雾
活得短似朝露 洗擦著坟墓
浮泥上 要建造净土
为回报 越搏越糟
要拼发满身冷汗成为露珠 不够数
要炼到置身赤地犹如洞天 未够高
要更快更多更大直到没更激味道
没更广阔囚牢 等年华成全野草
*舍命对赌
赌得狠过凶徒 梦中都带刀
活得空似匈奴 一世未停步
沿途力气踏沉乐土看不到
朝夕倒数
快得超过跑道 直插入云雾
活得短似朝露 洗擦著坟墓
浮泥上 要建造净土
为回报 美丽博弈残酷坚强便进步
劳累到 颓耐赤道梦遍花都
一直倒数
什么可算功劳 什么可当宝
夏花一瞬辛劳 拥抱著坟墓
红尘暴 脚下无净土 无人扫
如没永生 梦想都白造
忘掉往生 鞋尖都白舞
悲歌喜宴亦太早《逆苍生》
人 站到 太直 正好 跪人
又截断脊骨 打造神
神造了 偶像 教主 勾魂
恨那件法宝 全人类俯首献赠
人成就主人 驯服众生
像蚂蚁联群觅食之后 凡事不过问
宁愿变孤魂 宁犯众憎
违大势叛教规抗圣旨不甘安分
谁合作 涂炭亿兆苍生 其实是众生
奴隶佩服蝼蚁 就算天阔地大也像
牢狱里期望有救世主多兴奋 难得和枷锁合衬
aha 贼臣 歧视圣人
谁稀罕被御赐我自身 福份
我 不群 讪笑指引
不跟拍子唱颂礼义廉耻於跟人 蒙受圣恩
谁愿靠 万民 来护荫
反了天 迕逆了 反了海 僭越了
反了感 作闷了 反了台 作乱了
反了音 反众心 作孽了 作乐了
反众声 作别了 决裂了 背叛了
松了绑 我做我 我做我 得永生
我跪我 信朕
林夕在《天生地梦》,《无念》,《柔弱的角》里奉献的歌词都是他近几年的上流作品。不光是这样,除了唱功外,麦浚龙几乎每一张专辑的作曲编曲和作词都经得起最挑剔音乐人的评析。而他能把资源用来制作这种专辑(某歌手花几百万做专辑依然是渣渣),实在是听众甚至是粤语歌坛的一种福气。愿你我珍惜。下面是要说黄耀明的(提到黄耀明大家都邪恶地笑了),本来想写一句:你们懂哒~就算了,但是估计糊弄不过去。黄耀明部分暂时还没想好怎么写,林夕篇先告一段落。让我们进入另一位大词(pang)人(zi)的世界。
没错,他就是黄伟文。
先上图。本人作为歪闷的死忠粉,要说的实在太多。下面,让我们开始。
黄伟文篇
上帝如果是一个编剧,那么一定是一个非常喜欢热闹的编剧。可能是觉得一个梁伟文(林夕叫梁伟文) 不够, 还送来了一位黄伟文。这两位伟文作为香港作词界的两大高峰,不分高低却又各有所长。更奇怪的是,黄伟文仿佛是林夕的一个反面。与林夕为人谦和低调不同, 黄伟文为人张扬,鬼马。在写词上也是怪诞,鬼马,折堕,剑走偏锋。(当然近几年老了,以前那种“你不会有好结果”的词也越发少了。)我觉得黄伟文有个执念:要给他写的每首歌找一个合适的主人。比如他为年轻时候的陈奕迅写《两名男子街头相遇》,等到他进入而立之年,又为他单独写了男人四部曲。(《沙龙》,《葡萄成熟时》《人车志》,《陀飞轮》)。他为何韵诗写《汽水樽里的咖啡》,《再见露丝玛丽》,为卢巧音写《自恋影院》。
而另外一件有趣的事情在于,他写歌喜欢一个系列一个系列地写。比如上文说到的男人四部曲,除此之外还有垃圾四部曲,畸恋三部曲,海陆空三部曲等等。
除此之外,他的许多为人处世也很有意思,喜欢的奉若神灵,不喜欢的恨之入骨。但无论如何都无法否认,黄伟文对现在的香港乐坛所作出的贡献。下面,让我们进入第一位歌手。
1何韵诗:你是有病的,所以被宠爱。
照例。若想在香港乐坛找两位和黄伟文气质最贴切的歌手,男歌手肯定是陈奕迅。女歌手肯定是何韵诗。
何韵诗一直是走在争议前端的人物。作为梅艳芳的弟子,她在音乐之外的其他东西似乎更引人关注——无论是她的政治立场,还是她的中性打扮,亦或是和容祖儿的那些说不清道不明。可是除却这些因素,何韵诗在音乐上的表现依然可圈可点。甚至我可以在这个答案里第二次用这个评语(第一次是给麦浚龙):何韵诗几乎每一张专辑的作曲编曲和作词都经得起最挑剔音乐人的评析。尤其是她那张《青山十日谈》,可以认为是她个人音乐史上的一座巅峰。黄伟文在何韵诗身上不光是写情歌,有关注性别认知障碍的《汽水樽里的咖啡》,有疑似写何韵诗私人关系的《露丝玛丽》和《再见露丝玛丽》。更有为同性之爱正名的《劳斯莱斯》。歌词里的那句:“明明绝配 犯众憎便放开” 更像是对世人的控诉。能为她写出这样的歌,可谓是倾心倾力。而何韵诗的那首 《痴情司》(粤语)是我迄今为止,听到的最好的有关红楼梦的词。
八卦一句:歪闷这货居然说写这首歌的时候没看过红楼梦,只是听了何韵诗说了一遍就写出了《痴情司》。这种事情自己知道就好啦,说出来干嘛= =、而且只听说一遍就能写出来《痴情司》也是蛮吊。
再插嘴一句:虽然同性恋被大众认可只是时间长短的问题,何韵诗黄伟文能作为先锋,站出来为这个群体发声,恐怕光是这份担当就足够让人佩服。
不过另外一点很奇怪的事情是,相较于何韵诗对于政治领域的毫不避讳,黄伟文对于政治的兴趣甚至远不如林夕。黄伟文关注更多的是普世的价值观。而这点我下面也会慢慢说到。
何韵诗和黄伟文,在某些程度上其实像极了不同性别的两个人。就像黄伟文写给何韵诗的那首《青山黛玛》的开头,既像是告白,亦像是自白:你是有病的,所以被宠爱。
2容祖儿:喜欢你让我下沉,喜欢你让我哭。
是的,你没有看错,写完何韵诗,我就跑来写容祖儿了。很想知道你们脸上的坏笑是什么样子的。黄伟文对容祖儿的偏爱不如何韵诗那么明显。但是在黄伟文作品展上,作为开场首秀,联唱三首《桃色冒险》,《跑步机上》,《隆重登场》的歌手正是容祖儿。而黄伟文为容祖儿写歌也可说是毫不吝惜。几乎包含了黄伟文所擅长的所有种类,而且几乎首首大热。《心淡》更是在去年在香港评选的我最喜爱的粤语歌当中,拔得头筹。和杨千嬅歌里面塑造的港女形象类似,黄伟文为容祖儿所写的歌可能也是她自身的写照。从《痛爱》里:能持续获得糟蹋亦满足;到《16号爱人》里:忍了你,你更薄幸,能叫我跌到永不超生;再到《破相》里面:越笑不幸,留了提示,是谁极不幸。这种失恋了也要恶狠狠揭开伤疤,再撒上盐的折堕词正是黄伟文的拿手好戏。而这几首更是他这类词作里的经典。我们来看看痛爱的歌词:
曲 :陈辉阳
词 :黄伟文
演唱:容祖儿
仍然难禁看着你这个坏人
有什么的吸引
残酷至此更让我想靠近
心知要换个别人
还是有人
如同前世欠下你的吻
还怎么敢怒愤
已习惯亲朋好友问我怎会为你等
学会讲指因这种狠深得我心
喜欢你让我下沉
喜欢你让我哭
能持续获得糟踏亦满足
喜欢你待我薄情
喜欢你为人冷酷
若是你也发现
你也喜欢亏待我
我就让你永远痛爱着我
和谐甜美永没有天意弄人
有什么的吸引?
谁待我好
我就会不过问
偏偏碰着那坏人全部诱人
全球情侣故事也相近
宁愿天昏地暗
要为错的人伤过恨过
方算是勇敢
长世间不喜欢开心喜欢痛心
喜欢你让我下沉
喜欢你让我哭
能持续获得糟踏亦满足
喜欢你待我薄情
喜欢你为人冷酷
若是你也发现
你也喜欢亏待我
我愿让你爱上我更加多
喜欢你让我下沉
喜欢你让我哭
能持续获得糟踏亦满足
喜欢你待我薄情
喜欢你为人冷酷
若是你也发现
你也喜欢亏待我
我就让你永远痛爱着我歌词里写着:喜欢你让我下沉,喜欢你让我哭,能持续获得糟蹋亦满足
甚至自嘲:和谐甜美,永没有天意弄人,有什么的吸引?我想任何曾经承受过单恋之苦的人,都能体味这种痛到骨子里的痛爱。昨天有个朋友私信我,聊起最近失恋之苦。今天我把这首歌贴出来,是因为 这首歌陪伴我度过了一段暗无天日的失恋时光。是以把它再贴出来。但是我也要告诉那位朋友在经历失恋之后的我们也会发现:春天分手,秋天会习惯,苦冲开了便淡。来日方长,祝安好。
3杨千嬅:但我会成为你最牵挂的一个女子
杨千嬅真是一个有福气的歌手。林夕说:她是我身上的一块肉。黄伟文更是把她当做挚友。我们常常说某词人非常喜欢某个歌手,可是关系到挚友这一层的歌手与词人其实并不多。而两位词人都把杨千嬅视作自己挚友,这就非常难得了。如果说,林夕为杨千嬅写过很多量身打造的歌,(甚至写过一首歌名字就叫《杨千嬅》)。但是如果硬要从两位词人为杨千嬅写的众多歌词当中挑选一首最适合杨千嬅的歌,那么必然是这首《野孩子》。我很难想象黄伟文是怀着怎样的心情去写这首歌的,我甚至想,他在写这首歌的时候恐怕已经将自己感同身受成杨千嬅本人。
所以,在这首歌没有得到应有的重视,黄伟文在他自己的十年选里写下了这段话:
其实我一直怀疑杨小姐不曾喜欢过我为她写的歌词,那些道谢,直觉上都是客套话。但一直不太喜欢却一直采用,也许是种更伟大的包容,而我,真的,都尽力了。
上面话的分量,不必多言,而黄伟文也和杨千嬅从此中断了合作。
后来的事情,大家都知道了。包括杨千嬅听到《最佳损友》后将车停在马路边的痛哭,包括杨千嬅在黄伟文作品展上,挺着大肚子地上台演出。包括黄伟文从后台推出来的那辆婴儿车,包括最后徐徐响起陈奕迅的《最佳损友》。
我在YYconcert的时候,数度被这一场景感动到热泪盈眶。人生得一知己,尚且难得,而失而复得更加难得。而《最佳损友》这首字字戳心的歌,也成为这段伟大友谊的见证。
黄伟文作品展2012演唱会__演员杨千4刘浩龙:这是人生吧,笑着来捱打。
我知道我写下这个名字会有很多人觉得奇怪,甚至质疑。但是我仔细思量了一下,黄伟文对刘浩龙的确是不薄。
我敢打赌,大部分人知道刘浩龙都是因为他的声音很像陈奕迅。如果刚听港乐甚至可能觉得他是个刚刚出道的新人。可是,他是1976年的,比陈奕迅仅仅小两岁。
刘浩龙严格意义上来说,算不得是一个成功的艺人。
我们来看一下百度百科上他的简历:
1997年参加第一届全球华人新秀歌唱比赛连摘金奖、最佳演绎及最具演艺潜质三个奖项。同年正式出道。
1997年至2000年担任节目主持工作,包括Sunday新地带、周末新地带、翡翠音乐干线2000及多个旅游节目。1998年参演电视剧《双面伊人》,2002年参演电影《魂魄唔齐》。2012年推出专辑《小精选》。2010年参演电影《飞砂风中转》。2012年参演电视剧《第二人生》、举办《刘浩龙小音乐会》。2013年在电影《末日派对》中担任主演。
细心的人会发现,他的履历和陈奕迅是如此的相似。同样是选秀出身的他,境遇却千差万别。1997年到2000年当主持人。在2004年才推出他的第一张专辑,2012年才推出第一张个人精选《小精选》。而最后大部分人知道他,也是因为陈奕迅。
他有首蓝奕邦为他写的歌,叫《战友》。里面有句歌词叫:拿木吉他一起奋斗的年代,坐冷板凳等新发展。在我看来正是对他那些年的写照。战友的MV也很有意思,有兴趣的朋友,去搜了看看,里面就有他模仿陈奕迅唱功的片段。
而这样一位歌手,在那张仅仅15首歌的精选集里,黄伟文的歌就占了将近十首。若说黄伟文对他不好,我都不相信。
而《脏话阿七》这首歌,更像是黄伟文为这位歌手写的一首自传。在YYconcet上的短短演出,我看到了这个男人在娱乐圈所经历的浮沉和心酸。这首歌也在我困难的时候数度激励过我。击倒了再爬
昂然爬回家
练过功夫再继续打
就算他将你压下
为了活已不可再害怕
如世情无法再招架
试试怒骂直到骂退它
看看这首歌唱的吧,虽然它粗犷,不修边幅,没有那么多精巧的句子,但是我嗅到的是一个男人的心酸与血性。
贴上黄伟文作品展上的刘浩龙:【MV】刘浩龙 -脏话阿七 Concert YY 黄伟文作品展 现场版 中文字幕 12/02/14-高清MV在线播放PS:我知道很多何韵诗的歌迷非常讨厌他。他和何韵诗还有容祖儿的恩怨我不关心,也不想关心。或许他得到的掌声没有我上述的任何一位歌手多,但倘若有人在这篇答案底下辱骂他,拉黑不送。不为什么,仅仅是因为,他是个值得我们尊敬的歌手。
好啦,我回来了。在继续更新之前我想集中回答一下大家在评论区的问题
1窦唯对王菲的影响真的有那么大吗?
我询问了我在豆瓣上的那位朋友。他的意见是:窦唯对王菲有影响,但肯定不是最大的。在征询了他的意见之后,贴上他在豆瓣上对此的文字:《浮躁》全评其八:不该被忘却的,那个为王菲写《扫兴》的乐队 作者 豆瓣 渡辺纯一狼
2为什么不写XXX?!连XXX都不写!
我写的这几个歌手都是我比较有底气的。起码,写出来应当不会被内行人笑话,说这是什么玩意儿。大家说的那几位歌手我都听过,但是掂量斤两,没把握写好。这个答案写到现在,可以说是我在知乎上倾注心血最多的答案,赞同数已过千。所以我也得端正态度,对每位歌手尽力做出我最好的评价。
3为什么不写方文山?没什么可写的。
5陈奕迅:若你喜欢怪人,其实我很美。
讲句公道话,黄伟文对陈奕迅所贡献的,远比陈奕迅在其他人身上的要拿到的多。(对,比林夕多。)我已经不大数的清,黄伟文为陈奕迅写过多少好歌了,而这其中又有多少成为了陈奕迅个人生涯成功的标志。从畸恋三部曲,到男人四部曲(《裙下之臣》不在其列),再到“情人若寂寥地出身在1874”的《1874》。当然,还有那首《浮夸》。还有些歌我就不一一列举了(比如黄伟文和陈奕迅说想听你在我葬礼上唱《活着多好》)。如果陈奕迅没有黄伟文为他写歌,那么他只会是一个唱歌唱得不错的歌手,而绝不是陈奕迅。
在所有陈奕迅的歌里面,说到黄伟文,得说到下面几张专辑。第一张是《The Easy Ride》。这张专辑是黄伟文为陈奕迅包碟的。在里面包含了《阿士匹灵》,《冲口而出》,《大开眼戒》,《他一个人》,《热带雨林》,《我不好爱》,《人工智能》。质量之高,在陈奕迅历年的所有专辑都属少见。
另一张专辑则是《U—87》。在这张以麦克风命名的专辑里,黄伟文贡献的几首歌,分别是——《浮夸》,《不良嗜好》,《葡萄成熟时》。虽然《浮夸》现在火成这样,但是确实是黄伟文的巅峰之作。不良嗜好用戒烟比喻爱情,眼前一亮,却也一语双关写出男人的苦恼。(这样鬼怪却一语中的的词已经是黄伟文的标志)。我甚至建议可将《不良嗜好》加进男人四部曲当中。因为戒烟确实是大部分男人都要经历的一个过程啊。《葡萄成熟时》可以说是失恋疗伤神曲。每每有朋友失恋,我都会和他说,你去听一首歌,叫《葡萄成熟时》,听懂了,也就走出来了。毕竟谁都心碎过,哪个没有?
最后一张专辑是一张EP。在短短六首歌,黄伟文就为陈奕迅写了三首歌《无人之境》,《陀飞轮》。那么我为什么要把这三张专辑单独拎出来说呢?第一张专辑,代表了黄伟文为陈奕迅写的好歌
第二张专辑则代表了黄伟文开始为陈奕迅写歌。
第三张则代表了黄伟文词作的另一个高峰。从这里开始,他已经可以“随心所欲,不逾矩。”歌词风格也变得可正可邪,变幻无方,正式成为可以和林夕相提并论的大词人。我在写今天的标题的时候,几乎没怎么犹豫就写上了这句。这句歌词,被黄伟文认真地写在了十年选的扉页上。这既是对他自己的评价,也是对陈奕迅的评价。
若你喜欢怪人,其实我很美。
林夕篇增补:
1古巨基:先挑引我的人,爱的比我少,然后见面更少。
之前有好事者做过统计,计算出林夕为每一位歌手填词的数量,然后发现,第一名不是王菲,不是张国荣,而是古巨基。所以,其实说到林夕,古巨基是个不得不提的歌手。
相信很多人和我一样,在开始听港乐之前,对古巨基的印象还只是那个开场就会唱《好想好想》的一首歌歌手。可是当开始听港乐的时候,会发现,古巨基在乐坛居然有这么大量的优秀歌曲。我听古巨基是从他那张《时代》开始的。当时耳机里放的是《钻石败犬》————你努力情场仍然阴森,再努力寂寞仍然生吞,经过你雕琢青春的钻石,还是有份价值无负半生。
这种略带治愈系的慢歌简直就是古巨基的拿手好戏。古巨基的声线是所有男歌手中我最喜欢的之一(另一位是方炯镔),戴上耳机的时候,他的声音就像一团暖烘烘的热水将你裹住,非常舒服。再加上他运用纯熟的假音技巧,使得慢板抒情歌曲几乎都能被完美诠释。(不信?如果不是这么好的技巧和声线天赋是不能驾驭像《情歌王》和《劲歌金曲》这种串烧歌曲的。)另外还有一个原因就是,古巨基很多张专辑都是概念专辑。什么叫概念专辑?就是专辑的所有歌曲都是围绕同一主题展开的。比如先前举例说到的《青山十日谈》,《无念》,再比如李克勤的《智能身份》,林宥嘉的《大小说家》。而古巨基的概念是动漫和游戏。而诸如《任天堂流泪》,《大雄》,《漂流教室》,《必杀技》等等都来自这两张专辑。而这些统统由林夕作词。不过顺带说到概念专辑这点,在我心中,港乐坛排名第一位的概念专辑一直是卢巧音的《天演论》。
给古巨基的这句歌词,是《心跳回忆》中的一句歌词。这首夕阳组合的情歌一直是我心头所爱。为什么要说这样一首歌?这首歌本质上来说,是一首怨念颇重的歌:无论时隔多年我也心跳,最后和你虽如我那所料,先挑引我的人,爱的比我少,然后见面更少。
大家看看这样怨气重的话,歌里面的男主角分明是一个没有放下爱情的人。
如果这首歌算上半部分,如果有下半部分的话,那么肯定是古巨基2012年的《告别我的恋人们》。同样是夕阳组合,时隔十年,林夕为这位男主角写下了他认为最为完满的结局:
告别我的恋人们 谁没有得到更多
错误错得可玩味 未留下也算好结果
擦亮你的也擦伤我 难捱亦值得被庆贺
忍痛着切磋 只贪求下个 更好过
总会复元吧 痛快得快乐吗
曾经风吹雨打 才对得住炎夏
要是从未伤心到害怕
没有承受过多少牵挂
又怎证实感情开过花
又怎容纳下个她
早已复元吧 会爱得更易吗
还枯萎几扎花 才爱出一个家
学习成熟也因天真过
乐与苦 同样无价
我们也应感激这些 爱的代价
偶遇我的恋人们 谁情愿被不断替换
坚拒被割损 怎可磨合到 那一半
总会复元吧 痛快得快乐吗
曾经风吹雨打 才对得住炎夏
要是从未伤心到害怕
没有承受过多少牵挂
又怎证实感情开过花
又怎容纳下个她
回望初恋为何未杀出血路
才能体会到
爱只懂容纳爱 说永恒还太早
.
早已复元吧 会爱得更易吗
留低多少个疤 才爱出一个家
学习成熟也因天真过
乐与苦 同样无价
往日年少怎欣赏那首 爱的代价
告别我的恋人们 其实你听得到吗?忘掉被你感动却也心跳,往后情侣一如每次所料。我爱的不少,遗憾也不过是,并没法使我的心这样跳。《心跳回忆》
回望初恋为何未能杀出血路,才能体会到,爱只懂容纳爱,说永恒还太早。《告别我的恋人们》这时隔十年的答案,恐怕是林夕为古巨基送上的最好礼物。
PS:近来听闻古巨基和他相伴多年的女助理成婚,祝福他们,也祝福每一位,都好像他。准时来填坑。
2王菀之:火星只可似火星。
今天我们来说说王菀之。王菀之也是我非常喜欢的女歌手之一。声线好,唱功上佳,音乐素养也相当高。香港流传的创作四小强,就是指她,张敬轩,方大同与张继聪。一查她的经历,便可以知道早年就有学习系统音乐理论的底子。在音乐上,她出的几张专辑采用大量的钢琴加鼓点结合的编曲形式(这点主要感谢她本人和冯翰铭,只是冯翰铭不属于作词人,在此不再赘述。),再配合她本人的嗓音又是属于清亮一路,因此相得益彰,专辑质量大多数不低。
林夕和黄伟文都很爱为王菀之写歌。而且两人也在同一题材上为她写歌。林夕为她写《画意》,表面是写梵高,其实是颇为她鸣不平:看他有权乱去画,也许口袋也不致一片空白。在艺术和商业之间的抉择,在画意当中显得分外无奈又讽刺。
无巧不成书,黄伟文也是写了一首类似的歌来说这种现象:金线刺绣的歌词/命中要奉献他沦为谎话/天宫早变败瓦/真丝化做乱麻/你那生平杰作被埋他脚下。这首歌似写感情,实际上还是说的是类似的现象,只是怨气颇重。
除此以外,林夕似乎不满足于只给她情歌,更为她写了像《留白》和《我来自火星》这种歌。在这几首歌当中神来之笔颇多,比如标题里那句:火星只可似火星。将一个极大的意象和一个极小的意象放到一起对比,非常精彩。
而相较这些歌,我最喜欢的歌却是她为薛凯琪写,自己也翻唱过的一首歌,叫《小黑与我》。
而她本人翻唱的这首歌,是我听过的她最精彩的一次演出。
贴上链接:小黑与我 王菀之 小黑与我歌曲,小黑与我mp3在线试听
这首歌欲抑先扬,将一位屡受挫折的少女心思写的丝丝入扣。可是就算歌里说了:而命书说我像孤星一辈子。在结尾,歌里的女主人公依然在小巷发现了一只小黑猫:小小的猫比我还强壮,过去的咒语,我拒绝信。 这句词正是整首歌的点睛之笔。用通篇歌词诉说人生多艰,却在结尾决定坚强面对未来,这是最打动我的地方。下面我们来说一个现象:在很多时候,我们会发现,很多歌手有相当多的优秀作品,可是知名度还不如那些平庸之辈。譬如某歌手论专辑质量,不过尔尔,却到美国领了个大奖。像王菀之这样认真做音乐,却得不到赏识。这是为什么呢?
我认为,首先知名度不一定和音乐做的好坏程度成正比。很多歌手红起来是依靠某个契机。如果不是那首《董小姐》,现在的宋冬野也是一个小众歌手;如果没有《梵高先生》,李志现在或许依然在睡沙发。这是其一,其二是大众的音乐素养并没有达到这个要求。你和一个人说编曲,说作词,说各方面,他觉得听着还凑合就成。大部分网络歌手粗制滥造的程度令人发指,但是大家依然在传唱。所以林夕才会想“拯救”王菀之。在我看来王菀之算是一个幸运的歌手了,不和其他人比,和上文的刘浩龙比就已经算是幸运儿了。但是我们还是要感谢,正因为有这样的音乐人存在,他们坚持理想,做出优秀的音乐供我们不至于周围全是粗制滥造的烂情歌,而我们作为听众,更应当珍惜。3黄耀明:难道要等一千零一岁,才互相安慰。
终于,大家期盼已久的黄耀明要来了。让我来看看你们脸上的坏笑。既然说到黄耀明,那么必然要提到一个人。
对,那就是——————————————————————————————————刘以达。
很多人知道黄耀明,却很少人提到刘以达。可是提到达明一派,那么稍微对港乐有所了解的人,都至少听过这个名字。而达明一派正是由黄耀明和刘以达组成的组合。很多人问刘以达是谁,我上一张照片大家就知道是谁了。
......就是这货。
容我去蹲会儿墙角。因为之前有讨论过概念专辑的话题,我说刘以达的《麻木》算是香港电子乐的翘楚了。然后再给他们看刘以达照片,他们总是一副“你TM在逗我”的表情。
说到达明一派必须说刘以达,而刘以达虽然在各种喜剧电影里不正经,音乐还是非常非常正经的。
别的不说,这张1996年的专辑,其制作水准和音乐理念放到今日依然可以独领风骚。
我们再来说说和他搭伙的黄耀明。黄耀明最吸引我的是他身上的气质。这种柔和的气质在男人身上并不多见,而他的柔与张国荣的柔并不相同。张国荣的柔有些孤傲,而他更多的是一种媚的气质(非贬义)。这也是我为什么时至今日才开始写黄耀明的原因,这种气质成就了他,也局限了他。再看,林夕为他写的词,大多数是带点柔媚风格的词。譬如那首传唱已久的《春光乍泄》:难道你可遮掩着身体,来分享一切。再比如《身外情》:带走开心,却带不走拖手时的体温,微暖质感,留在脸上还未吻。再说一首新词《金粉世家》:一张脸一双眼晶亮如琉璃瓦,一道眉一双唇雕琢如瘦金笔划。这些词无一例外都带有明显的个人特色。与其说是黄耀明个人有些骚情的特色,不如说是林夕词在他声音里发生了相得益彰的化学反应。
说到这儿,我想再说我一个好友 @VivianWong特别喜欢的一首歌《风月宝鉴》。摘录歌词如下:你千年万年
想免却生离
谁介意你死别
想壮志不灭
谁敬佩你贞烈
石破海枯的挑战
你坚毅
你执迷
你起誓
你桑田永不变
想听信谎言
谁要对你分辩
想到发都白
无法看透黑白
石破海枯的贞节
你火热
你痴缠
你哀艳
你不眠也不变
过眼云烟里兜兜且转转
从顽石取每滴甜
死心眼塌地千洗百炼
炼石去补青天
你永垂不变
怎么可升仙
如顽石坐井想观天
守得到信念
等不到兑现
炼石错补青天
你以为展翅
真的可升仙
如顽石坐井想观天
守得到信念
等不到兑现
补不到奈何天
剪去了双眉
还有两眼分辨
双眼也不辨
还有笑意不灭
石破海枯的挑战
你坚毅
你执迷
你起誓
你桑田永不变
撕去了衣裳
还有送抱的脸
烧了这张脸
还有似舌火焰
石破海枯的加冕
你火热
你痴缠
你哀艳
你不眠也不变
想再见一面
谁要见你的面
想细诉思念
谁要你去想念可见,这首是一首略带挑逗意味的情欲词。加上浓重的电子编曲和黄耀明的声音,这是一首能让人听到耳根发烫的歌。‘
我理解大家一直在喊他名字的原因,但是我作为听众,对这件事情实在是不敢妄加评论。一来这是私事,二来网上关于此的猜测解读已经足够多了。但是我看到的是,林夕在尽力地,从黄耀明的角度来写词。这对林夕这样一位词人,已属于难能可贵。(这些都是假的!!!提示我都放在标题的那句歌词里了!!大家脑补去吧!!不谢!)
说到黄耀明,又不得不提的另一个男人是张国荣。因为他和张国荣在2002年出过一张合作专辑,叫《cross over》。
这个封面被无数港乐迷奉为经典。这两张精致的脸,这两位雌雄同体的男人,以这样的形式,出的一张专辑,真是要把人骨头唱酥了。可是也正是在这时候,张国荣抑郁病重,在《这么远,那么近》里,甚至不能到现场录音。而只是播放录音,加以合成。此时想来,不禁唏嘘。对黄耀明,我一直很矛盾。一方面,作为香港流行音乐的先导者,至今仍然是话题人物。他的言行,他对同性恋的观点,他的政治立场,甚至他和林夕的那些小暧昧,都为人津津乐道。但是从另一方面讲,他的音乐也并非尽善尽美(近几年在扶持后辈上是花了功夫),影响力与投入并不成正比。但是换念一想,他个人的天赋,他的本身的特质,已经是上帝给予的最佳礼物。
零CSS基礎 | 公開課
January 6, 2014 宋休: w * h xp下 12px[11 * 11] win7 12px[11 * 12] 雅黑: win7 12px[12 * 11] 标签样式尽量加给块元素,如<h2><a>,设置a字体尽量设置在h2上 行高设置比字体大 行高与高度:ie6都设,不考虑ie6可不设置高度 ie6/7 不支持给li加hover不支持 清浮动: ... <div class='clear'></div> <br clear='all' /> .clear{zoom:1} .clear:after{content:'',display:block;clear:both} 触发bfc css3中 flow root display:inline-block; table-cell/table-caption 注意:inline-block下面会有空隙,居中也麻烦 float不为None overflow不为visible position不为static relative 字体情况下每个数字宽度占字体一半大小 overflow (ie67 zoom:1) font复合样式包括line-height,如果line-height单独写需放在font后面 小图标统一都是背景 字体14 上8下8,则line-height:14 + 8 + 8 = 30 active 选项卡下边框盖住,active{ padding-bottom:1px } ie6 { padding-bottom:1px;margin-bottom:-1px;overflow:hidden;position:relative; } margin 负值 {margin-top:20px;}并且把父级高级撑开20px; {margin-top:-20px}少占父级20px,父级少撑开1px,自身大小占用大小不改变;position:relative解决ie6下出去的部分不会被父级截掉 letter-space ie6下会出问题 两border相交时,为梯形,不相交时,为直形 元素浮动之后宽度如果是内容撑开的,就给里边所有块元素,都加浮动 ,否则给元素设置宽度 type=hidden 模擬select radio 把選擇的值覆給hidden,所以可以提交 background-position控制圖片在元素中的位置 input與外層高度一致 + float:left; <style> .box{ height: 40px;border:1px solid #000000; font-size: 12px; font-family: '宋体';} input{ margin: 0; padding: 0;vertical-align: middle} span{ display: inline-block; height: 40px;vertical-align: middle; line-height: 40px;} input[type=radio]{ width: 13px; height: 13px;} select{ width: 300px; background: #0ff; height: 40px;} /* ie6不支持高度设置 */ option{ background: #000; color: #ffffff;} </style> <div class="box"> <input type="radio" name="" id=""/><span>邮件</span> <input type="text"/> </div>