对象之间的继承
(对象拷贝)
- 使用for...in结构进行遍历拷贝属性,子级对象已经有的属性就无需再继承父级对象的属性
- 可以使用一个封装函数将对象之间继承的功能封装起来,方便省事
// 父级的对象
var laoli = {
name : "laoli",
money : "10000",
house : ["商铺","住宅"],
tech : function(){
console.log("厨艺");
}
};
// 子级对象
var xiaoli = {
name : "xiaoli"
}
// // 对象之间继承,使用for...in
// for(var k in laoli){
// // 子级有的属性不需要继承
// if(xiaoli[k]){
// continue;
// }
// xiaoli[k] = laoli[k];
// }
// 封装一个函数用于对象之间继承
function extend(parent,child){
for(var k in parent){
// 子级有的属性不需要继承
if(child[k]){
continue;
}
child[k] = parent[k];
}
}
extend(laoli,xiaoli);
console.log(xiaoli);
原型继承
<script>
// 封装的构造函数就是用来创建一类对象
// 继承指的是 类型 和 类型之间的继承
// 学生类型 老师类型 --> 抽象,提取所有的公共的属性,放到一个 父类型中
// 当前学习阶段,没有一个专门的用来继承的方法
// 创建人类类型
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
// 学生类型
function Student(score){
this.score = score;
}
// 老师类型
function Teacher(salary){
this.salary = salary;
}
// 原型对象,可以去将自己的属性和方法继承给将来的实际对象使用
// 只能传一次,不能多次传了
Student.prototype = new Person("zs",18,"男");
// 要将constructor重新指向Student对象,因为给了一个新的对象后constructor会被更改
Student.prototype.constructor = Student;
// 生成一个实例
var s1 = new Student(88);
console.dir(s1);
console.log(s1.name);
console.log(s1.constructor);
</script>
坏处1:constructor会指向Person
坏处2:只能作一次传参,多的不能更改,比如我传第二个学生s2,除了分数不一样之外,姓名年龄性别都不能再更改了
函数的call方法
// call
// 函数本身就是一种对象,就能够有自己的属性和方法
// call 方法本身是一种执行函数的方法
function fn(a,b) {
console.log(this);
console.log(a + b);
}
var o = {
name: "zs"
}
// 普通函数调用
fn(2,3);
// call 方法在调用函数的时候,有两个功能
// 1.更改函数内部的 this 指向
// 2.调用函数执行内部代码
// 参数: 第一个参数用来指定 this,第二个及以后,就是传的实参
fn.call(o,3,4);

借用构造函数继承属性
- 结合call方法,将call的指向改为指定构造函数内部的this。
// 构造函数的属性的继承
// 人类类型
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 学生类型
function Student(name,age,sex,score) {
// 直接对父类型的构造函数进行一个普通调用
// Person 普通调用过程中,内部的 this 指向的是 window
// 可以通过 call 方法更改Person 内部的 this
Person.call(this,name,age,sex);
this.score = score;
}
// 老师类型
function Teacher(name,age,sex,salary) {
Person.call(this,name,age,sex);
this.salary = salary;
}
// 创建学生的实例对象
var s1 = new Student("zs",18,"男",89);
var s2 = new Student("ls",19,"男",92);
console.dir(s1);
console.dir(s2);
解决了属性继承的问题,可以多次传参了
构造函数的原型方法的继承
// 人类类型
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;
}
// 子类型的原型对象上,需要继承父类型原型对象的方法
// 方法1:对象拷贝继承
// for (var k in Person.prototype) {
// // 保留自己的 constructor 不要进行继承
// if (k === "constructor") {
// continue;
// }
// Student.prototype[k] = Person.prototype[k];
// }
// 方法2:原型继承
Student.prototype = new Person();
Student.prototype.constructor = Student;
// 老师类型
function Teacher(name,age,sex,salary) {
Person.call(this,name,age,sex);
this.salary = salary;
}
// 创建学生的实例对象
var s1 = new Student("zs",18,"男",89);
var s2 = new Student("ls",19,"男",92);
console.dir(s1);
console.dir(s2);
s1.sayHi();
组合继承
- 属性在构造函数内部继承,方法通过原型继承
<script>
// 组合继承:属性在构造函数内部继承,方法通过原型继承
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function () {
console.log("你好");
}
// 生成一个子类型
function Teacher(name,age,salary) {
// 继承父类的属性
Person.call(this,name,age);
this.salary = salary;
}
// 方法继承,通过原型对象继承
Teacher.prototype = new Person();
Teacher.prototype.constructor = Teacher;
// 生成老师的一个实例
var t1 = new Teacher("wang",45,10000);
console.dir(t1);
console.log(t1.name);
t1.sayHi();
</script>

函数进阶
函数声明和函数表达式
- 函数声明必须要有名字
- 函数声明会进行函数提升,在预解析阶段就已经创建,声明的顺序前后都可以调用
- 函数表达式可以没有名字,例如说匿名函数
- 函数表达式没有函数提升,必须要在表达式执行之后才能调用
推荐函数声明方法
现代浏览器一般都是变量声明提升,低版本比如IE8及以下都是函数声明提升,所以要解决这种问题一般都是采用声明变量,然后进行赋值的方式
var fn;
// if (true) {
// function fn() {
// console.log("fn-true");
// }
// } else {
// function fn() {
// console.log("fn-false");
// }
// }
if (true) {
fn = function () {
console.log("fn-true");
}
} else {
fn = function () {
console.log("fn-false");
}
}
fn();
函数也是对象
- 函数本身也是一种对象, 可以调用属性和方法
// 通过构造函数方法定义函数
// 函数本身也是一种对象
var fun = new Function('a','b','var a = "1";console.log(a+b)');
fun(2,3);
console.dir(fun);
函数的调用和this
- 普通函数,调用方法是给函数名或者变量名加()调用,this默认指向window
- 构造函数,通过new关键字调用,this指向的是将来创建的实例对象
- 对象里的方法,通过对象打点调用函数,加(),this默认指向的是调用的对象自己
- 事件函数,不需要加特殊的符号,只要事件被触发,就会自动执行,this指向的是事件源
- 定时器和延时器,不需要加特殊的符号,执行后,在规定的时间内自动执行,this指向的是window

call,apply,bind
call
- call()方法调用一个函数,其具有一个指定的this值和分别地提供的参数(参数的列表)
- 注意:该方法的作用和apply()方法类似,只有一个区别,就是call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组
- 语法:
fun.call(thisArg,arg1,arg2,arg3....) - thisArg
在fun函数运行时指定的this值,如果指定了null或者undefined那么内部this指向window - arg1......
指定的函数的参数列表
apply
- apply() 方法调用一个函数, 第一个参数是一个指定的 this 值,第二个参数是以一个数组 (或类似数组的对象)形式提供的参数。
- 注意:该方法的作用和 call() 方法类似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。
- 语法:
fun.apply(thisArg, [argsArray])
bind
- bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的 call 属性)。
- 当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。
- 一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器。提供的this 值被忽略,同时调用时的参数被提供给模拟函数。
- 语法:
fun.bind(thisArg,arg1, arg2, arg3, ...)
call的应用
<script>
// {} 的对象自己是没有 push 方法的
// 类数组对象 getElementsByTagName
var o = {
0: 10,
1: 20,
2: 30,
length: 3
};
// console.log(o[0])
// 增加一项新的数据
// o["3"] = 40;
// o.length = 4;
// 利用数组中的 push 方法,指定内部的this 为对象 o,就可以处理类数组对象的数据
Array.prototype.push.call(o,50);
console.log(o);
</script>
使用call利用数组中的push方法,指定内部的this为对象o,就可以处理类数组对象的数据了,也就是借用对象没有的一些方法
apply的应用
// 利用 apply 方法,将数组传给 max 的第二个参数
console.log(Math.max.apply(Math,arr));
// console.log(1,2,3);
// 利用console里的打印方法,实现零散输出
console.log.apply(console,arr);
bind的应用
不是立即触发的函数最好还是使用bind,比如定时器和事件函数
<script>
// 想修改的是定时器的函数内部的 this
var o = {
name: "zs",
age: 18,
s: function () {
setInterval(function () {
console.log(this.age);
// 不修改this的话,就是输出undefined,因为定时器的this默认为window
}.bind(this),1000);
}
}
// o.s();
// 更改 事件函数中的 this
document.onclick = function () {
console.log(this);
}.bind(o);
</script>
总结


函数的其他成员
- arguments 实参集合
- arguments.callee 函数本身,arguments的一个属性
- fn.caller 函数的调用者,如果在全局调用,返回的调用者为 null。
- fn.length 形参的个数
- fn.name 函数的名称
// 看一下函数内部的成员
function fn(a,b) {
// 实际应用中,会在函数内部直接调用一个arguments的关键字
console.log(arguments);
// 存储的是函数在调用时,传入的所有的实参组成的一个类数组对象
console.log(fn.arguments);
// 函数的调用者,函数在哪个作用域调用,caller就是谁,全局作用域下,值就是null
console.log(fn.caller);
// 指的是形参的个数,不是实参
console.log(fn.length);
// 函数名称
console.log(fn.name);
}
function test(){
fn(1,2,3,4);
}
test();

使用arguments判断实参中的最大数字
// 使用arguments数组来判断实参中的最大值
function max(){
// 判断实参中最大的数
var nowMax = arguments[0];
for(var i = 1; i< arguments.length;i++){
if(arguments[i]>nowMax){
nowMax = arguments[i];
}
}
return nowMax;
}
console.log(max(1,2,3,4,5,6,7,8,9));
高阶函数
- 函数可以作为参数
- 函数作为返回值
<script>
// 高阶函数
// 1.函数作为另一个函数的参数
// 定义一个函数,吃饭的函数,吃完饭之后,可以做其他的事情,看电影、聊天、看书
function eat(fn){
console.log("干饭");
// 接下来的事情是不固定的
fn();
}
eat(function(){
console.log("看电影");
});
// 2.函数作为另一个函数的返回值
// 需求:通过同一段代码,实现效果如下
// 输出 100 + m
// 输出 1000 + m
// 输出 10000 + m
function outer(n) {
return function inner(m){
console.log(m + n);
}
}
// 在外部执行inner函数
// 实现100+m的效果
var fun = outer(100);
fun(3);
// 1000 + m
var fun1 = outer(1000);
fun1(3);
// 10000 + m
var fun2 = outer(10000);
fun2(3);
</script>

函数闭包
函数定义时天生就能记住自己生成的作用域环境和函数自己,将它们形成一个密闭的环境,这就是闭包。不论函数以任何方式在任何地方进行调用,都会回到自己定义时的密闭环境进行执行。

观察闭包
- 从广义上来说,定义在全局的函数也是一个闭包,只是我们没办法将这样的函数拿到更外面的 作用域进调用,从而观察闭包的特点。
- 闭包是天生存在的,不需要额外的结构,但是我们为了方便观察闭包的特点,需要利用一些特殊结构将一个父函数内部的子函数拿到父函数外部进行调用,从而观察闭包的存在。
<script>
// 体会闭包
// 将一个内部函数拿到父函数的外面,观察是否还能调用父函数内部的变量
function outer() {
// 形成闭包环境中的变量不是一成不变的,可以被更改
var a = 10;
function inner() {
console.log(a++);
}
// 将inner 函数作为返回值
return inner;
}
var inn = outer();
inn();
inn();
</script>
闭包的用途
- 在外部可以读取到函数内部的成员
- 让函数内的成员始终存活在内存中
闭包的问题
- 因为作用域环境太大了,导致数据根本无法调用,得到的是跳出循环之后的数字
<script>
// 给数组中的每一项赋值一个 函数
var arr = [];
for (var i = 0 ; i <= 10 ; i++) {
// 自调用函数
(function (i) {
arr[i] = function () {
console.log(i);
};
})(i);
}
// 目的:调用数组对应的项,输出它的对应下标
arr[0]();
arr[1]();
arr[2]();
</script>
一般我们就用自调用函数封闭作用域范围,变小闭包的作用域,得到对应的下标
网友评论