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

来源:互联网 发布:绝地求生优化教程 编辑:程序博客网 时间:2024/06/06 20:20

对象的原型链

  • 凡是对象都有原型
  • 构造函数 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
原创粉丝点击