3-js面向对象基础 原型链进阶

对象的原型链

  • 凡是对象都有原型
  • 构造函数 Person 创建的对象 实例 p 有原型 => Person.prototype 或 p.__proto__
  • Person.prototype 是对象实例, 所以他也有原型 => Person.prototype.__proto__

问题: 原型是什么? 原型是对象。那对象又有原型,如何是个头?

    function Person() {
        // 一旦定义函数 就有了两个部分 构造函数 神秘对象(原型)
        // Person.prototype

        var p = new Person();
        // p 默认连接到 Person.prototype 上
        // 简单的表示为 p -- > p.__proto__
        console.log( p.__proto__ === Person.prototype );//true

        // Person.prototype  Person对象的原型
        // Person.prototype.__proto__ -->  Object.prototype, Person对象的原型的原型, 就是构造函数Object的原型
        // Object.prototype.__proto__ -->  null     构造函数Object的原型的 原型
    }
  • 结论:

    1. Person.prototype 是 实例 p 的原型对象, p 对象 使用 __proto__ 可以访问原型对象
    2. Person.prototype 的 原型对象是 Person.prototype.__proto__
    3. Person.prototype.__proto__ 里的 constructor属性 是 Object, 所以Person.prototype.__proto__ 就是 Object.prototype
    4. Object.prototype.__proto__ 是 null. 因此表明 Object.prototype 就是顶级.
  • 链式
    p –> Person.prototype( p.__proto__ ) –> Object.prototype –> null

Person的原型链结构

02-Person的原型链结构.png

  • 系统内置的原型链

    [] –> Array.prototype –> Object.prototype –> null

    /./ –> RegExp.prototype –> Object.prototype –> null

    … …

{} 对象的原型链结构

  • 在 js 中 对象 一般都有字面量
    123, ‘123’
  • 数组: []
  • 正则表达式对象: /./
  • 函数: function () {}

对象也有字面量: { },对象的原型链结构

{ } –> Object.prototype –> null

注意: {} 与 new Object() 含义相同,相当于用Object构造函数构造出来的实例

数组的原型结构图

数组的原型结构图

继承原型链结构图

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

    Student.prototype = new Person( '张三', 19, '男' );

    var stu = new Student();
    console.log( stu.constructor ); // Person构造函数  详解:自己没有,去原型找,原型是Person实例,Person对象实例也没有,去实例.__proto__找,就是在 Person.prototype 找,找到了,所以是 Person构造函数
    console.log( stu.name );    // 张三  自己没有name属性,去原型找,原型是Person实例,Person实例有 name,找到了,所以是 张三

继承原型链的图

05-继承原型链的图.png

动态函数 Function

动态函数就是在运行的过程中, 将一段字符串作为代码运行.

由于字符串可以随意的拼接. 因此得到动态的执行.

  • 定义动态函数, 并执行
    使用 Function 构造函数, 创建函数对象

    Function 是一个构造函数. new Function 得到 一个函数

    语法:

    new Function( arg0, arg1, …, argN, body )

    Function 的所有的参数, 除了最后一个以外, 都是生成的函数的参数,最后一个参数是 函数体

        /* 求两数和 */
        function getSum( num1, num2 ) {
            return num1 + num2;
        }
        /* 使用 new Function */
        var getSum2 = new Function( 'num1', 'num2', 'return num1 + num2');
        console.log( getSum2( 1, 2 ) ); // 3
        console.log( getSum( 1, 2 ) );  // 3

    由于实例化 Function对象,传的参数是字符串,所以我们可以实现动态执行的效果

        var demo = prompt("请输入你要执行的代码");  // 此处输入 alert(num1 * num2);
        var fuc = new Function( 'num1', 'num2', demo );
        fuc( 100, 200 );

    此处输入 alert(num1 * num2);最终会弹出 20000

    输入 alert(num1-num2); 最终弹出 -100’

    动态函数法一,字符串拼接函数体

        function fn1 ( min, max ) {
            var sum = 0;
            for( var i = min; i <= max; i++ ) {
                sum += i;
            }
            return sum;
        }
        var fn2 = new Function( 'min', 'max', 'var sum = 0;'
                +'for( var i = min; i <= max; i++ ) {'
                +'sum += i;'
                +'}'
                +'return sum;');
        console.log( fn1( 2, 5 ) ); // 14
        console.log( fn2( 2, 5 ) ); // 14

    动态函数法二,利用dom元素保存函数体

        <body>
            <div id='code' style='display: none;'>
                var sum = 0;
                for( var i = min; i <= max; i++ ) {
                    sum += i;
                }
                console.log( sum );
                return sum;
            </div>
        </body>
        <script>
            // 写一个函数, 有两个参数. 例如: num1, num2
            // 那么求 从 num1 累加到 num2
            // fn( 2, 5 )  =>  2 + 3 + 4 + 5 = 14
            var fn3 = new Function( 'min', 'max', tool('code') );
            function tool( idName ) {
                var elem = document.getElementById( idName );
                //var code = elem.innerHTML;  // 会把小括号等转义成 &lt
                var code = elem.lastChild.nodeValue;  // 就是方法体
                elem.parentNode.removeChild( elem );
                elem = null;
                return code;
            }
            fn3( 2, 5 );
        </script>

函数相关的一些参数

arguments

凡是函数调用,都会默认含有一个 arguments 对象,可以看作 “数组”.里面存储着调用时传入的所有参数. 可以使用数组的索引访问这些参数

例如: 写一个函数, 在参数中写任意个参数, 最后求其和

    function sum() {
        // 所有的参数都会存储到 arguments 中
        var sum = 0;
        for ( var i = 0; i < arguments.length; i++ ) {
            sum += arguments[ i ];
        }
        return sum;
    }
    console.log( sum( 1,2,3 )); // 6

取最后一个参数 arguments[ length - 1]
- 案例:利用 arguments 实现混入(扩展)
- 要求一: extend( o ) => 可以将 o 混入到当前对象 this 中
- 要求二: extend( o1, o2 ) => 可以将 o2 混入到 o1 中

      function extend() {
          var args = arguments;
          if ( args.length == 1 ) {
              // 混入到 this 中
              for ( var k in args[ 0 ] ) {
                  this[ k ] = args[ 0 ][ k ];
              }
          } else {
              // 混入到 arguments[ 0 ]中
              for( var k in arguments[ 1 ] ) {
                  args[ 0 ][ k ] = args[ 1 ][ k ];
              }
          }
      }
      var o1 = { name: '张三' };
      var o2 = { age: 19, gender: '男' };
      var o3 = {};
      // extend( o1, o2 );
      o3.extend = extend;
      o3.extend( o1, o2 );   // o1, o2, o3 分别怎么变化?
      // o1 被 o2混入,  o2不变,  o3还是 {}只是多了个 extend方法

函数名.length

函数名.length, 即函数的 length 属性. 表示 定义函数 时, 参数的个数

如果定义函数的时候, 定义了参数. 但是调用的时候又没有传递该参数. 那么该参数在函数内就是 undefined

    function A( a, b ) {}
    console.log( A.length );    // 2

函数.name 返回的是函数名

    function A( a, b ) {}
    console.log( A.name );      // A

函数的引用 callee 和 caller

js 中函数也是一个对象

-> callee 在函数的内部, 它表示 当前函数 的引用

-> caller 表示调用函数

  1. callee,一般在函数内部, 实现函数递归的时候, 我们一般使用 callee 表示函数的引用

        // 语法:arguments.callee 表示当前引用
        function foo() {
            console.log( arguments.callee === foo );
        }
        foo();  // true;
        // 一般在函数内部, 实现函数递归的时候, 我们一般使用 callee 表示函数的引用

    传统递归

        function fn () {
            fn(); // 自己调用自己
        }
        fn();

    新方法,使用 callee 来递归

    由于js是弱类型语言,以防 fn 被乱赋值,如 fn = 0 后,fn()调用 可能出现各种乱七八糟的问题

        function fn() {
            arguments.callee();  // 使用 callee 来递归
        }
        fn();
  2. caller 表示调用函数,就是在被调用函数中, 获得调用函数的引用

        // 语法: 函数名.caller
        function f2 () {
            console.log( f2.caller );
        }
        f2();   // null
    
        function fn() {
            f2();
        }
        fn();   // function fn() { .. }

eval 函数,eval与 Function的使用比较

(1)eval 函数与 Function 功能类似. eval 可以直接将字符串作为代码来执行.

语法: eval( 语句字符串 )

注意, 它与当前代码处于同一个作用域。

(eval很强大,熟悉的情况下用,可以实现很牛的功能,但是不熟悉就使用,会出现很多很危险的漏洞,所以开发中一般不建议使用,甚至禁止使用)

    // 一般书写代码
    // var num = 123;
    // console.log( num ); // => 123

    // 可以直接调用 eval 函数, 来实现字符串代码
    eval( 'var num = 123;' );
    eval( 'console.log( num );' );
    alert( num );   // 123

(2)Function
语法:Function 是用来生成函数中的, 所以如果要执行, 需要调用

    var fn = new Function ( ' alert( "执行 Function" );' );

    // => 生成了 一个函数 function f () { alert ... }

    fn(); // 执行

立即执行函数,又称作 自调用函数

    (new Function ( ' alert( "执行 Function" );' ))();

    (function () {
        alert ( '立即执行函数' );
     })();
     // 立即执行函数又称作 自调用函数,可以有效的控制作用域

json格式,json字符串转对象

json 格式( 严格的国际通用数据表示协议, 结构 )

json 格式 有两种结构 1: { } 2: [ ]

注意: json 格式中, 键名也必须使用双引号括起来.

说明:(在 js 中使用的 json 对象,是 js 一个对象格式, 相对较松散,所以键名不加双引号也不会报错,但是一旦国际化,就十分不规范,所以要括起来)

将字符串变成对象,有三种做法

  1. eval 做法
    var data = '[ { "name": "张三", "age": 19, "gender": "男"}, { "name": "李四", "age": 18, "gender": "女"} ]';
    var o1 = eval( "(" + data + ")" );   // 注意一个习惯. 就是数据两端加上圆括号
  1. Function 做法
    var data = '[ { "name": "张三", "age": 19, "gender": "男"}, { "name": "李四", "age": 18, "gender": "女"} ]';
    var o2 = ( new Function( 'return ' + data ) )();
  1. JSON.parse( ), 使用 ES5 中引入的标准处理 JSON 的语法,要求必须用引号引起来
    var data = '[ { "name": "张三", "age": 19, "gender": "男"}, { "name": "李四", "age": 18, "gender": "女"} ]';
    var o3 = JSON.parse( data );

为何 eval 转换 json格式的字符串 需要使用圆括号???

因为eval 函数,本质是执行 js 代码, 所以隐含的数据里面 { } 实际上是代码块的含义。

所以 json中的 { }读做了代码块的开始和结束,而加了括号以后,里面内容就变成了表达式,就不会出错了。

    // eval 函数,本质是执行 js 代码的
    // json 两种格式,[] 没问题,{} 就会有问题,被看成了代码块
    var s1 = '{ }'; // 空语句,没有任何结果
    var s2 = '{ 12345 }';   // 12345
    var o1 = eval( s1 );
    var o2 = eval( s2 );
    console.log( o1 );  // undefined
    console.log( o2 );  // 12345

这里把 name: 当成了一个标记语言,而 真正的语句 就只有 “张三”,所以就得到字符串

    var s1 = '{ name: "张三" }'
    var o1 = eval( s1 );    // 张三,没有得到对象, 而是得到了一个字符串

标记语言

    label123:
    while ( true ) {
        console.log( '第一层循环开始' );
        while ( true ) {
            console.log( '第二层循环开始' );

            while ( true ) {
                console.log( '第三层循环开始' );
                break label123;
                console.log( '第三层循环结束' );
            }

            console.log( '第二层循环结束' );
        }
        console.log( '第一层循环结束' );
    }
    console.log( 'over' );
    // 结果
    // 第一层循环开始
    // 第二层循环开始
    // 第三层循环开始
    // over

就算加上 ” “变成标准 JSON 格式,也会报错,相当于执行 “name”:”张三”

    var s1 = '{ "name" : "张三" }';
    var o1 = eval( s1 );    // 会报错   Uncaught SyntaxError: Unexpected token :(…)

加了括号以后,里面内容就变成了表达式。所eval要加( );

    var s1 = '{  }';
    var o1 = eval("(" + s1 + ")");  // 空对象

    var s2 = '{ name: "张三" }'
    var o2 = eval("(" + s2 + ")");  // { name: "张三" }

函数的原型链结构

Function可以用来建函数,所以在 js 中 函数 是 Function 的实例

    function Person() {}
    var p = new Person();

p是构造函数 Person 的实例,在该角度上看,函数就是对象,Function就是构造函数,那么我们可以得到 构造-实例-原型 三角形

Person, Function对象, Function.prototype

function Person( ) { };

  1. Person函数 由 Function构造函数对象 构造,

  2. Person函数 . __proto__ => Function.prototype

  3. Function 构造函数对象 . prototype => Function.prototype

函数的三角形图1

06-函数的三角形图-01.png

而Function 是构造函数,它也是函数对象,所以他也是由 Function构造的,所以鸡生蛋还是蛋生鸡的问题来了。。

Function 既是构造函数,也是实例

函数的三角形图2

07-函数的三角形图-02.png

所有函数对象的原型对象时 Function.prototype 函数的原型链结构-(不考虑对象的原型)

08-函数的原型链结构-不考虑对象的原型.png

完整的原型链结构-(包含对象与函数)

09-完整的原型链结构-包含对象与函数.png

instanceof 运算符

-> a of b -> b 的 a

-> instanceof ?

-> 错觉:判断某一个对象是否为某一个构造函数所创建出来的

instanceof 语法:

返回值 boolean, 用法: 对象 instanceof 构造函数

// 判断该对象是否 为 构造函数 的 实例 ? 错误

* 判断 构造函数的 原型属性 是否在对象的原型链上 *

    function Person() {}
    var p1 = new Person();      // 按照原有的原型结构来创建
                                // p1 -> 原来的 Person.prototype -> Object.prototype -> null
    // 设置原型
    Person.prototype = {};  // {} 不在 对象原型链 上
    console.log( p1 instanceof Person ); // false
    function Person () {}
    // 设置原型
    Person.prototype = {};
    var p1 = new Person();  // p1 -> 新的 Person.prototype. 即 {} -> Object.prototype -> null
    console.log( p1 instanceof Person );   // true 就在判断 {} 是否在 p1 的原型链上

通过原型链, 可以重新定义 js 的继承

js 的继承: 就是利用对象的动态特性添加成员, 或直接替换对象的方式修改原型链结构. 使得当前对象的原型链上的对象具有某些成员. 那么我的当前对象就可以使用这些成员了.

p -> Person.prototype -> Object.prototype -> null 在中间任意一处添加方法,p也就有了方法

p -> Person.prototype -> {} -> Object.prototype -> null 或者直接修改原型链,添加一个对象{},添加方法

过多的依赖原型链继承, 会损耗 性能

如果必须使用原型链继承, 最好提供一些快速访问的方法

这样非常损耗性能

    var o = {
        method: function () {
            console.log( '我是一个非常顶级的方法' );
        }
    };
    function Person () {}
    Person.prototype = o;

    function Student () {}
    Student.prototype = new Person();

    var s = new Student();
    s.method();
    // 先找自己,自己没有,
    // 去原型Student.prototype中找,就是Person构造函数的实例, 没找到
    // 再去上面 Person.prototype 中找,就是 o, o中有,终于找到了,返回,可以调用

改良,提供快速访问的方法,避免原型链检索,this.method并不占什么空间,存的是引用,实质函数,还是顶层的函数

    var o = {
        method: function () {
            console.log( '我是一个非常顶级的方法' );
        }
    };
    function Person () {}
    Person.prototype = o;

    function Student () {
        this.method = Person.prototype.method;
    }
    Student.prototype = new Person();

    var s = new Student();
    s.method();
    // 先找自己,自己就有 this.method = Person.prototype.method,直接找到,直接调用。快速访问,提高了性能

html的原型链结构非常的深,它就是采用这种存引用,快速访问的方法,避免了原型链检索,提高了性能

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值