在学JavaScript

163 阅读17分钟

在学JavaScript

主流浏览器

浏览器内核
IEtrident
chromewebkit=>blink(webkit)
safariwebkit
firefoxgecko(壁虎)
operapresto

浏览器历史和JS诞生

  1. 1990
    1. 蒂姆 伯纳斯 李 超文本分享资讯的人 world wide web 移植到C libwww/neus 允许别人浏览他人编写的网站
    2. 1993
      1. 美国伊利诺大学NCSA阻止(马克 安德森)MOSIAC 浏览器显示图片 图形化浏览器
    3. 1994
      1. 马克 安德森和吉姆 吉拉克硅谷 SGI
      2. MOSIAC communication corporation
      3. 网景公司 netscape navigator ->2003
    4. 1996
      1. 微软收购spy glass
      2. IE internet exploror 1.0
      3. IE 3 的诞生出现了第一个脚本语言 Jscript
      4. 网景公司Brendan eich 在NETSCAPE
      5. NAVIGATOR 开发出了livescript
    5. 2008
      1. V8引擎
        1. 直接翻译机器码
        2. 独立于浏览器运行

编程语言

  1. 编译型
    • 翻译过程: 源码->编译器->机器语言->可执行文件
    • 优点
      • 运行速度快
    • 缺点
      • 跨平台需要重新编译
  2. 解释型
    • 翻译过程:源码 -> 解释器 –> 解释一行执行一行
    • 优点
      • 不需要根据不同的平台重新编译
    • 缺点
      • 运行速度慢

脚本语言

动态语言 -> 脚本语言 -> 解释型语言 -> 弱类型语言

静态语言 -> 编译型语言 -> 弱类型语言

  • 脚本语言-> 脚本引擎-> 解释器 前后端都有

    JavaScript: 客户端脚本 JavaScript 解释器是在浏览器上的

    php: 服务端脚本 php 解释器是在服务器上的

    python 可以通过解释器编译成其他类型语言

    • 如Jython,CPython

ECMA

European Computer Manufactures Association(欧洲计算机制造联合会) ECMA - 262 脚本语言的规范 ECMAScript ES5、ES6 规范化脚本语言

单线程=>模拟多线程

​ 单线程 -> 一次只能做一件事

​ 多线程-> 一次可以做多件事

  • javascript 的引擎是单线程的
  1. 轮转时间片
  2. 短时间内轮流执行多个任务片段
  3. 任务1 任务2
  4. 切分任务1 任务2
  5. 随机排列这些任务片段,组成队列
  6. 按照这个队列顺序将任务片段送进js进程
  7. js线程执行一行一个又执行一个

耦合

高内聚,低耦合 模块的单一责任制

一个大块或者一个模块具有强的功能性,具有高的独立性

解耦合

JavaScript重点

  1. ECMAScript
    • 语法,变量关键字,保留字,值,原始类型,引用类型
  2. DOM(document object model) 文档对象模型
    1. W3C 规范增删改查标签
  3. BOM (browser object model) 浏览器对象模型
    1. 没有相应的规范,可以写相容性
      1. 由于每个浏览器的厂商不一样,所以没有相应的规范
    2. 事件

JS的值

  1. 原始值 -> 基本类型
    • Number
    • String
    • Boolean
    • undefined
    • null
  2. 引用值
    1. object
    2. array
    3. function
    4. date
    5. RegExp

判断类型

typeof

console.log(typeof(a)) // 此时的a 没有定义则是 undefined 
console.log(typeof(typeof(a)) // typeof 的返回值是 字符串 

声明变量

括号运算> 普通运算> 赋值

函数

基本函数的写法

// 基本写法 函数声明
function text (参数){
    // 执行语句
}
// 匿名函数表达式 函数字面量
var text = function text1 (){
    // 执行语句
    // 此时的text1 对外不可见
}
// 形参 -> 占位-> 形式上占位 -> 形式参数 -> 形参
function test(a,b){
    // 如果没有传进这个参数则会报错
    // 形参相当于在函数内部声明了 例如 var a 
    console.log( a + b )
}
// 实际参数 实参 => 按顺序  => 换位置无效
// 如果在函数调用实参中传入多余参数 与形参不相等 则 不报错 与形参数量一一对应多余的存入arguments 里
test(11,22)

function test(a,b){
    b = 3
  	console.log( arguments[1] )
    // 此时打印的为undefuned 
    // 如果是参没给传进来值 => b=undefined => undefined 无法给赋值
    // 如果在实参中传入值 -> 可以在函数内部修改值 -> 如果没用在实参中传入值 -> 则在函数内部修改值是没法用的
}
test(1)

function test(a,b){
    a = 3
  	console.log( arguments[0] )
    // 此时的 a 与 arguments[0] 是不是一个东西
    // 此时的 a 在栈内存中存储   arguments 在堆内存中存储
    // arguments 与实参中的值是一一一对应关系 映射关系 如果实参中的值没用的话 那么与arguments 建立不上连接 他怎么修改时没有用的 反而 如果实参中有的话 在函数内部将会映射到 arguments 里面 则 a 修改 arguments[0] 也会变 因为时一一对应关系
}
test(1,2)

每个函数必须有return 如果不写return的话js引擎默认在函数末尾添加return

return 的用法

  1. 终止函数执行,想在哪终止return写在哪
  2. 返回任意值

全局变量VS局部变量

  • 在全局声明叫做全局变量
  • 在函数内部声明叫局部变量
    • 函数内可以使用全局变量 全局不可以调用函数内变量

函数式编程

  • 一个固定的功能或者是程序被封装的过程,实现一个固定的功能或者程序,在这个封装体中

需要一个出口和一个入口

  • 入口就是参数,出口就是返回值

函数初始化参数

初始化参数 默认值:undefined

// a = 1 为 es6 的语法 
function test(a=1,b){
    console.log(a,b)
    // 此时打出来的 a 是 1 b 是 22 
    // 因为 arguments 跟形参是映射关系一一对应,如果 arguments 里面的值为 undefined 则会取实参中的值 那么反过来 如果实参中的值是undefined 则会取 arguments 里面的值 
}
test(undefined,22)

预编译

  1. 检查通篇的语法错误
  2. 预编译过程
  3. 解释一行,执行一行

函数声明整体提升,变量只有声明提升,赋值不提升

暗示全局变量

imply global variable

// 如果一个变量直接赋值了 没有声明则会挂载到 全局对象window上
b = 22 
console.log(window.b) 

AO activation object

活跃对象,函数执行上下文

  1. 创建了一个AO对象,寻找函数里面的形参和变量声明
  2. 寻找实参中的参数值赋值给形参
  3. 寻找函数体内函数声明
  4. 执行
function fn (a){
    var a = 1
    function a (){}
}
fn(2)
AO = {
    // a:undefined
    // a:2
    a : function a (){}
}
  • AO在预编译的时候会默认生成一个this这个this默认指向window,在进行形参赋值的时候会生成arguments
function fn (a){
    this.num = 3
   	var b = 4
    function d (){
        
    }
}
fn(2)
AO = {
   this : window
   a : undefined => 2
   arguments : [2] 
   b : undefined
   d : function (){}
}
  • 构造函数,实例化后的AO
function Test (){
    // var this = {
    //    name : 张三,
    //	  __proto__ : prototype
    // }
    this.name = '张三'
    // 隐士 return this
}
var t = new Test()
// 当这个函数 new 的时候 相当于在这个函数的开头声明的一个 this,这是这个 AO 的 this 已经被覆盖了 所以由 window 变成了一个对象 
GO = {
    t : undefined => {...}
    Test: function (){...}
}
AO = {
    this : window => {
        __proto__ : prototype
        name:'张三'
    }
}
                     

如果AO中没有这个值则会去GO里面找

GO global object

全局执行上下文

  1. 寻找变量声明
  2. 找函数声明
  3. 执行

GO ===window

var fn1 = 123
function fn1(){}

GO:{
     // fn1 : undefined
    fn1 : function fn1(){}
}

为什么要了解AO和GO

因为要知道作用域 作用域链相关产生的一切问题

  • AO => function 独立的仓库

对象

var o ={
    name:'zs',
    age:18,
    like:function(){}
}
// 对象有属性和方法
function test (a,b){}
// 函数也是一种对象类型 引用类型 引用值 
// test.name test.length test.prototype
// 对象 => 有些属性是我们无法访问的 
// 		  JS引擎内部固有的隐式属性
// [[scope]]
// 1. 函数创建时,生成的一个JS内部的隐士属性.
// 2. 函数存储作用域链的容器,作用域链 => 存储了 
//			AO/GO
//			AO,函数执期上下文
//			GO,全局执行器上下文
//			函数执行完成后,AO是要销毁的,也就是说每一执行函数的时候都是一个新的AO,老的AO在执行完函数后被销毁了,AO是一个即时存储的容器
		
作用域与作用域链

[[scope]] => [[scope.chain]]

  • 每一个函数被定义的时候,系统生成了[[scope]]属性 [[scope]]保存该函数的作用域链,该作用域链的第0位存储当前环境下的全局执行器上下文GO,GO里储存全局下所有的对象
    • 也就是说每一个函数定义的时候,就已经包含了GO全局执行期上下文
  • 函数被执行是的前一刻作用域顶端的第0位,储存函数生成的函数执行期上下文AO,同时第一位存储的是GO,查找作用域链式从上到下依次查找
    • 也就是说当函数执行的的前一刻必然作用域中存在一个AO和一个GO
  • 函数体内函数被定义时,是在上个函数环境下,所以函数体内的函数这时的作用域就是外层函数体被执行期的作用域链
  • 当函数体内函数执行时(前一刻),生成函数b的[[scope]],储存函数体内函数的作用域链,顶端第0位存储函数内函数的AO,外层函数的AO和全局的GO依次向下排列
  • 当函数体内函数执行完毕后他的AO会被销毁,作用域链回到定义时的状态
闭包
  • 当内部函数被返回到外部并保存时,一定会产生闭包,闭包产生原来的作用域链不释放,过渡的闭包可能回导致内存泄漏,或加载过慢

立即执行函数

中文名立即执行函数=> 英文名 IIFE immediately-invoked function expression

自动执行,执行完成以后立即释放,立即执行函数 =>初始化函数

(function(){})();
(function(){}()); W3C 建议这样写

一定是表达式才能被执行符号执行 ()=>也叫函数执行符=>括号括起来的任何东西 不是表达式也会被堪称表达式

函数声明变成表达式的方法 + - ! || && 在函数前加了之后 会立刻变成函数表达式 并且去掉函数名称如:

+ function test (){ // 代码块 }()

逗号运算符

括号里的逗号运算符

 var num = (1,2) 
 console.log(num)
 => 打印结果为 2

也就是说逗号运算符 返回的结果是最后一个

对象

  • 对象字面量

    var obj = {} // 对象字面量
    obj.name = 'zs' // 对象直接量
    
  • 系统自带的构造函数

    var obj = new Object() // 通过系统自带的构造函数构造对象 === 对象字面量
    obj.name = 'zs' // 对象直接量
    
  • 自定义构造函数

    function Car (){
        this.color = 'red'
    } 
    var car =  new Car() // 通过 new 实例化函数 如果 new 这个 this 指向的就是实例化对象 也就是这个 car
    Car() // 没有 new 的情况下执行了这个函数那么他在AO中的this储存的是window 
    
    • 当这个Car 被实例化的时候,相当于普通函数被执行了,那么必然产生AO,AO一产生系统看到你在构造对象了那么会立即保存一个this={}AO里去,当你new的时候构造函数的代码块都已经跑完了,当然他的this指向的是car也就是说实例对象,所以通过car.color 可以访问到this里的color,所以他必然在函数末尾隐士的加上了 return this

    • 在构造函数末尾 return 原始值 则无效,如果return 引用值 那么 这个构造函数则是 return 出来的那个

      AO = {
          this:{}
      }
      function Car (){
          // this = {
          // color = 'red'
      	// }
          this.color = 'red'
          // return this
      } 
      var car =  new Car() 
      
  • 对象的第二种调用方法

    如果我们在特殊条件下 需要 进行变量名称 或者字符串拼接调用我们可以 obj[var] 来调用

    // 其实在最早期JS引擎没有点语法的时候,通常使用 obj['name']来调用,当有了点语法后我们调用 obj.name => 默认转换为 obj['name'] 来调用
    var obj = {
        name1 : '张三1',
        name2 : '张三2',
        name3 : '张三3'
    }
    obj[ 'name' + 1 ]
    obj[ 'name' + 2 ]
    obj[ 'name' + 3 ]
    

包装类

原始值 => 简单类型是没有属性和方法的

var num = 11;
num.len = 3;
console.log(num.len) => undefined
// 他在中间进行了 new Number(num).len = 3 然后发现 num 是原始值保存不了 所以 内部 delete 了 在打印出来就是 undefined 因为 num 的 len 被删除了
var str = '我爱你呀'
str.length
console.log(str.length) 
// 那么为什么str 也是原始值它可以拥有length 属性呢 那是因为 他在调用的时候 默认调用了 New String(str).length => 这样他就是对象了 他就可以拥有属性跟方法啦数组截断
数组截断
var arr = [1,2,3,4,5,6]
arr.length = 2
console.log(arr) => 打印结果 [1,2] 
我们看到数组被截断了
字符串
var str = '123456'
str.length = 2
console.log(str) => 打印结果 '123456'
我们看到这样的话字符串是无效的,为什么呢 他的执行过程是这样的 => 
	new String(str).length = 2 => 执行后 他发现 length 没有地方保存 所以就 delete 掉了
	所以在打印str就是 => '123456' 

构造函数

  • 沿着proto一层一层寻找属性的链条,我们叫做原型链

  • 原型链最重要的属性proto

  • 当我们沿着proto向上寻找属性的时候,形成了一个链条,这个链条,叫做原型链

  • 所有原型的终点都是Object.prototype

    • Object.prototype下面有一个toString方法
  • 普通函数默认返回undefined构造函数被实例化之后返回this

  • 无法直接修改构造函数中的原始值,如果直接修改的话会

    proson.num++ => person.num = proson.prototype.num + 1
    
  • 如果是引用类型则可以修改 但是不推荐

  • {}new Object()的区别

    • 他俩是一个东西,他们两个的原型 ===相等
  • 原型的原型一定是Object构造出来的

  • new的时候做了什么

    实例化obj2
    调用构造函数的Obj的初始化属性和方法
    指定实例对象的原型
    
  • __proto__是系统自制的,可以更改他,但是不能自造他

  • 原型在构造函数之上,又是构造函数的一个属性

  • 实例对象继承了构造函数的原型,所以能够通过原型访问到属性

创建原型Object.Create

  • 不是所有的对象都继承Object.prototype

Call && Apply

继承

  • 普通继承

    function Teacher() {
        this.name = '张三'
    }
    Teacher.prototype.tSkill = 'JS/JQ'
    const t = new Teacher()
    console.log(t)
    // ---------------
    function Student() {
        this.name = '李四'
    }
    Student.prototype = Teacher.prototype
    Student.prototype.age = 18 
    const s = new Student()
    console.log(s)
    
    • 此时我们发现 修改了stuprototype,teacherprototype也跟着修改了,我们接下来想到了利用中间件来继承,也就是圣杯继承
  • 圣杯模式 => 继承

function Teacher() {
    this.name='张三'
}
Teacher.prototype = {
    tSkill : 'js/jq'
}
function Buffer() {

}
Buffer.prototype = Teacher.prototype
const buffer = new Buffer() // 此时的buffer 是空的 
function Student() {
    this.name = '李斯特'
}
Student.prototype = buffer
Student.prototype.sSkill = 'HTML/css' // 添加到buffer里面去,而不是buffer 的原型中 因为继承的是buffer 每个构造函数都是一个继承的过程 他继承了他的祖先 也就是 prototype
const student = new Student()
console.log(student)
  • 圣杯继承 => 模块化

    function Teacher() {
        this.name = '张三'
    }
    Teacher.prototype = {
        tSkill : 'JS/JQ'
    }
    function Student() {
        this.name = '李四'
    }
    function Buffer() {}
    Buffer.prototype = new Teacher()
    Student.prototype = new Buffer()
    
    
    
    function inherit(Target,Origin) {
        function Buffer () { }
        Buffer.prototype = Origin.prototype
        Target.prototype = new Buffer()
        Target.prototype.constructor = Target
        Target.prototype.super_class = Origin
    }
    inherit(Student,Teacher)
    var student = new Student()
    console.log(student)
    
    
    var inherit = (function () {
        var Buffer = function () {}
        function inherit(Target,Origin) {
            Buffer.prototype = Origin.prototype
            Target.prototype = new Buffer()
            Target.prototype.constructor = Target
            Target.prototype.super_class = Origin
        }
        return inherit
    })();
    
    var inherit = (function () {
        var Buffer = function () {}
        return function (Target,Origin) {
            Buffer.prototype = Origin.prototype
            Target.prototype = new Buffer()
            Target.prototype.constructor = Target
            Target.prototype.super_class = Origin
        }
    })();
    inherit(Student,Teacher)
    Student.prototype.age = 18
    var student = new Student()
    console.log(student)
    

枚举

一组有共同数据的集合叫做枚举

  • 什么是遍历,在一组数据中,按顺序一个一个获取其信息的过程,叫做便利

  • JavaScript中的枚举就是对象

    • 提到枚举两个字首先想到的就是遍历
    var o = {
        name : 'zs',
        age : 18
    }
    for(var key in o) {
        consoloe.log(o.key) => 为什么是undefined? 因为在JavaScript引擎中默认把 o.key 转换为 o['key'] 所以他是undefined
    }
    
  • hasOwnproperty 寻找对象中的属性再不在自身

    var o = {}
    // 参数为 key
    o.hasOwnproperty('name') // 返回 Boolean
    // 主要目的排除对象中原型上的属性
    
  • in 判断这个key存不存在对象中 => key 必须为字符串

    'name' in obj  => 隐士再找 obj['name']
    
  • instanceof 判断 a 的原型上存不存在b的原型

    [] instanceof Object // true
    

This 指向

  • 全局this指向window
  • 预编译函数 this 指向window
  • 构造函数this指向实例化对象
  • call/apply 改变this指向

callee和caller

注意 一下方法 严格模式中无法使用

  • calleearguments对象下的一个方法 返回的是当前执行的函数
  • caller是函数的一个方法,返回当前被调用函数的函数引用

typeof 可以返回哪些值

  • number/string/boolean/object/undefined/function
  • typeof null 返回的是 object

自定义封装typeof

我们可以用 Object.prototype.toString.call 来进行自定义的typeof 封装

    function myTypeof(type) {
        const types = {
            '[object Null]' : 'type-null',
            '[object Undefined]':'type-undefined',
            '[object Number]':'type-number',
            '[object String]':'type-string',
            '[object Boolean]':'type-boolean',
            '[object Function]':'type-function',
        }
        return types[Object.prototype.toString.call(type)]
    }

    console.log(myTypeof(null))

数据类型深入

null == undefined // true
null === undefined // flase
parseInt('1a') == 1 // true
isNaN("1000") // false


function isNaN(num){
    var result = Number(num) + ''
    if(result === 'NaN'){ // 直接判断 NAN 是不对的 因为他不与任何值相等
        return true
    }else{
        return false
    }
}

三目运算符

优点: 简洁,方便,自带返回值,缺点:不宜维护,无法注释

var num = 1,
    str = ''
str = num > 10 ? '大于十' : '小于十' // 有返回值,并且可与嵌套

面试题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    function Foo() {
        getName = function () {
            console.log(1)
        }

        return this
    }
    Foo.getName = function () {
        console.log(2)
    }
    Foo.prototype.getName = function () {
        console.log(3)
    }
    var getName = function () {
        console.log(4)
    }
    function getName() {
        console.log(5)
    }


    /**
     * GO : {
     *     getName: undefined => getName(5) => getName(4)
     *     Foo: Foo(){...}
     *
     * }
     * Foo.getName => 动态给对象添加了一个方法 方法名为 getName
     */

    Foo.getName() // 调用的自己动态添加的方法 getName 打印结果为 => 2
    getName() // 可以看到 当前GO 中的 getName 是 变量赋值的那个 也就是  => 4
    /**
     * GO : {
     *     getName: undefined => getName(5) => getName(4) => getName(1)
     *     Foo: Foo(){...}
     *
     * }
     */
    Foo().getName() // Foo 被调用之后 发现里面有一个 getName getName 为暗示全局变量 也就是说 把 GO 中的 getName 在进行赋值 赋值为 当前这个函数 打印结果为 => 1
    new Foo.getName() // 当 new 的时候 后面的函数已经跑完了 所以 Foo.getName() => 2
    new Foo().getName() // 当 Foo() 的时候 发现前面 new 了 他还默认把 this 指向 指为 他的实例 也就说 说 AO 中默认保存的 this 是他的实例了 在函数 末尾默认返回的 this 是他的实例 this.getName => 3
    new new Foo().getName() // 当new 过之后在new 是无效的 => 3

</script>
</body>
</html>

浅拷贝和深拷贝

浅拷贝为拷贝原始值跟地址,深拷贝则把地址跟原始值全部拷贝过来

  • 浅拷贝
function clone(origin,target) {
    target = target || {}
    for (const key in origin){
        if(origin.hasOwnProperty(key)){
            target[key] = origin[key]
        }
    }
    return target
}

console.log(clone(obj))
  • 深拷贝
function deepClone(origin, target) {
    target = target || {}
    const toStr = Object.prototype.toString,
          arrStr = '[object Array]'

    for (const key in origin) {
        if (origin.hasOwnProperty(key)) {
            if (typeof (origin[key]) === 'object') {
                if (toStr.call(origin[key]) === arrStr) {
                    target[key] = []
                } else {
                    target[key] = {}
                }
                deepClone(origin[key],target[key])
            } else {
                target[key] = origin[key]
            }
        }
    }
    return target
}

数组

数组就是另外一种对象的形式,我们可以看到使用对象调用的方式同样可以调用数组 例如 obj[0] arr[0],在js 中 obj.name 最终回被底层转换为 obj['name']

// 数组的三种声明方式
var arr1 = [] // 通过数组字面量生成方式声明
var arr2 = new Array() // 通过系统构造函数方式声明
var arr3 = Array() // 通过数组函数方式声明
// 当我们打印出来的时候 发现打印的结果一样 并且 他们的的constructor 都是Array 也就证明他们的 __proto__ 来自于 Array的prototype
--------------------------------------
// 对象的三种声明方式
var obj1 = {} // 通过数组字面量生成方式声明
var obj2 = new Object() // 通过系统构造函数方式声明
var obj3 = Object()// 通过数组函数方式声明
// 与数组的结果一样 
  • 稀松数组

    var arr = [,2,3,,5,6,,] // 默认会把逗号最后一位省略掉 这种数组叫做稀松数组
    var arr = new Array(1,2,3,4,5) // 通过系统构造函数创建数组,在参数中不允许出现 `,,` 这种情况 因为他是函数的参数,如果第一个参数写为数组类型并且没有后面的参数会创建第一个参数长度的数组
    
  • 基础数组增改

    var arr = [1,2,3,4,5]
    arr[5] = 5 // 为增给下标为5的增加值 为5
    arr[4] // 为查 如果差不多是 undefined 为什么因为数组是对象的一种展示方式
    arr[3] = 99 // 改 改下标为3的值为99
    
  • 数组方法

    • 改变原数组
    var arr = [1, 2, 3]
    // push unshift 
    // push 为从末尾推 unshift 为从开始插 他们两个的返回值都是数组加工后的长度
    

arr.push(55,66,77) // push的作用是向数组后面推入数据,返回值是推入数据后的长度 // pop shift // pop 为从末尾弹出一位, 返回值是弹出的那一位 // shift 为从数组起始位置弹出一位, 返回值是弹出的那一位 // 没有参数,传入参数无效 arr.pop() // reverse 反转数组 // 返回值为反转完的数组 arr.reverse() => [3, 2, 1] // sort 排序 // 默认排序为 数组的每一项进行 ascii码进行排序 // 可以传入一个函数,必须有两个参数, a b // 负数为 a 在前 // 正数为 b 在前 // 0 不动 arr.sort()


- 自定义数组方法

- push

  ```js
  Array.prototype.myPush = function () {
      for (var i = 0; i < arguments.length; i++) {
          this[this.length] = arguments[i]
      }
      return this.length
  }
  ```

  

- unshift

  ```js
  Array.prototype.myUnshift = function () {
      for (var i = arguments.length - 1; i >= 0; i--) {
          this.splice(0, 0, arguments[i])
      }
  
      return this.length
  }
  ```