美文网首页
JavaScript中的原型链与函数等问题

JavaScript中的原型链与函数等问题

作者: Ringo_ | 来源:发表于2019-08-19 17:29 被阅读0次

数据类型

number  
string  
boolean  
null  
undefined
object-
        Function
        Array
        Date
        ...

隐式转换

变量转换为字符串、===、==(类型不同,尝试转换类型再比较)

类型检测

typeof  
instanceof  
Object.prototype.toString  
constructor  
duck type  

typeof NaN - number
typeof null - object
typeof不能用来判断null与object object与array
undefined代表定义了但未赋值,null代表定义且赋值了,只是值为null。
什么时候需要赋值为null:初始赋值为null,表明将要赋值为对象;需要释放空间时,将变量赋值为null。

判断对象的类型更常使用instanceof操作符,obj instanceof Object,判断左边对象的原型链上是否有右边的属性,返回true或false。不同window或iframe间的对象类型检测不可用。

Object.prototype.toString.apply([]); === "[object Array]";
Object.prototype.toString.apply(function(){}); === "[object Function]";
Object.prototype.toString.apply(null); === "[object Null]";
Object.prototype.toString.apply(undefined); === "[object Undefined]";

回调函数

什么函数才是回调函数:自己定义的;没调用;但最终执行了。
常见的回调函数:dom事件回调函数;定时器回调函数;ajax请求回调函数;生命周期回调函数。

IIFE

立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。 immediately-invoked Function Expression
IIFE形式的函数调用:

(functionfoo(){
  vara=10;
  console.log(a);
})();

函数的声明和IIFE的区别在于,在函数的声明中,我们首先看到的是function关键字,而IIFE我们首先看到的是左边的(。也就是说,使用一对()将函数的声明括起来,使得JS编译器不再认为这是一个函数声明,而是一个IIFE,即需要立刻执行声明的函数。
两者达到的目的是相同的,都是声明了一个函数foo并且随后调用函数foo。

为什么需要IIFE:
如果只是为了立即执行一个函数,显然IIFE所带来的好处有限。
实际上,IIFE的出现是为了弥补JS在scope方面的缺陷:JS只有全局作用域(global scope)、函数作用域(function scope),从ES6开始才有块级作用域(block scope)。
对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function,只有function,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。
在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。

https://blog.csdn.net/mooncom/article/details/78532970

函数中的this

https://www.cnblogs.com/pssp/p/5216085.html

函数高级

原型(prototype)
每个函数都有一个prototype属性,它默认指向一个object空对象(原型对象)
所有函数都是Function的实例,包括Function本身
Object的原型对象是原型链尽头,Object.prototype.proto ==>null
原型对象中有个属性constructor,他指向函数对象(Date.prototype.constructor===Date)
可给原型对象添加属性(一般是方法),用来给实例对象使用

function Fun(){
    //prototype属性在定义函数时自动添加 默认为空
    //this.prototype = {}
}

Fun.prototype.test = function() {
    console.log('test')
} //给原型添加方法

var fun = new Fun() //创建对象时自动添加__proto__属性,默认为构造函数的prototype
fun.test()    //通过实例调用原型的方法,输出test

显式原型与隐式原型

显式原型:每个函数都有个prototype,即显式原型
隐式原型:每个实例对象都有一个proto,即隐式原型

function Fun(){

}

var fun = new Fun()

// Fun.prototype===fun.__proto__ 
//即对象隐式原型的值等于构造函数的显式原型的值

可以直接操作显式原型,但在es6之前不能直接操作隐式原型

原型链

https://www.bilibili.com/video/av41708223/?p=17

原型链的本质即 隐式原型链,用来查找对象的属性

*访问一个对象的属性时,先在自身属性中查找,找到返回
*如果没有 再沿着proto这条链向上查找,找到返回
*最终没找到 返回undefined

原型链的属性问题:

function Fun(){
    
}
Fun.prototype.a = 'a'
var f1 = new Fun()
console.log(f1.a)   //输出a

但一般不会在原型内添加属性

instanceof

a instanceof b 如果b函数的显式原型对象在a对象的原型链上返回true
https://www.bilibili.com/video/av41708223/?p=20

面试题:
https://www.bilibili.com/video/av41708223/?p=21

变量提升与函数提升

变量提升:通过var定义的变量在定义语句前就可以访问到,只是值为undefined
函数提升:通过function声明的函数,在定义前就可以访问使用

执行上下文

代码分类:全局代码;局部代码
全局执行上下文:1,在执行全局代码前将window确定为全局执行上下文,2,对全局数据进行预处理(var定义的全局变量添加为window属性,function声明的全局函数添加为window方法);3,开始执行

1.在全局代码执行前,js引擎会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文后(window)确定后,将其添加到栈中(压栈)
3.在函数执行上下文创建后,将其添加到栈中
4.在当前函数执行完后,将栈顶的对象移除(出栈)
5.当所有的代码执行完后,栈中只剩下window

执行的先后顺序:

function a(){}  
var a
console.log(typeof a)  //输出Function
//先执行变量提升,再执行函数提升

var c = 1
function c(c) { console.log(c)}
c(2)   //结果会报错

闭包

<body>
    <button>1</button>
    <button>2</button>
    <button>3</button>
    <script>
        var btns = document.getElementsByTagName('button');
        for(var i = 0; i < btns.length; i++)
        {
            (function (i) {
                var btn = btns[i];
                btn.onclick = function(){
                    alert(i+1);
                }
            })(i)
        }
    </script>
</body>

但一个嵌套的内部函数引用了嵌套的外部函数的变量(或函数)时,就产生了闭包。
什么是闭包:可以通过Chrome调试查看1.闭包是嵌套的内部函数 2.包含被引用变量的对象。
产生闭包的条件:函数嵌套;内部函数引用了外部函数的数据(变量/函数)

常见的闭包

1.将函数作为另一个函数的返回值
2.将函数作为实参传递给另一个函数调用

闭包的作用:
1.使用函数内部的变量在函数执行完之后,仍然存活在内存里,延长了局部变量的生命周期。因为在函数执行完之后,函数内部声明的变量一般不存在了。
2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)因为函数外部不能直接访问内部函数的变量

function f1(){
    //此时闭包就已经产生
    //函数提升,内部函数对象已经创建
    var a = 2;
    function f2() {
        a++;
        console.log(a);
    }
    return f2;   //将f2暴露出去,使f1可访问
}

var f = f1();
f();  //输出3
f();  //输出4
//f = null; 闭包死亡(闭包的函数对象成为垃圾对象)

f()一直存在,指向f2的闭包,a的值一直存在。

闭包的生命周期:
产生:在嵌套的内部函数定义的时候就产生了(不是在调用时)
死亡:在嵌套的内部函数成为垃圾对象时

闭包的应用(实现自定义js模块)

(function () {
    //私有数据
    var msg = "myFun";

    function fun1() {
        console.log("fun1: "+ msg);
    }
    
    function fun2() {
        console.log("fun2: "+ msg);
    }
    
    //向外暴露对象(给外部使用的方法)
    window.myfun1 = {
        fun1: fun1,
        fun2: fun2
    }
    
})()

在使用时,先引入js文件

<script>
    myfun1.fun1();
    myfun1.fun2();
</script>

闭包的缺点及解决
缺点:
函数执行完之后,函数内的局部变量没有释放,占用内存时间会变长
容易造成内存泄漏
解决:
能不用闭包就不用;及时释放

内存溢出:
·一种程序运行出现的错误
·当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误

内存泄漏:
·占用的内存没有及时释放
·内存泄漏积累多了就容易导致内存溢出

常见的内存泄漏:
意外的全局变量
没有及时清理的计时器或回调函数
闭包

function f1() {
    var a = 1;
    function f2() {
        console.log("f2: "+ a);
    }
    return f2;
}

var f = f1();
f();
f = null;  //需要加这一句 释放一直存在的a

面试题:

var name = "the window";
var obj = {
    name: "my object",
    getName: function () {
        return function(){
            return this.name;
        }
    }
};
alert(obj.getName());     //显示getName()整体
alert(obj.getName()());   //显示“the window”



var name2 = "the window";
var obj2 = {
    name2: "my object",
    getName: function() {
        var that = this;
        return function() {
            return that.name2;
        }
    }
}
alert(obj2.getName()());    //显示“my object”
  1. function fun(n,o) {
    console.log(o)
    return {
    fun: function(m) {
    return fun(m,n);
    }
    }
    }

    var a = fun(0); //undefined 此时闭包里 n的值为0
    a.fun(1); //0 此时闭包里为fun(1,0) 所以输出0
    a.fun(2); //0
    a.fun(3); //0

    var b = fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2

    var c = fun(0).fun(1); //undefined 0
    c.fun(2); //1
    c.fun(3); //1

面向对象高级

1.创造函数模式
先创建空的object对象,再动态添加属性方法
适用:起始时不确定对象内部数据
2.创建字面量模式
使用{}创建对象,同时指定属性/方法
适用于起始时对象内部数据是确定的,如果创建多个对象,有重复代码
3.工厂模式
通过工厂函数创建对象并返回,需要创建多个对象时使用,对象没有一个具体的类型,都是object类型。该模式并不常用。

原型链继承

1.定义父类型构造函数
2.给父类型的原型添加方法
3.定义子类型的构造函数
4.创建父类型的对象赋值给子类型的原型
5.将子类型原型的构造属性设置为子类型
6.给子类型原型添加方法
7.创建子类型的对象,可以调用父类型的方法
关键:子类型的原型为父类型的一个实例对象

//父类型
function Supper() {
    this.supperValue = 'supperValue'
};
Supper.prototype.showSupperValue = function() {
    console.log(this.supperValue)
};
//子类型
function Sub() {
    this.subValue = 'subValue'
};
//子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper();
Sub.prototype.showSubValue = function() {
    console.log(this.subValue)
};
var sub = new Sub();
sub.showSupperValue(); //输出supperValue
sub.showSubValue();    //输出subValue

借用构造函数继承

1.定义父类型构造函数
2.定义子类型构造函数
3.在子类型构造函数中调用父类型构造,通过call()调用

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

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

var s = new Student('zhang', 20, 9000);
console.log(s.name,s.age,s.price);

浏览器内核

支撑浏览器运行的最核心的程序,不同的浏览器内核不一样,浏览器内核由很多模块组成
Chrome,Safari:webkit
Firefox:Gecko
IE:Trident
360,搜狗等国内浏览器:Trident+webkit

内核的模块

js引擎模块:负责js程序的编译与运行
HTML css文档解析模块:负责页面文本的解析
DOM/CSS模块:负责dom/css在内存中的相关处理
布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
定时器模块:负责定时器的管理
事件相应模块:负责事件的管理
网络请求模块:负责ajax请求

线程机制与事件机制

进程:程序的一次执行,它占有一片独有的内存空间;可以通过Windows任务管理器查看进程
线程:是进程内的一个独立的执行单元;是程序执行的一个完整流程;是CPU的最小的调度单元

应用程序必须运行在某个进程的某个线程上;
一个进程中至少有一个运行的线程:主线程,进程创建后自动创建;
一个进程中也可以同时运行多个线程,程序是多线程运行的;
一个进程内的数据可以供其中的多个线程直接共享;
多个线程之间的数据是不能共享的;
线程池thread pool:保存多个线程对象的容器,实现线程对象的反复利用。

何为多进程与多线程:
多进程运行:一个应用程序可以同时启动多个实例运行
多线程:在一个进程内,同时有多个线程运行

比较单线程与多线程:
多线程能提高CPU的利用率,但增加了开销,会出现死锁与状态同步问题。单线程顺序编程简单易懂,缺点是效率低。

js是单线程运行的,但使用h5中的web workers可以多线程运行。

浏览器运行是多进程运行的。单进程:Firefox,老版本IE;多进程:Chrome,新IE。

定时器

定时器并不能保证真正定时,一般会延迟一点点。
定时器回调函数是在主线程执行的,js是单线程。

如何证明js是单线程执行的?
【alert会暂停当前主线程的执行,同时暂停计时,点击确定后恢复。】
setTimeout()的回调函数是在主线程运行的,定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行。

为什么js要用单线程模式而不用多线程模式?
js的单线程与他的用途有关。作为浏览器语言,js的主要用途是与用户互动以及操作dom,这决定了他只能是单线程,否则会带来很复杂的同步问题。

代码的分类:初始化代码;回调代码。

js引擎执行代码的基本流程:
先执行初始化代码,包含一些特殊的代码;
*设置定时器
*绑定监听
*发送ajax请求
后面在某个时刻才会执行回调代码

setTimeout(function () {
    console.log('aaaaaaa')
}, 2000);

console.log('初始化');//先输出该语句,再执行定时器

事件循环模型

相关文章

  • JavaScript中的原型链与函数等问题

    数据类型 隐式转换 变量转换为字符串、===、==(类型不同,尝试转换类型再比较) 类型检测 typeof NaN...

  • js基础-重新认识call,apply,bind[不看后悔系列]

    函数原型链中的 apply,call 和 bind 方法是 JavaScript 中相当重要的概念,与 this ...

  • JavaScript 复习——原型链

    JavaScript 原型链中的主要知识概念:原型,构造函数,实例,proto,prototype,instanc...

  • JavaScript 原型链(一)

    JavaScript 原型链中的主要知识概念:原型,构造函数,实例,proto,prototype,instanc...

  • 2018-11-22

    JavaScript的面向对象是基于constructor(构造函数)与prototype(原型链)的。 构造函数...

  • 22

    JavaScript的面向对象是基于constructor(构造函数)与prototype(原型链)的。 构造函数...

  • 面向对象2

    JavaScript的面向对象是基于constructor(构造函数)与prototype(原型链)的。 构造函数...

  • OOP-2

    JavaScript的面向对象是基于constructor(构造函数)与prototype(原型链)的。 构造函数...

  • JS进阶(1) —— 人人都能懂的构造函数

    大家都知道原型和原型链是 JavaScript 中最经典的问题之一,而构造函数又是原型和原型链的基础,所以先了解清...

  • 小白对前端诸问题的认识

    javascript中的原型、原型链继承 原型(原型对象)是每function一个函数时,其prototype属性...

网友评论

      本文标题:JavaScript中的原型链与函数等问题

      本文链接:https://www.haomeiwen.com/subject/ybeesctx.html