JS面向对象笔记二
菜单导航,《JS面向对象笔记一》, 参考书籍:阮一峰之《JavaScript标准参考教程》
七、元素的clientWidth、offsetWidth、scrollWidth的区别
一、构造函数和new命令
1、构造函数
- JavaScript语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)
- 为了与普通函数区别,构造函数名字的第一个字母通常大写,比如: var Person = function(){ this.name = '王大锤'; }
- 构造函数的特点:
a、函数体内部使用了this
关键字,代表了所要生成的对象实例;
b、生成对象的时候,必需用new
命令调用此构造函数
2、new
作用:就是执行构造函数,返回一个实例对象
var Person = function(name, age){ this.name = name; this.age = age; this.email = 'cnblogs@sina.com'; this.eat = function(){ console.log(this.name + ' is eating noodles'); } } var per = new Person('王大锤', 18); console.log(per.name + ', ' + per.age + ', ' + per.email); //王大锤, 18, cnblogs@sina.com per.eat(); //王大锤 is eating noodles
执行new命令时的原理步骤:
- 创建一个空对象,作为将要返回的对象实例
- 将这个空对象的原型,指向构造函数的
prototype
属性 - 将这个空对象赋值给函数内部的
this
关键字 - 开始执行构造函数内部的代码
注意点:当构造函数里面有return关键字时,如果返回的是非对象,new命令会忽略返回的信息,最后返回时构造之后的this对象;
如果return返回的是与this无关的新对象,则最后new命令会返回新对象,而不是this对象。示例代码:

console.log('---- 返回字符串 start ----'); var Person = function(){ this.name = '王大锤'; return '罗小虎'; } var per = new Person(); for (var item in per){ console.log( item + ': ' + per[item] ); } //---- 返回字符串 start ---- //name: 王大锤 console.log('----- 返回对象 start ----'); var PersonTwo = function(){ this.name = '倚天剑'; return {nickname: '屠龙刀', price: 9999 }; } var per2 = new PersonTwo(); for (var item in per2){ console.log(item + ': ' + per2[item]); } //----- 返回对象 start ---- //nickname: 屠龙刀 //price: 9999
new命令执行的内部过程,可以用下面的代码模拟:

/* 第一个参数:constructor 表示构造函数名 后面的params指该构造函数需要传递的参数,可以多个参数 */ function testNew(constructor, params){ //1、将当前函数testNew的所有参数arguments转成数组 var args = [].slice.call(arguments); //2、取出数组args中的第一个元素,也就是构造函数名constructor var constructor = args.shift(); //3、创建一个空对象,继承构造函数的prototype属性 var context = Object.create(constructor.prototype); //4、传入构造函数参数,执行构造函数 constructor.apply(context, args); //5、返回实例化的对象 return context; } //测试 var Person = function(name, age){ this.name = name; this.age = age; this.run = function(){ console.log(this.age + "岁的" + this.name + "正在跑步"); } } var per = testNew(Person, "王大锤", 18); console.log( per ); per.run(); /* Person {name: "王大锤", age: 18, run: ƒ} 18岁的王大锤正在跑步 */
如果调用构造函数的时候,忘记使用new关键字,则构造函数里面的this为全局对象window,属性也会变成全局属性,
则被构造函数赋值的变量不再是一个对象,而是一个未定义的变量,js不允许给undefined添加属性,所以调用undefined的属性会报错。
示例:

var Person = function(){ console.log( this == window ); //true this.price = 5188; } var per = Person(); console.log(price); //5188 console.log(per); //undefined console.log('......_-_'); //......_-_ console.log(per.price); //Uncaught TypeError: Cannot read property 'helloPrice' of undefined
为了规避忘记new关键字现象,有一种解决方式,就是在函数内部第一行加上 : 'use strict';
表示函数使用严格模式,函数内部的this不能指向全局对象window, 默认为undefined, 导致不加new调用会报错

var Person = function(){ 'use strict'; console.log( this ); //undefined this.price = 5188; //Uncaught TypeError: Cannot set property 'helloPrice' of undefined } var per = Person();
另外一种解决方式,就是在函数内部手动添加new命令:

var Person = function(){ //先判断this是否为Person的实例对象,不是就new一个 if (!(this instanceof Person)){ return new Person(); } console.log( this ); //Person {} this.price = 5188; } var per = Person(); console.log(per.price); //5188
二、this关键字

var Person = function(){ console.log('1111'); console.log(this); this.name = '王大锤'; this.age = 18; this.run = function(){ console.log('this is Person的实例对象吗:' + (this instanceof Person) ); console.log(this); } } var per = new Person(); per.run(); /* 打印日志: 1111 Person {} this is Person的实例对象吗:true Person {name: "王大锤", age: 18, run: function} */ console.log('---------------'); var Employ = { email: 'cnblogs@sina.com', name: '赵日天', eat: function(){ console.log(this); } } console.log(Employ.email + ', ' + Employ.name); Employ.eat(); /* 打印日志: --------------- cnblogs@sina.com, 赵日天 Object {email: "cnblogs@sina.com", name: "赵日天", eat: function} */
1、this总是返回一个对象,返回属性或方法当前所在的对象, 如上示例代码
2、对象的属性可以赋值给另一个对象,即属性所在的当前对象可变化,this的指向可变化

var A = { name: '王大锤', getInfo: function(){ return '姓名:' + this.name; } } var B = { name: '赵日天' }; B.getInfo = A.getInfo; console.log( B.getInfo() ); //姓名:赵日天 //A.getInfo属性赋给B, 于是B.getInfo就表示getInfo方法所在的当前对象是B, 所以这时的this.name就指向B.name
3、由于this指向的可变化性,在层级比较多的函数中需要注意使用this。一般来说,在多层函数中需要使用this时,设置一个变量来固定this的值,然后在内层函数中这个变量。
示例1:多层中的this

//1、多层中的this (错误演示) var o = { f1: function(){ console.log(this); //这个this指的是o对象 var f2 = function(){ console.log(this); }(); //由于写法是(function(){ })() 格式, 则f2中的this指的是顶层对象window } } o.f1(); /* 打印日志: Object {f1: function} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} */ //2、上面代码的另一种写法(相同效果) var temp = function(){ console.log(this); } var o = { f1: function(){ console.log(this); //这个this指o对象 var f2 = temp(); //temp()中的this指向顶层对象window } } o.f1(); /* 打印日志 Object {f1: function} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} */ //表示上面两种写法是一样的效果,this的错误演示 //3、多层中this的正确使用:使用一个变量来固定this对象,然后在内层中调用该变量 var o = { f1: function(){ console.log(this); //o对象 var that = this; var f2 = function(){ console.log(that); //这个that指向o对象 }(); } } o.f1(); /* 打印日志: Object {f1: function} Object {f1: function} */
示例2: 数组遍历中的this

//1、多层中数组遍历中this的使用 (错误演示) var obj = { email: '大锤@sina.com', arr: ['aaa', 'bbb', '333'], fun: function(){ //第一个this指的是obj对象 this.arr.forEach(function(item){ //这个this指的是顶层对象window, 由于window没有email变量,则为undefined console.log(this.email + ': ' + item); }); } } obj.fun(); /* 打印结果: undefined: aaa undefined: bbb undefined: 333 */ //2、多层中数组遍历中this的使用 (正确演示,第一种写法) var obj = { email: '大锤@sina.com', arr: ['aaa', 'bbb', '333'], fun: function(){ //第一个this指的是obj对象 var that = this; //将this用变量固定下来 this.arr.forEach(function(item){ //这个that指的是对象obj console.log(that.email + ': ' + item); }); } } obj.fun(); //调用 /* 打印日志: 大锤@sina.com: aaa 大锤@sina.com: bbb 大锤@sina.com: 333 */ //3、多层中数组遍历中this正确使用第二种写法:将this作为forEach方法的第二个参数,固定循环中的运行环境 var obj = { email: '大锤@sina.com', arr: ['aaa', 'bbb', '333'], fun: function(){ //第一个this指的是obj对象 this.arr.forEach(function(item){ //这个this从来自参数this, 指向obj对象 console.log(this.email + ': ' + item); }, this); } } obj.fun(); //调用 /* 打印日志: 大锤@sina.com: aaa 大锤@sina.com: bbb 大锤@sina.com: 333 */
4、关于js提供的call、apply、bind方法对this的固定和切换的用法
1)、function.prototype.call(): 函数实例的call
方法,可以指定函数内部this
的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。
如果call(args)里面的参数不传,或者为null、undefined、window, 则默认传入全局顶级对象window;
如果call里面的参数传入自定义对象obj, 则函数内部的this指向自定义对象obj, 在obj作用域中运行该函数

var obj = {}; var f = function(){ console.log(this); return this; } console.log('....start.....'); f(); f.call(); f.call(null); f.call(undefined); f.call(window); console.log('**** call方法的参数如果为空、null和undefined, 则默认传入全局等级window;如果call方法传入自定义对象obj,则函数f会在对象obj的作用域中运行 ****'); f.call(obj); console.log('.....end.....'); /* 打印日志: ....start..... Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} **** call方法的参数如果为空、null和undefined, 则默认传入全局等级window;如果call方法传入自定义对象obj,则函数f会在对象obj的作用域中运行 **** Object {} .....end..... */
call可以接收多个参数:第一个参数是this所指向的那个对象,后面的参数就是函数调用时需要的参数,模拟简写为:
func.call(thisValue, arg1, arg2, ...); 示例:

var obj = { name: '张三', age: 10, email: '张三@sina.com' } function change(name, age){ this.name = name; this.age = age; console.log(this); } change.call(obj, '王大锤', '28'); //Object {name: "王大锤", age: "28", email: "张三@sina.com"}
2)、function.prototype.apply(): 函数实例的apply方法,和call的作用差不多,也是改变this的指向,调用该函数。
唯一的区别就是,它接受一个数组作为函数执行时的参数,使用格式如下:
func.apply( thisValue, [arg1, arg2, ...] );

var obj = { name: '张三', age: 10, email: '张三@sina.com' } function change(name, age){ this.name = name; this.age = age; console.log(this); } change.apply(obj, ['王大锤', '28']); //Object {name: "王大锤", age: "28", email: "张三@sina.com"}
3)、function.prototype.bind() : bind方法主要用于将函数体内的this绑定到某个对象,然后返回新函数。
先来个简单示例,展示普通将方法赋值给变量,和使用bind()绑定的区别:

var d = new Date(); //第一次:将getTime方法赋值给print变量 var print = d.getTime; try{ print(); //执行时报错,进入异常处理 }catch(e){ console.log('报错:' + e.message); //捕捉异常 //打印结果:报错:this is not a Date object. } //第二次赋值:bind()应用,bind方法将getTime方法内部的this绑定到d对象,这时就可以安全的将这个方法赋值给其他变量 print = d.getTime.bind(d); print(); //1498740682340

var obj = { count:0, run: function(){ console.log(this); this.count++; } } //1、对象直接调用自己方法,run里面的this指向obj对象 obj.run(); console.log( obj.count ); /* 打印日志: Object {count: 0, run: function} 1 */ //2、将对象方法赋值给变量,这时run里面的this指向了顶层对象window var run2 = obj.run; run2(); //执行obj.count不会变化,创建了全局变量count=undefined console.log( obj.count ); //值未变化 console.log( count ); //undefined++ 等于NaN /* 打印日志: Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} 1 NaN */ //3、应用bind方法将run方法内部的this, 绑定到obj对象。 var run2 = obj.run.bind(obj); run2(); console.log( obj.count ); //值有变化了 /* 打印日志: Object {count: 1, run: function} 2 */
bind除了绑定this外,还可以绑定原函数的参数

var add = function(x, y){ return x * this.m + y * this.n; } var obj = { m: 2, n: 2}; var newAdd = add.bind(obj, 5); console.log( newAdd(5) ); //20 //另一种写法 var newAdd2 = add.bind(obj, 5, 5); console.log( newAdd2() ); //20
bind方法使用注意点:
a、bind方法没执行一次,都返回一个新函数。所以在我们日常进行绑定监听事件的时候要特别注意:
举个列子,给某个标签绑定点击事件:

/* 给element标签绑定和单击事件,和取消绑定单击事件 //1、第一种绑定和取消绑定方法 element.addEventListener('click', o.m.bind(o)); //绑定 element.removeEventListener('click', o.m.bind(o)); //取消绑定事件 //2、第二种绑定和取消绑定方法 var listenter = o.m.bind(o); element.addEventListener('click', listenter); //绑定 element.removeEventListener('click', listenter);//取消绑定 //说明:第一种绑定的事件是不能取消绑定的,为什么,因为这种绑定bind方法生成的一个匿名函数 //第二种绑定的事件才能取消绑定 */
b、将包含this的方法直接当做回调函数

var obj = { name: '王大锤', arr: ['aa', 'bb', 'cc'], print: function(){ this.arr.forEach(function(n){ console.log( n + ':' + this.name ); //这个this指向window }); }, print2: function(){ this.arr.forEach(function(n){ console.log( n + ':' + this.name ); //这个this指向obj }.bind(this)); } } obj.print(); /* 打印结果:此时print里面的forEach里面的this指向顶层对象window aa: bb: cc: */ obj.print2(); /* 打印结果:通过bind绑定this aa:王大锤 bb:王大锤 cc:王大锤 */
三、prototype对象
先看关于原型对象的一张经典图片,其次要了解prototype和__proto__的区别
配合上图,相关的代码如下:
var Foo = function(){ } var f1 = new Foo(); var f2 = new Foo(); var o1 = new Object(); var o2 = new Object(); var isTrue1 = Foo.prototype === f1.__proto__, isTrue2 = Foo.prototype === f2.__proto__, isTrue3 = Object.prototype === o1.__proto__, isTrue4 = Object.prototype === o2.__proto__, isTrue5 = Foo.prototype.__proto__ === Object.prototype, isTrue6 = f1.__proto__.__proto__ === Object.prototype, isTrue7 = f1.__proto__.__proto__.__proto__ === Object.prototype.__proto__, isTrue8 = f1.__proto__.__proto__.__proto__ === null; console.log(isTrue1 + ", " + isTrue2 + ", " + isTrue3 + ", " + isTrue4 + ", " + isTrue5 + ", " + isTrue6 + ", " + isTrue7 + ", " + isTrue8); //true, true, true, true, true, true, true, true var isTrue9 = Foo.prototype.constructor === Foo; var isTrue10 = f1.constructor === Foo; var isTrue11 = Foo.prototype.__proto__ === Object.prototype; var isTrue12 = Foo.prototype.__proto__.__proto__ === null; var isTrue13 = Foo.__proto__ === Function.prototype; /*Foo.constructor这里把Foo看成是Function构造函数的一个实例对象 所以构造函数Function的对象Foo的constructor 指向原构造函数Function */ var isTrue14 = Foo.constructor === Function; /* Function要特别理解一下: Function.constructor === Function === Function.prototype.constructur 当调用Function.constror时这时把Function看做是构造函数Function的一个实例对象, 即构造函数是Function, 实例对象也是Function, 然后对象Function的属性constructor指向构造函数Function */ var isTrue15 = Function.constructor === Function; var isTrue16 = Function.constructor === Function.prototype.constructor; console.log(isTrue9 + ", " + isTrue10 + ", " + isTrue11 + ", " + isTrue12 + ", " + isTrue13 + ", " + isTrue14 + " ," + isTrue15 + ", " + isTrue16); //true, true, true, true, true, true ,true, true
1、在JS里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)也是对象。
2、对象具有属性__proto__,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,
这也保证了实例能够访问在构造函数原型中定义的属性和方法。
比如上图例子: f1.__proto__, f2.__proto__ , Foo.prototype这三个对象指向同一个对象。
3、方法(Function): 方法这个特殊的对象,除了和其他对象一样有上述_proto_属性之外,
还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,
这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。
原型对象也有一个属性,叫做constructor,这个属性包含了一个指针prototype,指回原构造函数。
比如上图例子:Foo.prototype.constructor == Foo
总结:1)、对象有属性__proto__,指向该对象的构造函数的原型对象;
2)、方法除了有属性__proto__,还有属性prototype,prototype指向该方法的原型对象。
(方法调用__proto__属性的时候,其实是可看做把该方法当做构造函数Function的一个实例对象来使用;
调用prototype则是把该方法当做一个构造函数来使用)
配合上图和相关代码,我们看到:
4、构造函数Foo()的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。
5、原型对象Foo.prototype有一个指针constructor指回构造函数。
6、f1和f2是Foo这个对象的两个实例,这两个对象也有属性__proto__,指向构造函数的原型对象,这样子就可以访问原型对象的所有方法了。
7、构造函数Foo()除了是方法,也是对象,也有__proto__属性,指向它的构造函数的原型对象。函数的构造函数是Function嘛,因此这里的__proto__指向了Function.prototype。
其实除了Foo(),Function(), Object()也是一样的道理。
8、原型对象也是对象,它的__proto__属性指向它的构造函数的原型对象。这里是Object.prototype。
最后,Object.prototype的__proto__属性指向null。终于走到大结局,null没有自己的原型对象。
Object和Function的关联:
/* Object和Function的关系: 1、Function.prototype.__proto__ === Object.prototype 2、Object.__proto__ === Function.prototype 3、Object.constructor === Function */ //Function.prototype是可看做是构造函数Object的一个实例对象,原型指向Object
/*
不过Function.prototype和其他如Array, String, Number,还有自定义构造函数的prototype有点不一样
typeof Function.prototype === "function"
其他如Array, String, Number, 还有自定义构造函数的typeof都是"object"
*/ var isTrue17 = Function.prototype.__proto__ === Object.prototype; //Object.__proto__ 这里的Object可看做是构造函数Function的一个实例对象 var isTrue18 = Object.__proto__ === Function.prototype; //Object.constructor 这里的Object可看做是构造函数Function的一个实例对象 var isTrue19 = Object.constructor === Function; console.log(isTrue17 + ", " + isTrue18 + ", " + isTrue19); //true, true, true
原型对象的属性为所有实例对象所共有,修改原型对象属性的值,变动体现在所有的实例对象上;
当实例对象有与原型对象同名属性,则优先寻找实例对象自有属性,当没找到对象自有属性时,才会去原型对象去寻找该属性。
举个例子: 第一步,先创建一个构造函数,再实例化两个对象
var Person = function(name){ this.name = name; } var per1 = new Person('王大锤'); var per2 = new Person('艾边城'); console.log( per1 ); console.log( per2 );
展开日志截图所示:
第二步:通过构造函数的原型对象添加一个属性Color
Person.prototype.Color = "white";
console.log(per1);
展开日志,看到在实例对象的构造函数里面多了一个所有该构造函数实例对象所共享的属性Color
第三步:给per1对象设置Color属性
per1.Color = "Green";
console.log( per1 );
console.log( per2 );
四、对象的Copy, 和面向对象三大特性模拟(继承,多态,封装)
1、拷贝对象,需要满足以下两个条件:
- 拷贝后的对象,与原对象具有同样的prototype原型对象。
- 拷贝后的对象,与原对象具有同样的属性。
对象拷贝的函数,和示例测试code

function copyObject(orig) { //创建要复制的对象 var copy = Object.create(Object.getPrototypeOf(orig)); copyOwnPropertiesFrom(copy, orig); return copy; } //拷贝对象的属性 function copyOwnPropertiesFrom(target, source) { Object .getOwnPropertyNames(source) .forEach(function(propKey) { var desc = Object.getOwnPropertyDescriptor(source, propKey); Object.defineProperty(target, propKey, desc); }); return target; } //测试code var Student = function(name, age, otherInfo){ this.name = name; this.age = age; this.otherInfo = otherInfo; this.run = function(){ console.log(this.name + "正在 run..."); } } var stu = new Student("大锤", 18, {sports:["篮球", "跑步", "游泳"]}); var stu2 = stu; var stu3 = copyObject(stu); stu.name = "张三"; console.log(stu.name + ", " + stu2.name + ", " + stu3.name); console.log((stu==stu2) + ", " + (stu == stu3) + ", " + (stu2==stu3)); /* 张三, 张三, 大锤 true, false, false */
2、js面向对象之继承特性体现,让一个构造函数继承另外一个构造函数:
1)、在子类的构造函数中,调用父类构造函数
2)、让子类的原型指向父类的原型,这样子类原型继承了父类原型
多态的模拟则是重写子构造函数的继承自父构造函数的方法
示例演示

//1、继承 //第一步,创建构造函数 function Shape(){ this.x = 2; this.y = 2; console.log("hello, I'm Shape constructor !"); } Shape.prototype.move = function(x, y){ this.x += x; this.y += y; console.log("shape move, this.x=" + this.x + ", this.y=" + this.y); } //定义子类,继承父类 function Rectangle(){ Shape.call(this); //调用父类构造函数 console.log("哈喽,I'm Rectangle's constructor"); } //另外一种子类继承父类的写法 function RectangleTwo(){ this.base = Shape; this.base(); console.log("hello, I'm RectangleTwo's constructor..."); } function RectangleThree(){ console.log("Hello, I'm RectangleThree's constructor..."); } //第二步,子类继承父类的原型 Rectangle.prototype = Object.create(Shape.prototype); Rectangle.prototype.constructor = Rectangle; RectangleTwo.prototype = Object.create(Shape.prototype); RectangleTwo.prototype.constructor = RectangleTwo; RectangleThree.prototype = new Shape(); RectangleThree.prototype.constructor = RectangleThree; //测试结果 var rect1 = new Rectangle(); rect1.move(1, 1); console.log(rect1 instanceof Rectangle); console.log(rect1 instanceof Shape); /* hello, I'm Shape constructor ! 哈喽,I'm Rectangle's constructor shape move, this.x=3, this.y=3 true true true */ var rect2 = new RectangleTwo(); rect2.move(2, 2); console.log(rect2 instanceof RectangleTwo); console.log(rect2 instanceof Shape); console.log(rect2 instanceof Object); console.log(rect2 instanceof Rectangle); /* hello, I'm Shape constructor ! hello, I'm RectangleTwo's constructor... shape move, this.x=4, this.y=4 true true true false */ var rect3 = new RectangleThree(); rect3.move(3, 3); console.log(rect3 instanceof RectangleThree); console.log(rect3 instanceof Shape); console.log(rect3 instanceof Object); console.log(rect3 instanceof Rectangle); console.log(rect3 instanceof RectangleTwo); /* Hello, I'm RectangleThree's constructor... shape move, this.x=5, this.y=5 true true true false false */ //2、多态的体现 Shape.prototype.info = function(){ console.log("Shape's x = " + this.x + ", y = " + this.y); } Rectangle.prototype.info = function(){ console.log("Rectangle's x = " + this.x + ", y = " + this.y); } RectangleThree.prototype.info = function(){ console.log("RectangleThree's x = " + this.x + ", y = " + this.y); } //测试: Rectangle和RectangleThree子构造函数重写了原型的info方法,RectangleTwo没有重写,继承自Shape的info方法 var shape = new Shape(); shape.info(); // Shape's x = 2, y = 2 rect1.info(); //Rectangle's x = 3, y = 3 rect2.info(); //Shape's x = 4, y = 4 rect3.info(); //RectangleThree's x = 5, y = 5
3、对象封装的简单模拟

//3、对象的封装:隐藏细节,对外暴露需要的属性和方法 var person = (function(){ var obj = new Object(); obj.name = "大锤"; var perAge = 18; var perRun = function(){ console.log(this.name + "... 正在run..."); } var perJump = function(){ console.log(this.name + "今年" + perAge + "....正在jump"); } obj.run = perRun; obj.jump = perJump; return obj; })(); //测试, person对象对外公开的name, run, jump可以访问,其他内部的perAge 和perRun等不能访问 console.log( person.name ); //大锤 person.run(); //大锤... 正在run... person.jump(); //大锤今年18....正在jump person.name = "大锤三"; person.run(); //大锤三... 正在run...
五、异步执行之Ajax和Promise
1、传统的异步操作Ajax, 示例写法:

function testAjax(keyword, onloadBlock, onerrorBlock){ var xhr = new XMLHttpRequest(); var url = "http://www.cnblogs.com/tandaxia/p/7079214.html?search=" + keyword; xhr.open('GET', url, true); xhr.onload = function(e){ console.log(e); console.log(this); console.log("\n************ start ********\n"); if (this.status == 200){ onloadBlock(this.responseText); //处理服务器返回的结果 } } xhr.onerror = function(e){ onerrorBlock(e); } xhr.send(); //发送请求 } //测试调用 testAjax('hello world', console.log, console.error);
2、Promise实现的异步操作,区别与Ajax的,它的异步任务立刻返回一个Promise对象,使得程序具备正常的同步运动的流程,回调函数不必要一层层嵌套。
Promise对象只有三种状态:未完成(pending)、已完成(resolved)、失败(rejected)
操作的状态变化途径只有两种:未完成 --> 已完成;
未完成 --> 失败

function testPromise(keyword){ var xhr = new XMLHttpRequest(); var url = "http://www.cnblogs.com/tandaxia/p/7079214.html?search=" + keyword; var proObj = new Promise(function(resolveBlock, rejectBlock){ xhr.open('GET', url, true); xhr.onload = function(e){ if (this.status == 200){ resolveBlock(this.responseText); } } xhr.onerror = function(e){ rejectBlock(e); } xhr.send(); //发送请求 }); return proObj; //返回Promise对象 } //测试调用 testPromise('hello world').then(console.log, console.error);
六、DOM对象
1、基本概念
1.1、DOM : DOM是JavaScript操作网页的接口,全称“文档对象模型”(Document Object Model)。
它的作用是将网页转为一个JavaScript对象,从而可以用脚本进行各种操作。
浏览器根据DOM模型,将结构化文档(比如HTML和XML)解析成一系列的节点,再有这些节点组成一个树状结构(DOM Tree)。
所有的节点和最终的树状结构,都有规范的对外接口。所以,DOM可以理解成网页的编程接口。
1.2、节点:DOM的最小组成单位叫做节点(Node)。文档的树形结构(DOM树),就是由各种不同类型的节点组成。
节点的类型有七种:
a、Document: 整个文档树的顶层节点
b、DocumentType: doctype标签(比如<!DOCTYPE html>)
c、Element: 网页的各种HTML标签(比如<body>、<a>等)
d、Attribute: 网页元素的属性(比如class="right")
e、Text: 标签之间或标签包含的文本
f、Comment: 注释
g、DocumentFragment: 文档的片段
这七种节点都属于浏览器原生提供的节点对象的派生对象,具有一些共同的属性和方法。
1.3、节点树
一个文档的所有的节点,按照所在的层级,可以抽象成一种树状结构。这种树状结构就是DOM。
最顶层的节点就是document节点,它代表了整个文档。文档里面最高一层的HTML标签,一般是<html>,它构成树结构的根节点(root node),
其他html标签节点都是它的下级。
除了根节点以外,其他节点对于周围的节点都存在三种关系:
父节点关系(parentNode) : 直接的那个上级节点;
子节点关系(childNodes) : 直接的下级节点;
同级节点关系(sibling) : 拥有同一个父节点的节点
DOM提供操作接口,用来获取三种关系的节点。其中,子节点接口包括firstChild(第一个节点)和lastChild(最后一个节点)等属性,
同级节点接口包括nextSibling(紧邻在后的那个同级节点)和previousSibling(紧邻在前的那个同级节点)属性。
七、元素的clientWidth、offsetWidth、scrollWidth的区别
1、clientWidth和clientHeight: 表示元素节点的CSS宽度和高度, 返回一个整数值,
只对块级元素有效,对于行内元素返回0;
包括元素内容+padding部分,不包含border和margin, 如果有滚动条,要减去滚动条的尺寸;
document.documentElement的clientHeight属性,返回当前视口的高度(即浏览器窗口的高度),
等同于window.innerHeight属性减去水平滚动条的高度;
document.body的高度则是网页的实际高度。
一般来说,document.body.clientHeight大于document.documentElement.clientHeight
2、clientLeft表示元素节点左边框的宽度,不包括左侧的padding和margin;
clientTop表示网页元素的顶部边框的宽度
3、offsetWidth和offsetHeight: 表示元素节点的水平宽度和垂直高度,
包括元素本身的宽度或高度、padding和border, 以及水平滚动条的尺寸
4、offsetLeft表示元素左上角相对于最近的offsetParent节点(定位父级标签,即position不为static)的水平位移;
offsetTop返回垂直位移
5、scrollWidth和scrollHeight:表示当前元素的总宽度和总高度,
包括溢出容器、当前不可见的部分,包括padding,
不包括boder、margin以及水平滚动条和垂直滚动条的高度。
6、scrollLeft表示当前元素的水平滚动条向右侧滚动的像素数量,
scrollTop表示当前元素的垂直滚动向下滚动的像素数量。
对于没有滚动条的网页元素的这两个属性的值为0。
- 测试一下, 测试代码见下面(样式和js写在行内,复制粘贴即可看到效果)
- 鼠标事件属性
- offsetX和offsetY: 鼠标距标签左边和顶部的距离,即鼠标在当前标签中的xy坐标(不包含边框,即以填充区域的左上角为原坐标)
- x和y: 鼠标在浏览器可视区域距离左边和顶部的间距
- clientX和clientY: 意义和x和y一样,表示浏览器可视区域距离左边和顶部的间距
- pageX和pageY: 浏览器页面中距离左边和顶部的间距,包括滚动条区域,即包含隐藏在滚动条前面的左边和顶部的空间
- screenX和screenY: 鼠标在屏幕中距离左边和顶部的间距
- 当前标签属性
-
offsetLeft和offsetTop: 标签在浏览器中距离最近offsetParent标签(定位父级,即position不等于static)的左边和顶部的间距
1、元素自身有fixed定位,offsetParent的结果为null;
2、元素自身无fixed定位,且父级元素都未经过定位,offsetParent的结果为body标签
3、元素自身无fixed定位,且父级元素存在经过定位的元素,offsetParent的结果为离自身元素最近的经过定位的父级元素
4、body元素的offsetParent是null - offsetWidth和offsetHeight: 标签content+padding+border的宽度和高度(即内容+填充+边框)
- clientLeft和clientTop: 标签的左边边框和顶部边框的尺寸
- clientWidth和clientHeight: 标签content+padding的宽度和高度(即内容+填充)
- scrollLeft和scrollTop: 标签往左边或顶部滚动的距离(当有滚动条,且向左或向上有滚动距离时才有值)
- scrollWidth和scrollHeight: 表示当前元素的总宽度和总高度,
包括溢出容器、当前不可见的部分,padding;不包括boder、margin以及水平滚动条和垂直滚动条的高度
-
offsetLeft和offsetTop: 标签在浏览器中距离最近offsetParent标签(定位父级,即position不等于static)的左边和顶部的间距

<div style="margin: 500px 0;"> <ul style="color: #666;position: relative; height: 360px;"> <li>标签样式设置: <br/> 子级标签width=560px, height=300px, border=10px, padding=20px; <br/> 父级标签width=2000px, height=360px, border=2px, padding=10px; <br/> = (560+20*2+10*2)*3 + 140 = 1860 + 140 </li> <li style="float: left; padding: 10px; width: 460px; border: 1px dashed gray; ">鼠标事件属性 <ul> <li>offsetX和offsetY: 鼠标距<span style="color:red;">标签</span>左边和顶部的距离,即鼠标在当前标签中的xy坐标(不包含边框,即以<span style="color:red;">填充区域</span>的左上角为原坐标)</li> <li>x和y: 鼠标在浏览器<span style="color:red;">可视区域</span>距离左边和顶部的间距</li> <li>clientX和clientY: 意义和x和y一样,表示浏览器<span style="color:red;">可视区域</span>距离左边和顶部的间距</li> <li>pageX和pageY: 浏览器<span style="color:red;">页面</span>中距离左边和顶部的间距,包括滚动条区域,即包含隐藏在滚动条前面的左边和顶部的空间</li> <li>screenX和screenY: 鼠标在<span style="color:red;">屏幕</span>中距离左边和顶部的间距</li> </ul> </li> <li style="float: left; margin-left: 30px; border: 1px dashed black; padding: 10px; width: 600px;">当前标签属性 <ul> <li> offsetLeft和offsetTop: 标签在浏览器中距离<span style="color:red;">最近offsetParent标签(定位父级,即position不等于static)</span>的左边和顶部的间距<br/> 1、元素自身有fixed定位,offsetParent的结果为null;<br/> 2、元素自身无fixed定位,且父级元素都未经过定位,offsetParent的结果为body标签<br/> 3、元素自身无fixed定位,且父级元素存在经过定位的元素,offsetParent的结果为离自身元素最近的经过定位的父级元素<br/> 4、body元素的offsetParent是null </li> <li>offsetWidth和offsetHeight: <span style="color:red;">标签content+padding+border的宽度和高度(即内容+填充+边框)</span></li> <li>clientLeft和clientTop: <span style="color:red;">标签的左边边框和顶部边框的尺寸</span></li> <li>clientWidth和clientHeight: <span style="color:red;">标签content+padding的宽度和高度(即内容+填充)</span></li> <li>scrollLeft和scrollTop: 标签往左边或顶部<span style="color:red;">滚动</span>的距离(当有滚动条,且向左或向上有滚动距离时才有值)</li> <li>scrollWidth和scrollHeight: 表示当前元素的总宽度和总高度,<br/> 包括溢出容器、当前不可见的部分,padding;不包括boder、margin以及水平滚动条和垂直滚动条的高度 </li> </ul> </li> </ul> <div style="clear:both;"></div> <div style="width: 2000px; height: 360px; display: flex; border: 2px dashed red; padding: 10px; "> <div onmousemove="this.addEventListener('mousemove', function(e){ var e = e||window.event;var ele=this; var html = '鼠标信息: <br/>offsetX: ' + e.offsetX + ', offsetY: ' + e.offsetY + '<br/>x: '+ e.x +', y: '+ e.y +'<br/>clientX: ' + e.clientX +', clientY: ' + e.clientY + '<br/>pageX: ' + e.pageX + ', pageY: ' + e.pageY+'<br/>screenX: ' + e.screenX + ', screenY: ' + e.screenY + '<br/>当前标签信息:<br/>offsetLeft: ' + ele.offsetLeft +', offsetTop: ' + ele.offsetTop +', offsetWidth: ' + ele.offsetWidth + ', offsetHeight: ' +ele.offsetHeight +'<br/>clientLeft: ' + ele.clientLeft +', clientTop: ' + ele.clientTop +', clientWidth: ' + ele.clientWidth + ', clientHeight: ' +ele.clientHeight +'<br/>scrollLeft: ' + ele.scrollLeft +', scrollTop: ' + ele.scrollTop +', scrollWidth: ' + ele.scrollWidth + ', scrollHeight: ' +ele.scrollHeight +'<br/><br/>';this.innerHTML = html; })" style="width: 560px; height: 300px; padding: 20px; display: flex; flex-direction: column; justify-content: center; border: 10px dashed blue;background-color: #ffffcc;">1</div> <div onmousemove="this.addEventListener('mousemove', function(e){ var e = e||window.event;var ele=this; var html = '鼠标信息: <br/>offsetX: ' + e.offsetX + ', offsetY: ' + e.offsetY + '<br/>x: '+ e.x +', y: '+ e.y +'<br/>clientX: ' + e.clientX +', clientY: ' + e.clientY + '<br/>pageX: ' + e.pageX + ', pageY: ' + e.pageY+'<br/>screenX: ' + e.screenX + ', screenY: ' + e.screenY + '<br/>当前标签信息:<br/>offsetLeft: ' + ele.offsetLeft +', offsetTop: ' + ele.offsetTop +', offsetWidth: ' + ele.offsetWidth + ', offsetHeight: ' +ele.offsetHeight +'<br/>clientLeft: ' + ele.clientLeft +', clientTop: ' + ele.clientTop +', clientWidth: ' + ele.clientWidth + ', clientHeight: ' +ele.clientHeight +'<br/>scrollLeft: ' + ele.scrollLeft +', scrollTop: ' + ele.scrollTop +', scrollWidth: ' + ele.scrollWidth + ', scrollHeight: ' +ele.scrollHeight +'<br/><br/>';this.innerHTML = html; })" style="width: 560px; height: 300px; padding: 20px; display: flex; flex-direction: column; justify-content: center; border: 10px dashed red;background-color: #a6e1ec;">2</div> <div onmousedown="this.addEventListener('mouseup', function(e){var e = e||window.event;var ele=this; var html = '鼠标信息: <br/>offsetX: ' + e.offsetX + ', offsetY: ' + e.offsetY + '<br/>x: '+ e.x +', y: '+ e.y +'<br/>clientX: ' + e.clientX +', clientY: ' + e.clientY + '<br/>pageX: ' + e.pageX + ', pageY: ' + e.pageY+'<br/>screenX: ' + e.screenX + ', screenY: ' + e.screenY + '<br/>当前标签信息:<br/>offsetLeft: ' + ele.offsetLeft +', offsetTop: ' + ele.offsetTop +', offsetWidth: ' + ele.offsetWidth + ', offsetHeight: ' +ele.offsetHeight +'<br/>clientLeft: ' + ele.clientLeft +', clientTop: ' + ele.clientTop +', clientWidth: ' + ele.clientWidth + ', clientHeight: ' +ele.clientHeight +'<br/>scrollLeft: ' + ele.scrollLeft +', scrollTop: ' + ele.scrollTop +', scrollWidth: ' + ele.scrollWidth + ', scrollHeight: ' +ele.scrollHeight +'<br/><br/>';document.getElementById('tanShowInfo').style.display = 'flex';document.getElementById('tanShowMain').innerHTML = html;var html2 = html.replace(/<br\/>/g, '\n');console.log(html2);})" style="width: 560px; height: 300px; padding: 20px; display: flex; flex-direction: column; justify-content: center; border: 10px dashed black;background-color: yellow;overflow: scroll;" id="super12_three"> <img style="width: 900px; margin:0;padding:0;" src="https://img2018.cnblogs.com/blog/454511/201812/454511-20181201112207098-447232588.jpg"/> </div> </div> <div id="tanShowInfo" style="position: fixed; top: 0; right: 0; width: 600px; height: 260px; display: none; align-items: center; padding-left: 30px;background-color: #fff; z-index: 99; border: 5px dashed gray; color: #666;"> <div id="tanShowMain" style="padding-top: 20px;"></div> <div onclick="document.getElementById('tanShowInfo').style.display = 'none';" style="position: absolute; left: 0; top: 0; width: 30px; height: 30px; border-radius: 30px;background-color: black; color: #fff; line-height: 30px; cursor: pointer; text-align: center; font-size: 20px;">X</div> </div> </div>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?