一、函数的原型
1.创建对象的三种方式:
字面量、调用系统的构造函数、自定义构造函数
2.工厂模式创建对象:
function createObject(name,age) {
var obj=new Object();
obj.name=name;
obj.age=age;
obj.sayHi=function () {
console.log("您好");
};
return obj;
};
3.原型
- 每个构造函数都有一个原型对象prototype,这个对象中有一个构造器constructor指向了这个构造函数
- 每个通过构造函数实例化出来的实例对象中都有一个属性__proto__,这个属性指向一个原型对象,这个对象和上面的原型对象prototype是同一个,如下图:
- 我们把需要共享的属性和方法写在原型中,节省内存空间。实例对象中的方法可以相互调用,原型中的方法也可以相互调用。
- 简单的原型写法:
Student.prototype = {
//必须手动修改构造器的指向
constructor:Student,
height: "188",
weight: "70kg",
study: function () {
console.log("学习真好");
}
};
4.原型链
- 原型链:是一种关系,实例对象和原型对象的关系是通过__proto__来联系的,一个实例对象访问某个属性或方法时先在自己的内存中寻找某个属性和方法,如果没找到,再在__proto__属性指向的对象中寻找,如果还是没找到,则返回uundefined
- 函数的原型对象prototype也是一个对象,这个对象也有__proto__属性,它指向了Object函数的原型(Object.prototype),而Object的原型对象prototype中的__proto__属性指向了null
二、继承
- 改变原型的指向实现继承:让子类的构造函数的原型指向父类的一个实例对象,从而实现继承
function Animal(name,weight) {
this.name=name;
this.weight=weight;
}
//动物的原型的方法
Animal.prototype.eat=function () {
console.log("天天吃东西");
};
//狗的构造函数
function Dog(color) {
this.color=color;
}
Dog.prototype=new Animal("哮天犬","50kg");//实现继承,Dog的实例对象可以调用eat方法
- 使用call方法继承
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.sayHi = function () {
console.log("您好");
};
function Student(name,age,sex,score) {
//借用构造函数
Person.call(this,name,age,sex);//构造函数名字.call(当前对象,属性,属性,属性....);
this.score = score;
}
因为改变原型指向的继承方法直接初始化了属性,继承过来的属性的值都是一样的了,而这种继承方法就解决了这个问题,但缺陷是父级类别中的方法不能继承
- 组合继承
function Person(name,age,sex) {
this.name=name;
this.age=age;
this.sex=sex;
}
Person.prototype.sayHi=function () {
console.log("你好");
};
function Student(name,age,sex,score) {
//借用构造函数:属性值重复的问题
Person.call(this,name,age,sex);
this.score=score;
}
//改变原型指向----继承
Student.prototype=new Person();//不传值
Student.prototype.constructor=Student;//手动指向构造器
Student.prototype.eat=function () {
console.log("吃东西");
};
var stu=new Student("小黑",20,"男","100分");
即改变原型的指向,又使用call方法,属性和方法都被继承了
- 拷贝继承
用for-in循环将一个对象中的属性和方法复制到另一个对象中,很少用
三、函数
1.函数两种表达
- 函数声明
function f1() {
console.log("我是函数");
}
f1();
- 函数表达式
var ff=function () {
console.log("我也是一个函数");
};
ff();
区别:两种函数表达在进行预解析的时候会有不同,函数声明如果放在if-else的语句中,在IE8的浏览器中会出现问题,尽量多用函数表达式
2.函数中的this指向问题
- 普通函数和定时器中的this指向window
- 对象中、构造函数中、原型对象中的this指向实例对象
3.函数也是对象
- 每个函数都是对象,所有函数都是构造函数Function的实例对象,是对象就有__proto__属性,它指向构造函数Function的原型对象,而Function的原型对象prototype中的__proto__属性指向Object的原型对象
- 这里有一点复杂,需要细细理解。可以肯定一点,如果没有改变原型的指向的话,每个构造函数的原型对象中的__proto__都指向Object的原型对象
4.函数的几个属性
- name:函数的名字,只能读不可写
- arguments:实参的个数
- length:函数定义时形参的个数
- caller:调用者
5.函数作为参数使用
- 函数作为参数时,如果是命名函数,只传入函数的名字,没有括号
四、apply、call、bind函数
- apply和call方法是改变this指向的,它们都在构造函数Function中的原型中。
- bind方法是复制一份,有返回值,返回值是一个函数,作用也是改变this的指向。
- apply和call是函数调用时改变了this指向,而bind是复制的时候就改变了this指向。
- appky和call语法:
f1.apply(对象,[…]);
f1.call(对象,…);
这两个方法参数的传递方式不同 - bind方法传参数时可以在复制的时候传进去,也可以在复制之后调用的时候传进去:
function f1(x, y) {
console.log((x + y) + ":=====>" + this.age);
}
function Person() {
this.age = 1000;
}
var per = new Person();
var ff = f1.bind(per,10,10);
ff();
//以上是在复制是时候传参数进去
function f1(x, y) {
console.log((x + y) + ":=====>" + this.age);
}
function Person() {
this.age = 1000;
}
var per = new Person();
var ff = f1.bind(per);
ff(10,10);
//以上是复制之后在调用的时候传参数进去
- 获取某个对象的数据类型可以借助call方法来实现:
Object.prototype.toString.call(对象); //此时输出这个对象的数据类型
五、闭包和沙箱
1.闭包
- 函数A中有一个函数B,函数B中可以访问函数A中定义的变量或数据,这大概就是闭包(并不严谨)。
- 闭包的模式:函数模式的闭包,对象模式的闭包
- 闭包的作用:缓存数据,延长作用域链(是优点也是缺点)
- 举例:
function f2() {
var num = 10;
return function () {
num++;
console.log(num);
}
}
var ff = f2();
ff();//11
ff();//12
ff();//13
2.沙箱
- 两种写法:
//第一种
(function () {
var num=10;
console.log(num);
})();
//第二种
(function () {
var num=20;
console.log(num+10);
}());
- 沙箱就是一个独立的环境,这里面的变量和数据不会和外层的冲突
3.浅拷贝和深拷贝
- 浅拷贝:就是把一个对象的地址给另一个对象,并没有开辟新的空间。
- 深拷贝:需要开辟新的空间,然后把这个空间的地址给另外一个对象。