美文网首页
JavaScript

JavaScript

作者: hellomyshadow | 来源:发表于2020-05-31 17:33 被阅读0次

    JavaScript是一种专为网页交互设计的脚本语言,国际标准是ECMAScript
    由三部分组成:

    • ECMAScript(ECMA-262定义)提供核心语言功能

    • 文档对象模型(DOM)提供访问和操作网页的方法和接口

    • 浏览器对象模型(BOM)提供与浏览器交互的方法和接口
      JavaScript的这三个组成部分在当前五大主流浏览器中都得到了不同程度的支持(IE、FireFox、Chrome、Safari、Opera)
      基本所有的浏览器都大体上支持ECMAScript第三版,但对于DOM和BOM的支持相对比较而言则差很多。
      JavaScript是一种可以与HTML标记语言混合使用的脚本语言,在浏览器中解释执行,是一种解释型语言(预编译、执行)
      引用数据类型:Object
      Object的每个实例都具有下列属性和方法:

    • constructor:保存着用于创建当前对象的函数,即构造函数。

    • hasOwnProperty(propName):用于检测给定属性在当前对象实例中(而不是原型中)是否存在。

    • 原型对象.isPrototypeOf(obj):判断实例对象obj的原型是不是当前原型对象。

    • propertyIsEnumerable(propName):用于检查给定属性是否能够用for-in语句来枚举。

    • toLocaleString()、toString()、valueOf()
      Global对象,一个全局的、并不存在的对象,其内部定义了一些属性和方法:

    • encodeURI()、encodeURIComponent()、decodeURI()、decodeURIComponent() --> 对URI编码、解码

    • eval():把字符串转为JS脚本,无形中的JavaScript解析器

    • parseInt()、parseFloat()、isNaN()

    • escape()、unescape() --> 编码、解码

    函数的三种定义方式:

    • function语句形式
      function test() { return 10; }
    • 函数直接量
      var test = function() { return 10; }
    • Function构造函数
      var test = new Function('return 10;');
    函数定义的对比.jpg

    arguments:函数内部的一个伪数组,保存了所有实参;
    arguments.length:实参的个数
    函数名.length:形参的个数
    arguments.callee:指向函数体。函数名也指向函数体。
    函数名的指向可能发生变化,所以在函数内部通常使用 arguments.callee 自我访问;
    //判断参数个数
    function test(a, b) {
    if(arguments.length === arguments.callee.length) {
    ...
    } else {
    console.log('实参与形参的个数不匹配!');
    }
    }
    //递归调用
    function fack(num) {
    if(num <= 1) {
    return 1;
    } else {
    return num * arguments.callee(num-1);
    }
    }
    //然而,在严格模式下,是不允许访问arguments中的属性的!

    call()、apply() 真正强大的地方是,能够扩充函数赖以运行的作用域,实现对象与方法解耦合

    闭包
    使用闭包时,必须严格清楚作用域链
    本质上,闭包就是一个函数的返回值也是一个函数,且内函数访问了外函数的变量,外函数的作用域不会释放!
    特性:封闭性,类似于静态语言中的 private,起到了保护变量的作用
    function maker(a) {
    return function() {
    return a++;
    }
    }
    var counter = maker(1);
    counter(); // 1
    counter(); // 2
    counter(); // 3

    JavaScript模拟类的两种简单方式

    • 工厂模型
      function createPerson(name, age) {
      var obj = new Object();
      obj.name = name;
      obj.age = age;
      obj.say = function() {
      console.log(this.name, this.age);
      }
      return obj;
      }
      var p1 = createPerson('Mack', 18);
      var p2 = createPerson('Any', 20);
    • 构造函数形式
      function Person(name, age) {
      this.name = name;
      this.age = age;
      this.say = function() {
      console.log(this.name, this.age);
      }
      }
      //静态属性/方法:只能通过构造函数名调用,实例对象无权调用!
      Person.num = 12;
      Person.getNum = function() { console.log('static getNum') };

    var p1 = new Person('Mack', 18);
    p1.constructor === Person // true

    函数内部的 this 指向其调用者,所以把构造函数当作普通函数调用时,其this将指向window
    Person('Mack', 18); //相当于把name='Mack',age=18,say()挂载到window对象上

    原型:prototype
    每个函数都有一个prototype属性,总是指向一个对象,用于存放所有实例共享的属性和方法;
    var p2 = new Person('Any', 20);
    p2.say === p1.say // false
    这是因为每一次通过 new 创建实例时,都会重新执行一次构造函数Person,生成新的属性和方法体;
    原型可以解决实例的共享属性和方法
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    Person.prototype.say = function() {
    console.log(this.name, this.age);
    }
    var p1 = new Person('Mack', 18);
    var p2 = new Person('Any', 20);
    p2.say === p1.say // true

    构造函数.prototype === 原型对象
    原型对象.constructor === 构造函数(模板)
    Function.prototype 也指向一个对象,但 Object.prototype 不是指向对象!

    Object.getPrototypeOf():ECMA5新特性,获取实例对象的原型对象
    Object.getPrototypeOf(p1) === Person.prototype; // true
    Person.prototype.getPrototypeOf(p1); // true

    in 操作符与hasOwnProperty():
    in 会在对象的原型链上查找,而hasOwnProperty()只会在当前对象中查找
    Person.prototype.sex = '男';
    'sex' in p1 // true
    p1.hasOwnProperty('sex'); // false

    Object.keys():ECMA5新特性,获取对象的可枚举的属性(方法)名,不包括原型链上的;
    Object.getOwnPropertyNames():与Object.keys()功能相同,但不区分是否可枚举;
    Object.keys(p1) // ["name", "age"],也不包括不可枚举的(如)
    Object.keys(Person.prototype) // ["say"]
    Object.getOwnPropertyNames(Person.prototype) // ["contructor", "say"],contructor属性不可枚举

    //简单原型
    function Person(){ }
    Person.prototype = {
    constructor: Person, // 手动添加原型对象的构造器
    name: 'Mack',
    say: function(){ }
    }
    这种方式修改了原型对象的指针指向,手动添加的构造器constructor与其他属性没有任何区别,也是可以枚举的!
    ECMA5新特性提供了添加原型对象的属性的方法:Object.defineProperty()
    Person.prototype = {
    name: 'Mack',
    say: function(){ }
    }
    Object.defineProperty(Person.prototype, 'constructor', {
    enumerable: false, //设置添加的属性constructor 不可枚举
    value: Person
    });

    //原型的动态性:修改原型对象的指针与创建实例的顺序性
    function Person(){ }
    var p = new Person();
    Person.prototype.say = function(){
    console.log('say');
    }
    p.say(); //say 原型指针不变,实例通过原型链可以找到原型对象上的属性/方法
    与之对比:
    function Person(){ }
    var p = new Person();
    Person.prototype = {
    constructor: Person,
    say: function(){ }
    }
    p.say(); // error 原型指针指向发生了变化,但实例仍在旧的原型链上查找

    原型对象中的属性能够被所有实例所共享,但实例不能修改共享属性的指向,否则相当于为自己添加同名属性。
    function Person(){ }
    Person.prototype.name = 'Mack';
    var p1 = new Person();
    p1.name = 'Any'; // p1 -> { name: 'Any' }
    var p2 = new Person();
    console.log(p2.name); // Mack
    如果共享属性是引用类型,那么一个实例修改了其内容,所有实例也将修改,这也是原型的最大问题。
    原型对象中的属性(方法)被所有构造函数的实例所共享,类似于Java中的static,所以通常使用构造函数+原型的模式来模拟类
    function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    }
    Person.prototype.work = function(){
    console.log(this.job);
    }

    动态原型模式:把代码都封装到一起
    function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    //动态原型方法
    if(typeof this.work != 'function') {
    Person.prototype.work = function() {
    console.log(this.job);
    }
    }
    }
    //稳妥构造函数式:durable object(稳妥对象)
    最适合在安全环境中使用,常用于对安全级别要求很高的程序中
    特点:没有公共属性,方法内不能引用this
    function Person(name, age) {
    var obj = new Object();
    //定义私有变量和函数(private),通过方法去访问
    var durableName = name;
    obj.getName = function() {
    return durableName;
    }
    return obj;
    }

    隐式原型:proto
    prototype 称为显示原型
    在创建实例对象时,JS会为其添加一个 proto 属性,指向构造函数的原型对象
    实例对象.proto === 构造函数.prototype === 原型对象
    函数也有隐式原型,而且所有函数(包括Object和Function)都是Function的实例;
    Person/Object/Function.proto === Function.prototype
    原型链
    显示原型对象中也有隐式原型 proto,指向当前构造函数的父构造函数的原型对象;
    原型链由 proto 构成,在访问对象的属性/方式时,会沿着原型链逐层查找
    而原型链的尽头就是Object.prototype,因为Object是JS的顶层父类,所有的对象/构造函数,包括Function,都直接或间接派生自Object
    Object.prototype.proto === null
    p.show() -> Person -> p.proto -> Person.prototype -> Person.prototype.proto -> ... -> Object.prototype
    A instanceof B 的判断的标准:如果B的显示原型在A的原型链上,则返回true.

    JS的继承
    function Sup(name) {
    this.name = name;
    }
    Sup.prototype.show = function() { console.log('Sup show') }

    原型继承:让一个类型的原型指向另一个类的实例,就能实现简单的JS继承
    function Ted(age) {
    this.age = age;
    }
    Ted.prototype = new Sup('Father');
    //1. 此时,Ted的原型对象中将包含一个指向另一个原型的指针
    //2. 相应地,另一个原型中也包含指向另一个构造函数Sup的指针,那么Ted的原型对象的构造器也就变成了Sup的构造器
    Ted.prototype.constructor // Sup
    var t = new Ted(20)
    t.name // Father
    t.show() // Sup show
    // t.proto -> Ted -> Ted.prototype -> new Sup('Father')
    //原型继承的特点:即继承了父类的模板,又继承了父类的原型对象
    问题在于:父类的属性不能通过 new 子类进行初始化赋值

    类继承:借助构造函数的方式继承,只继承模板,不继承原型对象
    function Ted(name, age) {
    // 绑定父类的构造函数(模板)
    Person.call(this, name)
    this.age = age;
    }
    var t = new Ted('Mack', 22)
    t.name // Mack
    但是,因为没有继承父类Sup的原型链,所以不能访问其原型链上的属性/方法
    t.show() // error

    混合继承
    function Ted(name, age) {
    // 1.借助构造函数继承
    Sup.call(this, name);
    this.age = age;
    }
    // 2.原型继承
    Ted.prototype = new Sup();
    // 3. 修正 Ted 原型对象的构造器
    Ted.prototype.constructor = Ted;
    // 隐式原型构成的原型链:Ted.prototype.proto = new Sup().proto = Sup.prototype

    混合继承的缺点:继承了两次父类模板(Sup.call(),new Sup())

    如何做到只继承一次父类模板
    function extends(ted, sup) {
    //参数sup、ted分别表示父类和子类
    // 1.创建一个空函数,目的是中转
    var F = new Function();
    // 2. 空函数的原型对象a指向父类sup的原型对象
    F.prototype = sup.prototype;
    // 3. 原型继承F,间接继承了父类sup的原型对象
    ted.prototype = new F();
    // 4. 修正子类ted的构造器指向
    ted.prototype.constructor = ted;
    // 5. 自定义一个子类ted的静态属性,保存父类sup的原型对象,便于解耦,也便于获取父类的原型对象
    ted.super = sup.prototype;
    // 6. 加保险:判断父类原型对象中的构造器
    if(sup.prototype.constructor == Object.prototype.constructor) {
    sup.prototype.constructor = sup;
    }
    }
    function Sup(name, age) {
    this.name = name;
    this.age = age;
    }
    Sup.prototype.show = function() { console.log('Sup show') }

    function Ted(name, age, sex) {
    // 通过自定义的静态属性获取父类的原型对象,进而获取其构造器,实现模板继承
    Ted.super.constructor.call(this, name, age);
    this.sex = sex;
    }

    //让 Ted 继承 Sup
    extends(Ted, Sup);

    // 在子类的原型对象上添加与父类的原型上同名的方法
    Ted.prototype.show = function() { console.log('Ted show') }

    var t = new Ted('Jack', 22, '男')
    t.age // 22
    t.show() // Ted show
    Ted.super.show() // Sup show

    相关文章

      网友评论

          本文标题:JavaScript

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