JS面向对象笔记二

一、构造函数和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命令时的原理步骤:

  1. 创建一个空对象,作为将要返回的对象实例
  2. 将这个空对象的原型,指向构造函数的prototype属性
  3. 将这个空对象赋值给函数内部的this关键字
  4. 开始执行构造函数内部的代码

注意点:当构造函数里面有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__ , Function.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表示元素左上角相对于父节点的水平位移;

    offsetTop返回垂直位移

  5、scrollWidth和scrollHeight:表示当前元素的总宽度和总高度,

    包括溢出容器、当前不可见的部分,包括padding, 

    不包括boder、margin以及水平滚动条和垂直滚动条的高度。

  6、scrollLeft表示当前元素的水平滚动条向右侧滚动的像素数量,

    scrollTop表示当前元素的垂直滚动向下滚动的像素数量。

    对于没有滚动条的网页元素的这两个属性的值为0。

原文链接:http://www.cnblogs.com/tandaxia/p/7079214.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏lzj_learn_note

4-字符串

字段宽度是转换后的值保留的最小字符个数,精度是数字转换结果中应该包含的小数位数或字符串转换后的值所能包含的最大字符个数。

9630
来自专栏从流域到海域

Python set(集合) 这一定是最全的介绍集合的博文

Python的set是一个无序且无重复元素的集合,概念上相当于数学上的无序集,数据结构上相当于dict的键。 既然set是集合,则必然可以实现并、交、...

24750
来自专栏柠檬先生

JavaScript 基础(一)

基本语法: 区分大小写:       ECMAScript 中的一切(变量,函数名和操作符)都区分大小写。 标识符:     表示符就是指,变量,函数,属性...

220100
来自专栏GreenLeaves

JavaScript之获取和设置元素属性

1.与我前面的随笔获取元素的那些方法不同http://www.cnblogs.com/GreenLeaves/p/5689075.html 获取元素属性的方法g...

228100
来自专栏Golang语言社区

Go语言interface详解

interface Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计...

37990
来自专栏雪胖纸的玩蛇日常

老男孩Python全栈开发(92天全)视频教程 自学笔记06

21460
来自专栏HTML5学堂

获取对象具体类型的功能函数

HTML5学堂:JavaScript当中,时常会使用到typeof来进行数据类型的检测,但是我们觉得typeof不能够满足我们的需求,对于数组、函数、时间对象等...

33370
来自专栏轮子工厂

2. C语言 -- printf 的花式操作

(。・∀・)ノ゙嗨!大家好,我是呆博~很开心可以在这里给接着大家分享我的 C 语言学习笔记~因为微信对于代码块的支持并不是很好,所以代码部分以截图形式呈现,如果...

42570
来自专栏行者常至

004.golang 常量与运算符

Go虽然保留了指针,但与其它编程语言不同的是,在Go当中不 支持指针运算以及”->”运算符,而直接采用”.”选择符来操作指针 目标对象的成员

11710
来自专栏Golang语言社区

Go语言interface详解

interface Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计...

37670

扫码关注云+社区

领取腾讯云代金券