美文网首页
JavaScript-基础2

JavaScript-基础2

作者: zhulichao | 来源:发表于2020-07-23 09:19 被阅读0次

最近参加了一次为时两天的JavaScript培训,回顾课程内容并进行整理,其中参考了如下两篇老师的文章。

JavaScript编程技法入门
JS编程道场

简介

函数是第一等公民。
JavaScript的核心是 函数 和 对象 。

typeof Number // 'function'
typeof String // 'function'
typeof Function // 'function'

简化版jquery选择器实现

通过对javascript原生操作的封装,实现简化版的jquery类库。这里的代码并不复杂,个人感觉主要是要有类库的概念和实现思路,考虑将项目中使用的工具类逐步封装成类库。

var $ = function(selector) {
    var domObj = document.querySelector(selector);
    var result = {
        '0': domObj,
        show: function() { domObj.style.display = 'none'; },
        hide: function() { domObj.style.display = 'block'; },
        append: function(str) {
            domObj.innerHTML += str;
        },
        val: function(value) {
            if (value === undefined) {
                return domObj.value;
            } else {
                domObj.value = value;
            }
        },
        on: function(action, callback) {
            // 特定浏览器,这里是针对Chrome的写法
            if (domObj.addEventListener) {
                domObj.addEventListener(action, callback);
            }
        }
    };
    return result;
}

对象

js是解释型动态语言,可随时添加属性或方法,很灵活。

var obj1 = new Object();
obj1.name = 'object1';
obj1.getName = function() {
    return this.name;
};

推荐使用如下字面量形式的定义方式。

var obj2 = {
    name: 'object2',
    getName: function() {
        return this.name;
    }
};

遍历对象的属性和方法。

for(var key in obj2) {
    console.log(key + '=' + obj2[key]);
}

函数

函数的三种定义方式如下:

// 匿名函数,通过一个变量来指向一个匿名函数,当这个函数不再需要时,JS的运行时环境会自动收集内存垃圾
var func1 = function(name) {
    return 'hello,' + name;
};
// 具名函数,有变量提升,也就是在这段代码的前面是可以调用func2方法的
function func2 (name) {
    return 'hello,' + name;
};
// 函数对象,这也表明了函数其实是Function类型的对象
var func3 = new Function('name', 'return "hello," + name');

函数体的内部只有在函数被调用时才会进入,下面demo的运行结果是先后输出1、2、3、4、5。使用Chrome调试断点进入时,打开Sources注意观察Scope的变化,可以看到运行到func1()时Scope只有Globle,当进入到调用的方法时,会有Local,里面是函数内部定义的变量或方法,当函数内部用到了外面的变量时,会有Closure,里面是引用的外部变量。

console.log(1);
var func1 = function() {// 进来之后有Local
    console.log(3);
    var a = 0;
    function func2() { // 进来之后有Closure
        console.log(5);
        var f = a + 5;
    }
    console.log(4);
    func2();
}
console.log(2);
debugger;
func1(); // 这时只有Global
运行顺序 Chorme调试

作用域

this取决于在哪调用,而不是在哪定义,通过“对象.”调用的,this都是这个对象,直接调用的this都是window。

var func = function(name) {
    this.name = name;
};
func('jobs'); // 进入func,this是window
var o = {
    setName: func
};
o.setName('jobs'); // 进入func,this是o
var method = o.setName;
method('jobs'); // 进入func,this是window
var obj = new func('jobs');// 进入func,this是obj

通过函数的call、apply、bind方法进行调用可改变作用域,call和apply只是传递参数的方式不同。

var o = {};
func.call(o, 'jobs'); // 绑定作用域为o
func.apply(o, ['jobs']);
func.bind(o)('jobs');

模拟类

函数名的第一个字母大写,表示是用来构造对象的,不是用来执行的。

用方式一模拟类,o1和o2有自己的name属性,但第三个输出结果为false,这说明o1.sayHello和o2.sayHello指向不同内存,这样存在内存的浪费。

// 方式一
var Person = function(name) {
    this.name = name;
    this.sayHello = function() {};
};
var o1 = {}, o2 = {};
Person.call(o1, 'jobs');
Person.call(o2, 'gates');
console.log(o1.name); // 输出jobs
console.log(o2.name); // 输出gates
console.log(o1.sayHello === o2.sayHello); // 输出false

用方式一模拟类,第三个输出结果为true,这时不存在内存浪费了,但破坏了封装,在外部也可以调用sayHello方法。

// 方式二
function sayHello() {}
var Person = function(name) {
    this.name = name;
    this.sayHello = sayHello;
};
var o1 = {}, o2 = {};
Person.call(o1, 'jobs');
Person.call(o2, 'gates');
console.log(o1.name); // 输出jobs
console.log(o2.name); // 输出gates
console.log(o1.sayHello === o2.sayHello); // 输出true

用方式三模拟类,通过即时函数封装作用域后,可以比较好的模拟类了。

// 方式三
var Person = function() {
    // private,在外部无法直接调用
    function sayHello() {}
    var Person = function(name) {
        this.name = name;
        this.sayHello = sayHello;
    };
    // public 
    return Person;
}(); // 即时函数,封装作用域
var o1 = {}, o2 = {};
Person.call(o1, 'jobs');
Person.call(o2, 'gates');
console.log(o1.sayHello === o2.sayHello); // 输出true

用方式四模拟类,在即时函数封装作用域的基础上,又添加了命名空间。

// 方式四
// 模仿命名空间,可封装类库给别人使用
var cplh = cplh || {};
// 模仿封装
(function(g) {
    // 模仿private,没有加到Person上
    function sayHello() {}
    var Person = function(name) {
        this.name = name;
        this.sayHello = sayHello;
    };
    // 模仿public 
    g.Person = Person;
})(cplh); // 加上括号来说明这是一个表达式
// 模仿初始化
var o1 = {}, o2 = {};
cplh.Person.call(o1, 'jobs');
cplh.Person.call(o2, 'gates');
console.log(o1.sayHello === o2.sayHello); // 输出true

原型

所有对象都有原型对象__proto__,只有函数对象有原型属性prototype,当这个函数被用作构造函数来创建实例时,该函数的原型属性(prototype)将被作为原型赋值给所有对象实例,也就达到了重用方法的作用,即所有实例的原型引用的是函数的原型属性

var i = 5;
var str = 'abc';
var Person = function() {};
var person = new Person();
// 所有对象都有原型对象
i.__proto__ // 有 [Number: 0]
str.__proto__ // 有 [String: '']
Person.__proto__ // 有 [Function]
// 只有函数对象有原型属性
i.prototype // undefined
str.prototype // undefined
Person.prototype // 有 [Object]
// 所有实例的原型引用的是函数的原型属性
console.log(i.__proto__ === Number.prototype) // 输出true
console.log(str.__proto__ === String.prototype) // 输出true
console.log(person.__proto__ === Person.prototype) // 输出true

在执行完 var Person = function() {}; 语句时,会分配三块内存,Person变量的内存、function函数的内存、protptype指向对象的内存,这个对象的constructor又指向Person指向的函数。

var Person = function() {};
console.log(Person);
console.log(Person.prototype); // 输出一个Object,有constructor属性
console.log(Person.prototype.constructor === Person); // true
内存

JavaScript是动态语言,取决于运行时,不是定义时,因此下面的代码中示例一的对象o是有sayHello方法的。通过如示例二、示例三、示例四的方式给Function、String、jquery等全局函数的prototype添加方法来实现共有的新功能。

// 示例一
var o = {};
o.__proto__ = Person.prototype;
console.log(o instanceof Person); // 输出 true
Person.prototype.sayHello = function() {};
console.log(o.sayHello); // 输出 function() {}
// 示例二
Function.prototype.method = function(name,fn) {
    this.prototype[name] = fn;
    return this;
};
function Person (name) {
    this.name = name;
};
Person.method('sayHello',function() {
     return 'Hello,' + this.name;
}).method('show',function() {
     return 'show';
});
var jobs = new Person('jobs');
console.log('Hello,jobs' === jobs.sayHello()); // 输出 true
console.log('show' === jobs.show()); // 输出 true
// 示例三
String.prototype.fill = function(id){
    // var sql = "select * from " + this;
    $.getJSON('/' + this, null functon(json, textStatus){
        var data = JSON.parse(json);
        $(id).append(data);
    });
};
// 给id为wellTable01的元素添加从wells表中或接口中查出的数据
"wells".fill('#wellTable01');
// 示例四
(function(){
    var myMethod = function() {
        console.log('自定义方法');
    };
    $.prototype.myMethod = myMethod;
})();

实现类

利用原型实现类,注意下面方式一到方式四的区别,方式一到方式三的o对象已经有了sayHello方法,达到了重用方法的目的,但使用了__proto__,理论上JavaScript中是不让用的。

// 方式一
// 构造函数
var Person = function(name) {
    this.name = name;
};
// 方法体
var proto = {
    sayHello: function(){}
};
// 创建对象
var o = {};
// 赋值方法
o.__proto__ = proto;
// 初始化
Person.call(o, 'jobs');
console.log(o.sayHello);

方式二只是将proto作为Person的属性使用的。

// 方式二
// 构造函数
var Person = function(name) {
    this.name = name;
};
// 方法体
Person.proto = {
    sayHello: function(){}
};
// 创建对象
var o = {};
// 赋值方法
o.__proto__ = Person.proto;
// 初始化
Person.call(o, 'jobs');
console.log(o.sayHello);
// 方式三
// 构造函数
var Person = function(name) {
    this.name = name;
};
// 方法体
Person.prototype = {
    sayHello: function(){}
};
// 创建对象
var o = {};
// 赋值方法
o.__proto__ = Person.prototype;
// 初始化
Person.call(o, 'jobs');
console.log(o.sayHello);
// 方式四
// 构造函数
var Person = function(name) {
    this.name = name;
};
// 方法体
Person.prototype = {
    sayHello: function(){}
};
var o = new Person('jobs');
console.log(Person.prototype.sayHello);

方式四使用new创建的对象,也就是说函数Person被用作构造函数来创建实例了,这时该函数prototype将被作为原型__proto__赋值给对象的实例,也就相当于如下代码。注意当函数名的第一个字母大写时,表示是用来构造对象的,不是用来执行的,该函数的prototype是给构造出来的对象用的,函数自己用不了。

// var o = new Person('jobs'); 等同于以下代码
// 创建对象
var o = {};
// 赋值方法
o.__proto__ = Person.prototype;
// 初始化
Person.call(o, 'jobs');

当定义一个当函数被用作构造函时,这个函数会默认在函数体开头和结尾添加如下代码,如果函数return的不是对象,在使用new实例化对象时这个return语句会被忽略,因为需要返回一个对象。

function Class() {
    // 默认添加
    var this = {};
    ...
    // 会被忽略,因为需要返回对象
    return 1;
    ...
    // 默认添加
    return this;
}

实现继承

封装自己的框架,要有类、方法、复用。

function Class(options) {
    var Klass = function() {
        // 如果Class有初始化
        if(typeof this.initialize === 'function'){
            this.initialize.apply(this, arguments);
        }
    };
    for(var attr in options) {
        Klass.prototype[attr] = options[attr];
    }
    Klass.extends = function(proto) {
        // 定义子类
        var Child = function() {
            Klass.apply(this, arguments);
        };

        // var f = {};
        // f.__proto__ = this.prototype;
        // // 相当于上面的,不能使用__proto__
        // var f = new Klass();

        // Child.prototype = f;

        // 继承基类
        // 不能使用 Child.prototype = this.prototype; 这样extends添加的方法会影响到基类
        var F = function() {
            /*
            这是定义F函数的原因,如果不显示定义constructor的指向,constructor将指向Object,通过constructor已经无法确定对象的类型了。
            这样显示定义constructor属性的在实例中是可枚举的。
            */
            this.constructor = Child;
        };
        F.prototype = this.prototype;// 继承
        var f = new F();
        Child.prototype = f;

        // 类的实例方法
        for(var attr2 in proto) {
            Child.prototype[attr2] = proto[attr2];
        }
        return Child;
    };
    return Klass;
};

// 方式一
var Person1 = new Class();// 函数对象可以执行
var jobs1 = new Person();
// 方式二
var Person2 = new Class({
    show: function() {}
});
var jobs2 = new Person();
// 方式三
var Person3 = new Class({
    show: function() {}
});
var Actor = Person3.extends({
    sayHello: function(){}
});
var jobs3 = new Actor();
我对继承的理解

封装商业框架,有Model和View。

 var Adobe = (function() {
    var Model = function() {};
    // 实例方法
    Model.prototype = {
        constructor: Model,
        get: function() {},
        set: function() {}
    }
    // 绑定作用域,在context作用域上执行func函数
    function proxy (func, context) {
        return function() {
            func.apply(context, arguments);
        };
    }

    var View = function () {
        //Hack: 绑定事件,这段代码不明白
        var events = this.events || {};
        for(var item in events){
            var func = proxy(this[events[item]], this);
            var reg = /(.+)\s+(.+)/;
            var result = reg.exec(item);
            $(result[2]).bind(result[1], func);
        }
    };

    View.extend = Model.extend = function(options) {
        var Parent = this;
        // 构造函数
        var Klass = function() {
            if(typeof this.initialize === 'function'){
                this.initialize.apply(this, arguments);
            }
            Parent.apply(this, arguments);
        };
        // 继承
        var F = function() {
            this.constructor = Klass;
        };
        F.prototype = this.prototype;
        Klass.prototype = new F();
        Klass.prototype.__super__ = this.prototype;
        // 扩展
        for(var name in options){
            Klass.prototype[name] = options[name];
        }
        return Klass;
    };
    return {
        Model: Model,
        View: View
    }
})();

// 使用代码
var Todo = Adobe.Model.extend({
    initialize: function(title) {
        if(typeof title === 'string'){
            this.set({title: title});
        }
    }
});
var todo = new Todo('Test');
console.log(typeof todo.get === 'function');
console.log(typeof todo.set === 'function');
console.log(typeof todo.initialize === 'function');

相关文章

  • React Native 学习资料汇总

    JavaScript: 1、JavaScript-语言基础速览 2、JavaScript 中文教程 3、JavaS...

  • JavaScript-基础2

    最近参加了一次为时两天的JavaScript培训,回顾课程内容并进行整理,其中参考了如下两篇老师的文章。 Java...

  • javascript-事件基础和事件绑定

    javascript-事件基础和事件绑定 一、事件驱动 1.1事件 javascript侦测到的用户的操作(点击、...

  • JavaScript-基础

    JavaScript组成 ECMAScript:将人类的语言翻译为计算机语言(计算机只能识别0/1),同时将计算机...

  • 01、JavaScript-基础

    每天一句:没有方向感,我们不知道自己走向哪里。没有方向感,我们所有的努力就缺乏一个标准,我们每时每刻所有的努力都处...

  • JavaScript-基础概念

    柯里化 柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且...

  • 作用域链,闭包,原型链

    Javascript-基础概念总结 【作用域】javascript中的作用域可以理解为一个语句执行的环境大小,有全...

  • JavaScript-字符串对象方法应用

    JavaScript-字符串对象方法应用

  • Javascript 机器学习资源汇总

    翻译 Machine Learning in Javascript- A compilation of Resou...

  • JavaScript-逻辑元算基础

    逻辑运算又称布尔代数,就是布尔值的"算数"运算。逻辑运算符包括:1,逻辑与(&&) 2,逻辑或( || ) 3,逻...

网友评论

      本文标题:JavaScript-基础2

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