对象

js 没有类,只有对象,万物皆对象

数据和行为封装在一起,就是对象.

创建

  • 直接创建
var obj = {
    name:"yy"
    age:1
    introduce: function(){ };
}
  • 构造函数方式
var Obj = funciton(){ //1字母大写,非强制
    this.name = "yy";  //2含this
    this.age = 1;
    this.introduce= function(){};
    // no return,如果返回的是对象,则返回new出来的也是返回的对象
}
var obj = new Obj(); //3 new出来

使用 new 命令时,它后面的函数依次执行下面的步骤。

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

this

this 就是属性或方法“当前”所在的对象,当前意味着 this 可变.

到底怎么来的?

对象是内存里的k-v组成
实际上k又是包含在属性描述对象里,所以对象本质上是由很多的属性描述对象构成.
当属性的值是函数时,实际就是c里的1个函数地址.
函数在不同的环境()下,可理解为传入的参数可以不同,有不同的表现!
this它在js里的设计目的就是在函数体内部,指代函数当前的运行环境.
这个环境也是个对象,即当前方法所在的对象.

带 this 的就是构造函数?No,也许是希望 f 被不同的对象引用时,this 指向不同的对象:

function f() {
  return "姓名:" + this.name;
}

this 环境总结:

1 全局环境使用this,它指的就是顶层对象window。
2 构造函数中的this,指的是实例对象。
3 如果对象的方法里面包含this,this的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。

方法运行时所在的对象是重点,划出来要考...

情况 3 的例子

var A = {
  name: "张三",
  describe: function() {
    return "姓名:" + this.name;
  }
};

A.descire(); //'张三'

var name = "李四";
var f = A.describe; //f在顶层,因此describe里的this也指向顶层,
f()(
  // this.name = '李四'.

  A.describe
)(); //同理,'李四'
var A = {
  p: "张三",
  m: {
    info: function() {
      console.log(this.p);
    },
    p: "李四"
  }
};

A.m.info(); // ‘李四’,info所在的运行时对象为A.m

再来个例子:

var o = {
  f1: function () {
    console.log(this);
    var f2 = function () {
      console.log(this); //本this运行时所在的对象为f2,为1个临时对象,临时对象
      //都可以理解为在顶层创建的?
    }();
  }
}

o.f1() // Object Window

//等效于
var temp = function(){console.log(this);}
var o = {
  f1:unction(){
    console.log(this);
    var f2 = temp();
    } //不变

}

解决办法:保存第 2 层的 temp this,用的非常多

var o = {
  f1: function() {
    console.log(this);
    var that = this;
    var f2 = (function() {
      console.log(that);
    })();
  }
};

o.f1();

再来个例子:

var o = {
  v: "hello",
  p: ["a1", "a2"],
  f: function() {
    var that = this;
    this.p.forEach(function(item) {
      console.log(that.v + " " + item);
    });
  }
};
o.f(); //hello hello

一句话: 对象方法想把this传给自己的子函数,就先保存我自己变成that,后代再用that.

绑定 this

call: func.apply(thisValue, arg1,arg2,...)
apply: func.apply(thisValue, [arg1, arg2, ...])
bind: func.bind(thisValue,arg1,arg2), 返回新的函数, 添加新的参数, 将func的运行环境设置为了thisValue.

fn.call(obj,a1,a2...)

apply 和 call 的唯一区别:apply 的参数传入可以是数组.

thisValue 就是改变调用者的 this 环境.

  • slice 的例子
var a = [1, 2, 3];
a.slice(); //[1,2,3],不指定start,end,输出不变
var args = Array.prototype.slice.call(arguments); //arguments转数组. arguments

本质可以可理解为:

Array.prototype.slice = function(start, end) {
  var result = new Array();
  start = start || 0;
  end = end || this.length; //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键
  for (var i = start; i < end; i++) {
    result.push(this[i]);
  }
  return result;
};
  • 其他
//1
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a); // 15

//2
Array.apply(null, ["a", , "b"]); // [ 'a', undefined, 'b' ]

//3
var o = new Object();
o.f = function() {
  console.log(this === o); //希望this指向o
};
$("#button").on("click", o.f.apply(o));
  • bind 例子
var counter = {
  count: 0,
  inc: function() {
    this.count++;
  }
};

var func = counter.inc.bind(counter);
func();
counter.count; // 1

继承

原型对象 prototype

function 才有 prototype 属性,叫做原型对象

构造函数的 prototype 属性才有意义,被其实例化的对象,共享 prototype 的属性和方法。这也是它被称为原型对象的原因.

function Cat() {
  this.age = 12;
}
Cat.prototype.color = "red";

c1 = new Cat();
c2 = new Cat();
c1.color; // red 共享了原属性
c2.color; // red
Cat.prototype.color = "blue";
c1.color; // blue
c1.color = "red"; // 设置实例对象本身的color属性,优先级最高,覆盖原原型对象的属性
c2.color; //bule

原型对象是 js 继承的基础,因为会共享所有原型对象的属性,形成链式继承.

instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回 true。

注意,instanceof 运算符只能用于对象,不适用原始类型的值

a = new Array();
a instanceof Array; //true
a instanceof Object; //true
s = "abc";
s instanceof Array; //false 只适应于对象

construtor

constructor 是 prototype 的1个属性,指向原型对象所在的的构造函数.

function P() {}
var p = new P();
p.constructor === P; // true
p.constructor === P.prototype.constructor; // true
p.hasOwnProperty("constructor"); // p没有constructor属性,来自prototype

var pp = new p.constructor(); //通过实例,而不是构造函数来创建新对象也是可以的.

直接更新对象的方式来修改原型对象时,一般要同时修改 constructor 属性的指向。

function Person(name) {
  this.name = name;
}
Person.prototype.constructor === Person; // true
Person.prototype = {
  a: function() {}
};
Person.prototype.constructor === Person; // false
Person.prototype.constructor === Object; // constructor来自普通对象

所以一般不要像上面那样更新 prototype,而是:

C.prototype.method1 = function (...) { ... };

子类继承父类的写法

// 第一步,子类继承父类的实例
function Rectangle() {
  Shape.call(this); // 调用父类构造函数
}
// 另一种写法
function Rectangle() {
  this.base = Shape;
  this.base();
}
// 第二步,子类继承父类的原型
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

如果只是继承某个方法,(并无实际作用感觉,破坏了对象的继承原则)

ClassB.prototype.print = function() {
  ClassA.prototype.print.call(this); //调用A的print方法,但是this对象使用B的.
};

Mixin 模式

js 模块化

模块就是封装,仅提供方法,不提供变量.

独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。 为了在模块内部调用全局变量,必须显式地将其他变量输入模块

var module1 = (function($, YAHOO) {
  //...
})(jQuery, YAHOO);

立即执行函数(IIFE)

立即执行函数”(Immediately-Invoked Function Expression,IIFE).

外部无法访问私有变量_count

模块化的思想,非常常见了.

var module1 = (function() {
  var _count = 0;
  var m1 = function() {
    //...
  };
  var m2 = function() {
    //...
  };
  return {
    m1: m1,
    m2: m2
  };
})();

console.info(module1._count); //undefined

还能起到作用如命令空间,这个倒是很常见的写法

(function($, window, document) {
  function go(num) {
    //go仅模块内部使用
  }

  //external定位在windows上,全局可使用
  window.external = {
    init: initialize,
    destroy: dieCarouselDie
  };
})(jQuery, window, document);

放大模式

是为了模块里扩充,添加其他方法,或者模块的方便:

var module1 = (function(mod) {
  mod.m3 = function() {
    //...
  };
  return mod;
})(module1);

宽放大模式

允许模块为空,

var module1 = (function(mod) {
  //...mod可能来自其他部分,可能加载到空对象
  return mod;
})(window.module1 || {});