首页 > 技术文章 > javascript高级(2)

f2ehe 2019-11-24 13:27 原文


typora-copy-images-to: media


第02阶段.前端基本功.前端基础.js进阶

基础语法

学习目标

  • 了解
 重新认识instanceof
- 原型链不可变
- for..in的问题
  • 重点

  • 重点

    • 上下文调用模式

    • js中的继承

    • 闭包

1. 重新认识instanceof

js基础阶段对instanceof作用的描述 : 判断一个对象是否是某个构造函数的实例

现在我们学习了原型,也学习了原型链,所以我们现在可以更严谨的描述他的作用:

判断一个函数的原型对象,是否在实例对象的原型链上(判断函数Array的原型是否在arr的原型链上)

var arr = [];
console.log(arr instanceof Array); //true
console.log(arr instanceof Object);  //true

2. 原型链不可变(prototype新加了一个就指向了这个刚创建的原型对象,原来函数创建的实例就无法指向这个原型)

function Person(){
}
var p1 = new Person();
console.log(p1 instanceof Person); // ?

Person.prototype = {
    constructor : Person,
    a : 1
}
console.log(p1 instanceof Person); // ?
var p2 = new Person();
console.log(p2 instanceof Person); // ?

3. 上下文调用函数

3.1 回顾之前我们学习的调用函数的几种方式

  • 普通调用

    this -- > window

    function fn(){
        console.log(this);  // window
    }
    fn();
    
  • 对象调用

    this -- > obj

    function fn(){
    	console.log(this) // obj
    }
    var obj = {
        f : fn
    };
    obj.f();
    
    
  • 构造函数调用

    this -- > 构造函数的实例

    function Person(name){
        this.name = name; //this --> 实例
    }
    
    var p1 = new Person('zs');
    
  • 定时器中的回调函数

    this -- > window

    setTimeout(function(){
    	console.log(this)// window
    },1000);
    
  • 事件处理函数调用

    this --> 事件源

    box.onclick = function(){
    	console.log(this); // box
    }
    

我们总结了一句话: this的指向是在函数调用时决定的. 谁调用这个函数,this指向谁

3.2 上下文调用函数的三个方法

为什么要学习上下文调用函数?

因为这样我们就可以随意的控制函数中this的指向

3.2.1 call方法

作用: 调用该函数,并修改函数中this的指向

**语法: ** 函数名. call(对象,[实参]);

参数详解:

第一个参数: 要让函数中this指向谁,就写谁

后面的参数: 被调用函数要传入的实参,以逗号分隔

function fn(x, y){
    console.log(this); // ['a','b','c']
    console.log(x + y); //8
}
fn.call(['a','b','c'], 3, 5);

3.2.2 apply 方法

作用: 调用该函数,并修改函数中this的指向

**语法: ** 函数名. apply(对象,数组);

参数详解:

第一个参数: 要让函数中this指向谁,就写谁

第二个参数: 要去传入一个数组,里面存放被调用函数需要的实参

function fn(x, y){
    console.log(this); // {a : 1}
    console.log(x + y); //8
}
fn.apply({a : 1}, [3, 5]);

3.2.3 bind方法

作用: 不调用函数,克隆一个新的函数,并修改新函数中this的指向,将新的函数返回

**语法: ** 函数名. bind(对象[,实参]);

参数详解:

第一个参数: 要让函数中this指向谁,就写谁

后面的参数: 被调用函数要传入的实参,以逗号分隔

function fn(x, y){
    //调用newFn时打印结果
    console.log(this);  //{b:2}
    console.log(x + y); //8
}

var newFn = fn.bind({b:2}, 3, 5);
newFn();

小结:

  • call和apply 会调用函数 , bind不会调用函数
  • apply要求把实参放到数组中,然后把数组传进来
  • 当我们需要自己调用函数,并且要修改函数中this的指向的时候,用call/apply
  • 当我们需要浏览器帮我们调用的时候(事件处理函数,定时器的回调函数),并且要修改函数中this的指向的时候用bind

4. for..in的问题

for...in 遍历时,会把原型上的属性也遍历出来

function Person(name){
    this.name = name;
}

Person.prototype.type = 'human';

var zs = new Person('zs');

for(var key in zs){
    console.log(key); // name , type  把type也打印出来了
}

如果我们就想打印对象自己身上的属性,可以使用hasOwnProperty方法来解决

**hasOwnProperty的作用: ** 判断是不是对象自己的属性

**hasOwnProperty的语法: ** 对象.hasOwnProperty('属性') 是则返回true, 否则false

function Person(name){
    this.name = name;
}

Person.prototype.type = 'human';

var zs = new Person('zs');

for(var key in zs){
    if(zs.hasOwnProperty(key)){
        console.log(key);   //只有自己的name
    }
}

5. js中的继承

5.1 为什么要学习js中的继承

此时我们希望son对象,也拥有far对象的money, house这些属性,但是我们不想再重新写一遍,怎么办?

var far = {
    money : $1000000000,
    house : ['别墅', '大别墅', '有游泳池的打别墅', '城堡'];
}

var son = {
    
}

5.2 两个对象之间的继承

5.2.1 遍历对象

var far = {
    wife : '小龙女',
    money : 1000000000,
    house : ['别墅', '大别墅', '有游泳池的打别墅', '城堡']
}

var son = {
    wife : '小黄飞',
}

//通过for..in遍历far
for(var key in far){
    son[key] = far[key];
}

console.log(son); // 此时son拥有了money和house .

但是以上代码书写存在一个问题 : 把wife的值也继承下来了.我们希望自己有的,就不继承了.所以我们要改一下代码

for(var key in far){
    //自己没有的才继承
    if(!son.hasOwnProperty(key)){
        son[key] = far[key];
	}
}

这样处理解决刚才的问题,但是我们发现两个对象上面存在一样的代码.这样比较占用内存

5.2.2 通过create方法实现继承

通过ceate方法实现继承,可以避免遍历对象方式,占用内存的问题

**create方法语法: **Object.create(参考对象) 返回一个新的对象

**create方法的作用: ** 返回的新的对象的__proto__ 指向参考对象

var far = {
    wife : '小龙女',
    money : 1000000000,
    house : ['别墅', '大别墅', '有游泳池的打别墅', '城堡']
}

var son = Object.create(far);
son.wife = '小黄飞';
console.log(son.money); //1000000000
console.log(son.house); //['别墅', '大别墅', '有游泳池的打别墅', '城堡']
console.log(son.wife); // 小黄飞
console.log(son.__proto__ === far); //true

5.3 构造函数的继承

我们工作中要经常创建多个具有相同属性的对象,所以经常要写构造函数.

那么构造函数创建的出来的对象该如何实现继承呢?

5.3.1 借用构造函数

function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayHello = function(){
        console.log('hello, ' + '我是' + this.name );
    }
}

function Student(name, age, score){
    this.score = score;
    Person.call(this, name, age);//借用构造函数
}

var stu = new Student('zs', 18, 100);
console.log(stu); //{score : 100, name : zs, age : 18}
stu.sayHello(); //hello, 我是zs

但是我们一般不会把方法写在构造函数的函数体内,我们把方法写在函数的原型上,那么这个时候构造借用函数就无法继承方法了,我们想要继承原型上的方法.可以用原型继承

5.3.2 原型继承

用来继承方法

function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayHello = function(){
        console.log('hello, ' + '我是' + this.name );
    }
}

function Student(name, age, score){
    this.score = score;
}

Student.prototype = new Person();

var stu = new Student('zs', 18, 100);
console.log(stu); //{score : 100}
stu.sayHello(); //hello, 我是undefined

我们发现,方法继承下来了,但是属性却没有继承下来

5.3.3 组合继承

借用构造函数 + 原型继承

function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayHello = function(){
        console.log('hello, ' + '我是' + this.name );
    }
}

function Student(name, age, score){
    this.score = score;
    Person.call(this, name, age);
}

Student.prototype = new Person();

var stu = new Student('zs', 18, 100);
console.log(stu); //{score : 100, name : zs, age : 18}
stu.sayHello(); //hello, 我是zs

6. 闭包

**概念: ** 函数和函数作用域的结合

**通俗理解: ** 内部函数使用外部函数的变量, 整个外部函数形成了一个闭包

6.1闭包的作用:

  1. 私有化数据
  2. 数据保持
function main(){
    var money = 10000;  //放到局部作用中,防止全局变量污染(私有化数据)

    return {
        queryMoney : function(){
            return money;
        },
        payMoney : function(num){
            money -= num;
        },
        addMoney : function(num){
            money += num;
        }
    }
}

var moneyManger = main();  // 通过moneyManger 可以获取到局部的变量money

6.2闭包的缺点:

由于内部的函数使用了外部函数的变量,导致外部这个函数无法被回收掉.如果代码中大量的存在闭包,可能会导致内存泄露 (不要刻意使用闭包);

7.拓展内容

7.1 函数的静态成员和实例成员

成员: 泛指属性和方法

静态成员: 函数也是对象, 函数自己的属性

实例成员: 特指构造函数时,写在构造函数体内,通过this.xxx.给实例添加的属性

function Person(){
	this.name = name; //实例成员
    this.age = age; // 实例成员
}

Person.type = '呵呵'; //静态成员

7.2.Function的原型图

Functon 自己创造了自己

Object是Function的实例

推荐阅读