构造函数的问题
使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题
:
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = function () {
console.log('hello ' + this.name)
}
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
在该示例中,从表面上好像没什么问题,但是实际上这样做,有一个很大的弊端。 那就是对于每一个实例对象,type
和 sayHello
都是一模一样的内容, 每一次生成一个实例,都必须为重复的内容,多占用一些内存,如果实例对象很多,会造成极大的内存浪费。
console.log(p1.sayHello === p2.sayHello) // => false
对于这种问题我们可以把需要共享的函数定义到构造函数外部:
function sayHello = function () {
console.log('hello ' + this.name)
}
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = sayHello
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
这样确实可以了,但是如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题。
你肯定想到了可以把多个函数放到一个对象中用来避免全局命名空间冲突的问题:
var fns = {
sayHello: function () {
console.log('hello ' + this.name)
},
sayAge: function () {
console.log(this.age)
}
}
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = fns.sayHello
this.sayAge = fns.sayAge
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
console.log(p1.sayAge === p2.sayAge) // => true
更好的解决方法:原型
Javascript 规定,每一个构造函数都有一个 prototype
属性,指向另一个对象。 这个对象的所有属性和方法,都会被构造函数的实例继承。
这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype
对象上。
function Person (name, age) {
this.name = name
this.age = age
}
console.log(Person.prototype)
Person.prototype.type = 'human'
Person.prototype.sayName = function () {
console.log(this.name)
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayName === p2.sayName) // => true
构造函数、实例、原型三者之间的关系
任何函数都具有一个 prototype
属性,该属性是一个对象。
构造函数的 prototype
对象默认都有一个 constructor
属性,指向 prototype
对象所在函数。
console.log(F.constructor === F) // => true
通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype
对象的指针 __proto__
。
var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true
总结:
也就是说,在我们调用 person1.sayName()
的时候,会先后执行两次搜索:
而这正是多个对象实例共享原型所保存的属性和方法的基本原理。
总结:
实例对象读写原型对象成员
读取:
值类型成员写入(实例对象.值类型成员 = xx
):
引用类型成员写入(实例对象.引用类型成员 = xx
):
复杂类型修改(实例对象.成员.xx = xx
):
更简单的原型语法
我们注意到,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype
。 为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:
-
任何函数都具有一个
prototype
属性,该属性是一个对象 -
构造函数的
prototype
对象默认都有一个constructor
属性,指向prototype
对象所在函数 -
通过构造函数得到的实例对象内部会包含一个指向构造函数的
prototype
对象的指针__proto__
-
所有实例都直接或间接继承了原型对象的成员
-
属性成员的搜索原则:原型链
了解了 构造函数-实例-原型对象 三者之间的关系后,接下来我们来解释一下为什么实例对象可以访问原型对象中的成员。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性
-
搜索首先从对象实例本身开始
-
如果在实例中找到了具有给定名字的属性,则返回该属性的值
-
如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性
-
如果在原型对象中找到了这个属性,则返回该属性的值
-
首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。
-
”然后,它继续搜索,再问:“ person1 的原型有 sayName 属性吗?”答:“有。
-
”于是,它就读取那个保存在原型对象中的函数。
-
当我们调用 person2.sayName() 时,将会重现相同的搜索过程,得到相同的结果。
-
先在自己身上找,找到即返回
-
自己身上找不到,则沿着原型链向上查找,找到即返回
-
如果一直到原型链的末端还没有找到,则返回
undefined
-
先在自己身上找,找到即返回
-
当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上
-
也就是说该行为实际上会屏蔽掉对原型对象成员的访问
-
同上
-
同样会先在自己身上找该成员,如果自己身上找到则直接修改
-
如果自己身上找不到,则沿着原型链继续查找,如果找到则修改
-
如果一直到原型链的末端还没有找到该成员,则报错(
实例对象.undefined.xx = xx
)
更简单的原型语法
我们注意到,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype
。 为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}
在该示例中,我们将 Person.prototype
重置到了一个新的对象。 这样做的好处就是为 Person.prototype
添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 constructor
成员。
所以,我们为了保持 constructor
的指向正确,建议的写法是:
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person, // => 手动将 constructor 指向正确的构造函数
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}
原生对象的原型
所有函数都有 prototype 属性对象
案例:随机方块
1.创建一个页面,在页面中设置一个层,产生的方块都是在这个层中
<style>
.map {
width: 800px;
height: 600px;
background-color: #CCC;
position: relative;
}
</style>
2.创建common.js文件
//foreach的兼容代码
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
// 1. Let O be the result of calling toObject() passing the
// |this| value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get() internal
// method of O with the argument "length".
// 3. Let len be toUint32(lenValue).
var len = O.length >>> 0;
// 4. If isCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let
// T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let k be 0
k = 0;
// 7. Repeat, while k < len
while (k < len) {
var kValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty
// internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
// ii. Call the Call internal method of callback with T as
// the this value and argument list containing kValue, k, and O.
callback.call(T, kValue, k, O);
}
// d. Increase k by 1.
k++;
}
// 8. return undefined
};
}
//格式化日期的代码
//根据id获取元素的代码
//innerText和textContent的兼容
//获取第一个子元素的兼容
//获取最后一个子元素的兼容
/**
* 格式化日期
* @param dt 日期对象
* @returns {string} 返回值是格式化的字符串日期
*/
function getDates(dt) {
var str = "";//存储时间的字符串
//获取年
var year = dt.getFullYear();
//获取月
var month = dt.getMonth() + 1;
//获取日
var day = dt.getDate();
//获取小时
var hour = dt.getHours();
//获取分钟
var min = dt.getMinutes();
//获取秒
var sec = dt.getSeconds();
month = month < 10 ? "0" + month : month;
day = day < 10 ? "0" + day : day;
hour = hour < 10 ? "0" + hour : hour;
min = min < 10 ? "0" + min : min;
sec = sec < 10 ? "0" + sec : sec;
str = year + "年" + month + "月" + day + "日 " + hour + ":" + min + ":" + sec;
return str;
}
/**
* 获取指定标签对象
* @param id 标签的id属性值
* @returns {Element}根据id属性值返回指定标签对象
*/
function my$(id) {
return document.getElementById(id);
}
/**
* 设置元素的文本内容
* @param element 任意元素
* @param text 任意文本内容
*/
function setInnerText(element, text) {
if (typeof(element.textContent) == "undefined") {
element.innerText = text;
} else {
element.textContent = text;
}
}
/**
* 获取元素的文本内容
* @param element 任意元素
* @returns {*} 任意元素中的文本内容
*/
function getInnerText(element) {
if (typeof(element.textContent) == "undefined") {
return element.innerText;
} else {
return element.textContent;
}
}
/**
* 获取父级元素中的第一个子元素
* @param element 父级元素
* @returns {*} 父级元素中的子级元素
*/
function getFirstElement(element) {
if (element.firstElementChild) {
return element.firstElementChild;
} else {
var node = element.firstChild;
while (node && node.nodeType != 1) {
node = node.nextSibling;
}
return node;
}
}
/**
* 获取父级元素中的最后一个子元素
* @param element 父级元素
* @returns {*} 最后一个子元素
*/
function getLastElement(element) {
if (element.lastElementChild) {
return element.lastElementChild;
} else {
var node = element.lastChild;
while (node && node.nodeType != 1) {
node = node.previousSibling;
}
return node;
}
}
/**
* 获取某个元素的前一个兄弟元素
* @param element 某个元素
* @returns {*} 前一个兄弟元素
*/
function getPreviousElement(element) {
if (element.previousElementSibling) {
return element.previousElementSibling
} else {
var node = element.previousSibling;
while (node && node.nodeType != 1) {
node = node.previousSibling;
}
return node;
}
}
/**
* 获取某个元素的后一个兄弟元素
* @param element 某个元素
* @returns {*} 后一个兄弟元素
*/
function getNextElement(element) {
if (element.nextElementSibling) {
return element.nextElementSibling
} else {
var node = element.nextSibling;
while (node && node.nodeType != 1) {
node = node.nextSibling;
}
return node;
}
}
/**
* 获取某个元素的所有兄弟元素
* @param element 某个元素
* @returns {Array} 兄弟元素
*/
function getSiblings(element) {
if (!element)return;
var elements = [];
var ele = element.previousSibling;
while (ele) {
if (ele.nodeType === 1) {
elements.push(ele);
}
ele = ele.previousSibling;
}
ele = element.nextSibling;
while (ele) {
if (ele.nodeType === 1) {
elements.push(ele);
}
ele = ele.nextSibling;
}
return elements;
}
/**
* 返回当前浏览器是什么类型的浏览器
*/
function userBrowser(){
var browserName=navigator.userAgent.toLowerCase();
if(/msie/i.test(browserName) && !/opera/.test(browserName)){
console.log("IE");
}else if(/firefox/i.test(browserName)){
console.log("Firefox");
}else if(/chrome/i.test(browserName) && /webkit/i.test(browserName) && /mozilla/i.test(browserName)){
console.log("Chrome");
}else if(/opera/i.test(browserName)){
console.log("Opera");
}else if(/webkit/i.test(browserName) &&!(/chrome/i.test(browserName) && /webkit/i.test(browserName) && /mozilla/i.test(browserName))){
console.log("Safari");
}else{
console.log("不知道什么鬼!");
}
}
//为任意一个元素绑定事件:元素,事件类型,事件处理函数
function addEventListener(element,type,fn) {
if(element.addEventListener){
//支持
element.addEventListener(type,fn,false);
}else if(element.attachEvent){
element.attachEvent("on"+type,fn);
}else{
element["on"+type]=fn;
}
}
//为任意的一个元素解绑某个事件:元素,事件类型,事件处理函数
function removeEventListener(element,type,fn) {
if(element.removeEventListener){
element.removeEventListener(type,fn,false);
}else if(element.detachEvent){
element.detachEvent("on"+type,fn);
}else{
element["on"+type]=null;
}
}
/**
* 获取的是页面向上或者向左卷曲出去的距离的值,返回的是对象
* @returns {{top: (Number|number), left: (Number|number)}}
*/
function getScroll() {
return {
top: window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop || 0,
left: window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft||0
};
}
3.在页面中引入JS文件
<script src="common.js"></script>
4.实现方块的随机产生位置
<script>
//产生随机数对象的
(function (window) {
function Random() {
}
Random.prototype.getRandom = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
};
//把局部对象暴露给window顶级对象,就成了全局的对象
window.Random = new Random();
})(window);//自调用构造函数的方式,分号一定要加上
//产生小方块对象
(function (window) {
//console.log(Random.getRandom(0,5));
//选择器的方式来获取元素对象
var map = document.querySelector(".map");
//食物的构造函数
function Food(width, height, color) {
this.width = width || 20;//默认的小方块的宽
this.height = height || 20;//默认的小方块的高
//横坐标,纵坐标
this.x = 0;//横坐标随机产生的
this.y = 0;//纵坐标随机产生的
this.color = color;//小方块的背景颜色
this.element = document.createElement("div");//小方块的元素
}
//初始化小方块的显示的效果及位置---显示地图上
Food.prototype.init = function (map) {
//设置小方块的样式
var div = this.element;
div.style.position = "absolute";//脱离文档流
div.style.width = this.width + "px";
div.style.height = this.height + "px";
div.style.backgroundColor = this.color;
//把小方块加到map地图中
map.appendChild(div);
this.render(map);
};
//产生随机位置
Food.prototype.render = function (map) {
//随机产生横纵坐标
var x = Random.getRandom(0, map.offsetWidth / this.width) * this.width;
var y = Random.getRandom(0, map.offsetHeight / this.height) * this.height;
this.x = x;
this.y = y;
var div = this.element;
div.style.left = this.x + "px";
div.style.top = this.y + "px";
};
//实例化对象
var fd = new Food(20, 20, "green");
fd.init(map);
console.log(fd.x + "====" + fd.y);
})(window);
</script>