Fork me on GitHub
image frame

Walter

面朝大海 春暖花开

MySQL 8.0+重置密码

关闭数据库

1、在终端中输入:

sudo /usr/local/mysql/support-files mysql.server stop

2、在访达中找到安装好的mysql,关闭mysql运行。

跳过验证

// 进入数据库指令文件
cd /usr/local/mysql/bin

// 跳过权限认证
sudo ./mysqld_safe --skip-grant-tables

免密码进入数据库

在上述指令运行后,新开一个终端,同时保持原来那个终端也开着,在新的终端输入指令如下:

//  执行mysql指令
/usr/local/mysql/bin/mysql

// 进入名为<mysql>的数据库
use mysql;

把之前密码清空:
update user set authentication_string=''  where user='root';

// 刷新权限
flush privileges;

// 修改密码 但不适用于8.0+的版本
// set password for 'root'@'localhost' = password('新的密码');

// 8.0+版本修改密码
ALTER user 'root'@'localhost' indentified by '新密码';

//刷新一下
flush privileges;

// 退出mysql
exit

mysql常用命令:

1、进入本地数据库:
/usr/local/mysql/bin/mysql -u root -p 根据提示输入密码

2、进入远程数据库:
假设远程主机的IP为:23.106.134.88,用户名为root,密码为123456:
mysql -h23.106.134.88 -u root -p 根据提示输入密码。

3、启动mysql服务:
sudo /usr/local/mysql/support-files/mysql.server start;

4、终止mysql服务:
sudo /usr/local/mysql/support-files/mysql.server stop;

5、重启mysql服务:
sudo /usr/local/mysql/support-files/mysql.server restart;

ESC服务器Linux下安装mysql

1、重启服务器:

[root@sir-xiao server]# reboot

2、检查是否已经安装mysql服务:

[root@sir-xiao server]# yum list installed | grep mysql

如果显示一下内容,则表示没有安装:-bash: gerp: command not found
3、下载mysql安装包:

[root@sir-xiao server]# rpm -ivh http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm

4、安装mysql:

[root@sir-xiao server]# yum install -y mysql-server
或者
[root@sir-xiao server]# yum install mysql-community-server

5、设置开机启动:

[root@sir-xiao server]# systemctl enable mysqld.service

6、查看服务是否加入开机自动启动:

[root@sir-xiao server]# systemctl list-unit-files | grep mysqld

7、查看mysql的默认临时密码:

[root@sir-xiao server]# systemctl list-unit-files | grep mysqld

8、root用户登录mysql数据库:

[root@sir-xiao server]# mysql -u root -p 
Enter password: 
根据提示 输入密码

9、修改root用户密码:

mysql> use  mysql;----切换到mysql db

mysql> UPDATE user SET authentication_string = PASSWORD('newpass') WHERE user = 'root';---新版本mysql执行

mysql> UPDATE user SET Password = PASSWORD('newpass') WHERE user = 'root';---老版本mysql执行

mysql> flush privileges;---命令立即执行生效

10、忘记密码重置密码:

1、查找mysql的启动文件:[root@sir-xiao etc]# whereis  my

2、vim /etc/my.cnf
修改my.cnf启动参数,在文件的最后增加,mysql启动参数—— --skip-grant-tables。启动
mysql时不启动授权表 grant-tables
[root@sir-xiao etc]# vi /etc/my.cnf skip-grant-tables
[root@sir-xiao etc]# :wq
vi模式Enter进入,按“i”键,编辑文件,按“esc”键,输入:wq写入保存退出,:q 不保存退出

3、重启mysql服务

4、修改密码:同上

5、密码修改成功后需要进入vi模式下重新编辑/etc/my.cnf去掉增加的skip-grant-tables 重启mysql服务,就可以用密码登录了

Nginx同域名下配置多项目

webpack.config.js

build: {
    index: path.resolve(__dirname, '../dist/index.html'),
    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/admin/',
    productionSourceMap: true,
    devtool: '#source-map',
    productionGzip: true,
    productionGzipExtensions: ['js', 'css'],
    bundleAnalyzerReport: process.env.npm_config_report
  }

把 assetsPublicPath修改为你在nginx配置的路径。

router.js

//base要和上面配置的assetsPublicPath配置的一样
 mode:'history',
 base: '/admin/',

配置nginx:

keepalive_timeout  65;
gzip  on;//开启Gzip压缩
gzip_disable 'msie6'; #不使用gzip IE6
gzip_min_length 100k; #gzip压缩最小文件大小,超出进行压缩(自行调节)
gzip_buffers 4 16k; #buffer 不用修改
gzip_comp_level 3; #压缩级别:1-10,数字越大压缩的越好,时间也越长
server {
    listen 80;
    server_name www.xxx.cn;//配置你的域名
    location /{//配置默认的项目
         root /data/www/web;
         try_files $uri $uri/ /index.html;
         index index.html index.htm;
         #proxy_pass http://127.0.0.1:8080;
     }
     location ^~/admin{//配置 域名/admin 对应的项目
         alias /data/www/admin/;//别名 配置项目文件路径
         try_files $uri $uri/ /index.html;
         index index.html index.htm;
         if (!-e $request_filename) {//防止二级路由下页面reload空白页面
             rewrite ^/(.*) /admin/index.html last;
             break;
         }
         #proxy_pass http://127.0.0.1:8080;
     }

Nginx下配置Vue项目

1、配置选择:

  • Linux系统:Centos 6.5 x64
  • Nginx版本:1.7.8

2、安装nginx:

cd /usr/local
//下载nginx
wget http://nginx.org/download/nginx-1.7.8.tar.gz
cd nginx-1.7.8
make
make install

3、开启nginx服务:

/usr/local/nginx-1.7.8/sbin/nginx

重启或关闭进程:

/usr/local/nginx-1.7.8/sbin/nginx -s reload

/usr/local/nginx-1.7.8/sbin/nginx -s stop

4、关闭防火墙:

service iptables stop

5、修改配置文件:

usr/local/nginx-1.7.8/conf/nginx.conf

server {
    listen 80;//监听80端口
    server_name www.yangkunxiao.cn;   //项目对应域名
    location /{
     root /data/www/dist;     //项目存放路径
     try_files $uri $uri/ /index.html;    //vue项目配置history模式
     index index.html index.htm;    //项目首页名称
     #proxy_pass http://127.0.0.1:8080;   //域名对应URL,这个URL对应的就是http://m.horace.space,可通过域名直接访问
    }
    location /assets/{//配置资源文件夹
        root /usr/;//资源文件夹路径
        autoindex on;
    }
 }

7、多个项目配置多个service即可
6、重启即可

Nginx下配置静态资源

1、查看nginx安装路径:

whereis nginx

2、新建静态资源存储文件夹:

mkdir assets

3、进入nginx目录中,修改nginx.conf文件:

cd nginx: /usr/local/nginx/conf

vim nginx.conf

具体配置如下:
server {
    listen 80;
    server_name www.yangkunxiao.cn;
    location /{
     root /data/www/dist;
     try_files $uri $uri/ /index.html;
     index index.html index.htm;
     #proxy_pass http://127.0.0.1:8080;
    }
    location /assets/{
        root /usr/;
        autoindex on;
    }
 }

4、保存并重启nginx

php和mySql

简介

通过PHP,开发者可以和数据库相连。
在PHP5.0版本以后,官方推荐一下两种方式进行PHP和数据库的连接:

  • MySQLi extension (“i” 意为 improved):MySQLi 只针对 MySQL 数据库
  • PDO (PHP Data Objects):PDO 应用在 12 种不同数据库中
    因为我这里只安装了mySql,所以本文栗子均以第一种方式进行操作。

连接数据库

在访问数据库之前,我们需要进行数据库的连接

$serverName = 'localhost';//数据库地址
$serverRoot = 'root';//数据库登录名称
$passWord = '';//数据库密码
$dbName = "myDB";
$conn = new mysqli($serverName,$userName,$userPassword);
//连接失败
if($conn->connect_error){
    die("数据库连接失败:".$conn->connect_error)
}
mysqli_set_charset($this->conn, "utf8");//设置字符编码为utf8格式 否则汉字可能出现乱码的情况
//数据库操作完成之后 一定记得关闭数据库的连接
$conn->close();

操作数据库

1、创建数据库:$sql = "CREATE DATABASE IF NOT EXISTS dbname"
2、创建表:上面的连接语句要修改下,加个参数,用来选择数据库:

$conn = new mysqli($serverName,$userName,$userPassword,$dbName);
$sql = "CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, 
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
)";

属性说明:

  • NOT NULL - 每一行都必须含有值(不能为空),null 值是不允许的。
  • DEFAULT value - 设置默认值
  • UNSIGNED - 使用无符号数值类型,0 及正数
  • AUTO INCREMENT - 设置 MySQL 字段的值在新增记录时每次自动增长 1
  • PRIMARY KEY - 设置数据表中每条记录的唯一标识。 通常列的 PRIMARY KEY 设置为 ID 数值,与 AUTO_INCREMENT 一起使用。
    每个表都应该有一个主键(本列为 “id” 列),主键必须包含唯一的值

3、插入数据:$sql='INSERT INTO table_name (column1, column2, column3,...) VALUES (value1, value2, value3,...)'
4、插入多条数据:

$sql = "INSERT INTO student (column1, column2, column3,...)
VALUES (value1, value2, value3,...);";
$sql.="INSERT INTO student (column1, column2, column3,...)
VALUES (value1, value2, value3,...);";
$sql.="INSERT INTO student (column1, column2, column3,...)
VALUES (value1, value2, value3,...);";

注意:sql语句之间用;隔开

5、查询数据:$sql = 'SELECT * FROM myDB;'
6、更新数据:
UPDATE table_name SET column1=value, column2=value2,... WHERE some_column=some_value
7、删除数据:DELETE FROM table_name WHERE some_column = some_value

php基础

PHP

(全称:PHP:Hypertext Preprocessor,即”PHP:超文本预处理器”)是一种通用开源脚本语言。

PHP的作用

  • PHP 可以生成动态页面内容
  • PHP 可以创建、打开、读取、写入、关闭服务器上的文件
    • PHP 可以收集表单数据
    • PHP 可以发送和接收 cookies
    • PHP 可以添加、删除、修改您的数据库中的数据
    • PHP 可以限制用户访问您的网站上的一些页面
    • PHP 可以加密数据

变量

<?php
    $x=5;
    $y=6;
    $z=$x+$y;
    echo $z;
?>

变量作用域

PHP有四种不同的变量作用域:

  • local:局部作用域。在 PHP 函数内部声明的变量是局部变量,仅能在函数内部访问
  • global:全局作用域。在所有函数外部定义的变量,拥有全局作用域。除了函数外,全局变量可以被脚本中的任何部分访问,要在一个函数中访问一个全局变量,需要使用 global 关键字。
  • static:当一个函数完成时,它的所有变量通常都会被删除。然而,有时候我们希望某个局部变量不要被删除,可以使用static
  • parameter:参数作用域。参数是通过调用代码将值传递给函数的局部变量。参数是在参数列表中声明的,作为函数声明的一部分

局部和全局作用域

$x = 5;//全局变量
function test(){
    global $x;//使用global关键字 使用全局变量

    static $n = 0;//局部变量

    $y = 10;//局部变量

    $n++;

    echo 'x: '.$x.'<br/>';

    echo 'y: '.$y.'<br/>';

    echo $n.'<br />';
};

test();

test();

test();

常量

常量值被定义后,在脚本的其他任何地方都不能被改变。常量是一个简单值的标识符。该值在脚本中不能改变。
一个常量由英文字母、下划线、和数字组成,但数字不能作为首字母出现。 (常量名不需要加 $ 修饰符)。
设置常量,使用 define() 函数,函数语法如下:

bool define ( string $name , mixed $value [, bool $case_insensitive = false ] )
该函数有三个参数:

  • name:必选参数,常量名称,即标志符
  • value:必选参数,常量的值
  • case_insensitive :可选参数,如果设置为 TRUE,该常量则大小写不敏感。默认是大小写敏感的
<?php
define("GREETING", "欢迎访问 Runoob.com");
echo GREETING;    // 输出 "欢迎访问 Runoob.com"
echo '<br>';
echo greeting;   // 输出 "greeting"
?>

数据类型

  • 字符串
  • 整型:整数是一个没有小数的数字
  • 浮点型:浮点数是带小数部分的数字,或是指数形式。
  • 布尔型
  • 数组
  • 对象:在 PHP 中,对象必须声明。首先,你必须使用class关键字声明类对象。类是可以包含属性和方法的结构。然后我们在类中定义数据类型,然后在实例化的类中使用数据类型
  • NULL

Hexo博客搭建

Hexo

Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。

安装

  • Node(本文不做多余叙述)

  • Git(本文不做多余叙述)

  • Hexo
    $ npm install -g hexo-cli
    安装完成后,到一个新的文件夹下,打开Git Bash,执行一下命令:

    $ hexo init <folder>
    $ cd <folder>
    $ npm install

    新建完成之后,打开新建的项目,查看项目目录:

  • public:执行hexo generate命令,生成的HTML静态文件

  • scaffolds:模版文件夹

  • source:资源文件夹,文章源码目录,该目录下的markdown和html文件均会被hexo处理。该页面对应repo的根目录,404文件、favicon.ico文件,CNAME文件等都应该放这里

    • _data:你的数据文件夹,例如:
      • friends.json:存放友情链接的数据
      • musics.json:存放音乐播放器的数据
    • _posts:文章存放文件夹
    • categories:分类
    • tags:标签
      • thems:主题文件夹,Hexo 会根据主题来生成静态页面。以我当前使用的metery主题为例
      • langauages:存放语言相关设置的yml文件
      • layout:存放文章的ejs模版,可以在里面修改相关的HTML、CSS、JS等
      • source:该主题下使用的资源文件夹
      • _config.yml:该主题的配置文件
  • _config.yml:该项目的配置文件
  • db.json:source解析所得到的
  • package.json:项目所需模块项目的配置信息

部署

  • GitHub上新建一个项目
  • 本地Git Bash上配置github账户信息
    • git config --global user.name 'yourName'
    • git config --global user.email 'yourEmail'
      • 创建SSH
      • 在gitbash中输入:
      • ssh-keygen -t rsa -C "youremail@example.com,生成ssh。
      • cd ~/.ssh 找到id_rsa.pub
      • cat id_rsa.pub复制其中的内容到github上,创建一个新的 SSH KEY
  • 修改_config.yml
    deploy:
    type: git
    repo: https://github.com/YourgithubName/YourgithubName.github.io.git
    branch: master
  • 回到Git Bash
    npm i hexo-server
    hexo clean
    hexo generate
    hexo server
  • 上传到GitHub
    npm install hexo-deployer-git --save
    hexo clean
    hexo generate
    hexo deploy
    注意deploy的过程中要输入你的username及passward。
    然后在浏览器中输入 http://yourgithubname.github.io 就可以看到你的个人博客。

作用域和作用域链

作用域

在JavaScript中的作用域有全局作用域局部作用域(在JavaScript中局部作用域即是函数作用域)以及块级作用域

  • 全局作用域:在最外层函数定义的变量即拥有全局作用域,对于任意函数来说,都可以访问到。例如:

    {
      var a = 1;
      var fn = function(){
        console.log(a);//1
      }
      fn();
    }
  • 局部作用域:和全局作用域相反,局部作用域的变量即是在特定代码块中才能过访问,对于外部是不能够访问的。注意:在函数内部定义变量的时候,如果不用var,那么你声明的就是全局变量了。

    {
      var a = 1;
      var fn = function(){
        var b = 2;
        console.log(a)//1
      }
      fn();
      console.log(b);//b is not defined
    }
  • 块级作用域:在代码块中使用let定义的变量,只能在当前代码块中进行访问。块级作用域可以形成暂时性死区。

    var fn = function(){
      for(let i = 0; i < 10; i++){
        console.log(i);//1-9
      }
      console.log(i);//undefined
    }
    fn()

作用域链

个人理解,作用域就是在函数内部可以访问外部变量的机制,使用链式查找哪些变量可以被函数内部访问。说起作用域链,那么不得不说执行环境了。

执行环境(Execution Context)

EC是JavaScript中一个最为重要的概念。EC定义了变量和函数有权访问的其他数据。JavaScript中,函数在运行时都会产生一个执行环境,并且JS引擎还会产生一个与当前EC相关联的变量对象(Variable Object,即VO)。EC中所有定义的变量和方法都包含在VO中。全局执行环境是最外围的执行环境,它是一个“兜底”的执行环境。

JS引擎在进入一段可执行的代码时,需要完成以下三个初始化工作:

首先,创建一个全局对象(Global Object,即GO),将Math、String、Data等常用的js对象作为其属性,但是这个GO在全局是不可见的,不可直接访问的。因此它还有另外一个属性window,并将window指向了自身,这样就可以在全局通过访问window,来访问GO的属性和方法了。

var globalObject = {
  Math,
  String,
  Data,
  Function,
  ...
  window:this
}

其次,JS引擎会创建一个执行环境栈(Execution Context Stack 即ECS),与此同时还会创建一个全局环境EC。当JS的执行流执行到一个函数时,JS引擎就会把该函数的EC推到ECS中,当函数执行完之后,再把EC从ECS中弹出,将执行流的控制权交还给上一层的EC。ECMAScript的执行流就是由这种机制控制着。

var ecStack = [];
//执行到函数fn
ecStock.push(EC);
//执行完fn
ecStack.pop(EC);

最后,JS引擎会创造一个和EC相关连的变量对象VO。如果这个环境是一个函数,则将其活动对象(Action Object,即AO)作为其变量对象。初始时AO只包含一个变量,即arguments。作用域链的下一个变量对象来自于外部包含环境,而下一个变量对象来自下一个包含环境,这样一直延伸到全局环境。全局变量对象(GO)始终都是作用域链的最后一个对象。

每一个函数在定义的时候,都会创建一个与之关联的[[scopes]]属性,该scope总是指向定义函数时的执行环境EC。举个🌰:

var fn = function(){};
console.dir(fn);

scopes

如上图所示,函数fn的[[scopes]][0]即是它的执行环境,GO。

再看:

var a = 1;
var fn = function(){
  var b = a + 1;
  return function(){
    return a + b + 1;
  }
}
var t = fn();
console.dir(t)

scopes

fn中始终都没有定义变量a,那么JS引擎就会沿着Scope Chain一直向上寻找a,最终在GO中找到了a。

作用域链

当JavaScript的代码块在运行时,就会创建与之相关的作用域链。

作用域链的作用就是保证当前环境对其有权访问的变量和方法进行有序的访问 ——JavaScript高级程序设计

作用域链的前端(也就是开头)就是当前执行环境EC的变量对象,它的变量对象来自于它的外部包含环境,再下一个变量对象同时也来自再下一个外部包含环境,这样一直延伸到全局执行环境,同时,GO也是作用域链的最后一个对象。

标识符解析

当在某个环境中为了读取或写入从而引入一个标识符时,必须通过搜索来确定该标识符代表了什么。搜索过程从当期作用域链的前端开始,向上逐级搜索,如果在局部环境中查找到了该标识符的定义,则停止搜索‘否则将一直沿着作用域链向上查找,直到找到GO上。如果找不到,则会报错。

延长作用域

虽然作用域只有两种:全局和局部(函数),但是还是有其他办法可以用来延长作用域链。其主要思路就是:有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象在代码执行后就被销毁。

1、try-catch语句的catch块

2、with语句

闭包

什么是闭包?个人理解就是由于函数的嵌套,并且对外提供访问接口就会产生闭包。

var fn = function(){
  var b = 2;
  return function(){
    return b
  }
}
var t = fn();
console.log(t());//2

在上面的代码中,正常情况下,函数fn执行完之后,应该JS的垃圾回收机制被标记“等待清除”,等待下一次垃圾回收机制执行的时候被清除,但是由于返回函数中引用了fn中的变量b,所以函数fn并不会被清除,而是一直保存着内存中,直到没有任何引用,才会被清除。

从上面的🌰我们可以得出,闭包有什么作用呢?

  • 闭包可以使得程序在函数外部可以访问到函数内部的变量
  • 闭包可以在内存中维持一个变量
var fn = function(){
  var array = new Array();
  for(var i = 0; i < 10; i++){
    array[i] = function(){
      return i
    }
  }
    return array
}
var arr = fn();
console.log(arr[0]());//10
console.log(arr[1]());//10

正常情况下,我们期待的结果就是返回0和1,但是结果出人意料。为什么呢?

其实我们可以先看下arr[0]执行时,它的scope chain:

scopes

我们可以清楚的看到,此时arr[0]中并没有i的定义,它会沿着作用域链向上找i,那么就会找到fn中的i,但是此时,i已经变成10了。

从此我们也可以得出一个结论:

JS的函数中的变量的值不是在编译的时候确定的,而是在运行时再去寻找的。

如果我们想让上面的🌰按照我们预期的执行,那么考虑使用立即执行函数。

var fn = function(){
  var array = new Array();
  for(var i = 0; i < 10; i++){
    array[i] = (function(num){//函数参数的传递是按值传递,会创建i的副本,而不是直接使用变量i
      return num
    })(i)
  }
  return array
}
var arr = fn();
console.log(arr[0])
console.log(arr[1])

使用原生JavaScript实现call、apply和bind

思路

在JavaScript中this的指向问题中提到,作为对象方法的调用。函数还可以作为某个对象的方法调用,这时this就指这个上级对象。也就是我们平时说的,谁调用,this就指向谁。

实现方法:在传入的参数中传入一个方法,然后执行这个方法,最后删除该方法(为了保持对象的前后一致性)。

call

var obj = {
    age ; 10
}
var fn = function(){
    this.value = 100;
}
/**
将newCall绑定在Function的原型上 context:上下文 即this
...rest ES6语法糖 用来取代arguments
*/
Function.prototype.newCall = function(context,...rest){
    if( context instanceof Object ){
        context = context || window;
    }else{
        context = Object.create(null);
    }
    //使用symbal 避免原来的context上有函数fn
    const fn = Symbal();
    context[fn] = this;//此时this 谁调用 指向谁
    context[fn](...rest);//context 调用fn,改变this指向
    delete context[fn];//删除fn属性 保持原对象的统一
}
var fun = function(){
    console.log(this.age);//10
    // fn.newCall(this);
    //console.log(this.value); 100
}

fun.newCall(fn);

apply

和call类似,只是传参的不同,不多说 ,直接上代码:

Function.prototype.newCall = function(context,parames){
    if( context instanceof Object ){
        context = context || window;
    }else{
        context = Object.create(null);
    }
    //使用symbal 避免原来的context上有函数fn
    const fn = Symbal();
    context[fn] = this;//此时this 谁调用 指向谁
    context[fn](parames);//context 调用fn,改变this指向
    delete context[fn];//删除fn属性 保持原对象的统一
}

bind

bind和call、apply的区别,这里不再多做赘述。直接上代码:

Function.prototype.bind = function (context,...innerArgs) {
  var self = this
  return function (...finnalyArgs) {
    return self.call(context,...innerArgs,...finnalyArgs)
  }
}

JavaScript原型、原型链和继承

原型对象

无论我们什么时候创建一个函数,它都会有一个属性prototype,该属性是一个指针,它指向函数的原型对象,该原型对象所拥有的属性和方法都可被函数的实例所共享。举个🌰:

var fn = function(){
alert(0)
};
fn.prototype.name = 'a';
fn.prototype.todo = function(){
alert('todo')
};
var newFn = new fn();
console.dir(fn)//查看fn所有的属性和方法
console.log(newFn.name);//'a'
newFn.todo();

所有的原型对象都会自动拥有一个属性constructor(构造函数),这个属性包含一个指向构造函数的指针。即:

fn.prototype.constructor = fn;

当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版中管这个指针叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。——JavaScript高级程序设计

该属性是实例和构造函数的原型对象之间的联系,和构造函数并无直接关联,其实实例之所以可以调用构造函数的原型对象上面的属性和方法,也是通过该属性实现的。

小结:

  • 所有的引用类型都可以自由的扩展其属性(除null)
  • 所有的引用类型都有_proto_属性(除null)
  • 所有的对象都有prototype属性
  • 所有的引用类型的_proto_都指向它们的构造函数的prototype
  • 当寻找一个引用类型的属性时,如果在当前对象找不到该属性的定义,就会沿着_proro_一直向上寻找,直到找到为止或者找到Object.prototype(即null)为止。

注意:大家可能已经注意到,函数既有prototype属性,也有_proto_属性。下面就针对函数做一个解释:

var Fn = function(){};
Fn.prototype.colors = ['red','yellow'];
Fn.prototype.todo = function(){
alert('todo')
};

var fn1 = new Fn();
console.log(Object.getPrototypeOf(fn1))//Fn.prototype
console.log(Object.getPrototypeOf(Object.getPrototypeOf(fn1)))//Object.prototye
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(fn1))))//null

请记住:一切引用类型都有一个隐式原型Object.prototype。

原型链

原型对象的问题

原型对象最大的问题就是因其共享性所导致的。看个🌰:

//原型
var Fn = function(){};
Fn.prototype.colors = ['red','yellow'];
Fn.prototype.todo = function(){
alert('todo')
};

var fn1 = new Fn();
fn1.colors.push('blue');

var fn2 = new Fn();
console.log(fn2.colors);// ["red", "yellow", "blue"]

为什么要使用原型对象

首先,看一段代码:

//新建一个构造函数 
var Person = function (){ 
drink(){ 
console.log('drink') 
} 
}; 
Person.prototype.eat = function(){ 
console.log('eat') 
} 
var person = new Person();
person.eat();

在这段代码中,我们分别在Person和Person.prototye上挂载了 eat 和 drink 函数,然后使用 new 关键字对构造函数 Person 进行了实例化。

对于 drink 函数:每进行一次实例化,都要重新在内存中占用一些资源。

对于 eat 函数:我们将 eat 函数挂载在 Person 的原型上,Person 的实例每次只需要调用原型上的方法即可,节约了内存占用。

继承

ECMAScript中实现继承的主要就是依靠原型链来实现的。

原型链继承

最为ECMAScript最主要的继承方法,其基本思想就是让一个引用类型继承另一个引用类型的属性和方法。在上面我们说过,每一个构造函数都有一个原型对象,原型对象都有一个指向构造函数的指针constructor,每一个构造函数都可以生成一个实例,每一个实例都有一个指向原型对象的内部指针_proto_。简单来说就是:

var Fn = function(){};
var Test = function(){}
var fn = new Fn();
Fn.prototype.constructor = Fn;
fn._proto_ => Fn.prototype;

如果此时我们让一个Fn的原型对象指向另一个类型的实例呢?

Fn.prototype = new Test();

那么此时,原型对象就会有一个指向另一个原型的指针,另一个原型也会包含一个指向另一个构造函数的指针。如此层层递进,即构成了原型链。

function SuperType(){
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.Fun = function(){

};
function SubType(){
}
//继承了SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"

优点:可以通过 instanceOf 和 isPrototypeOf 检测

缺点:

1、父类型中的私有属性会变为子类型中的公有属性

2、创建子类型的时候,不能像父类型的构造函数中传递参数。

构造函数继承

JavaScript规定,每一个构造函数都有一个prototype属性,它指向一个对象,这个对象中的所有的属性和方法都会被构造函数的实例所继承。

构造函数的继承是通过new关键字,生成实例完成的。使用new关键字生成实例的过程中,就会把this绑定到实例上,具体过程如下:

  1. 在内存中先生成一个object的实例对象,

  2. 将实例对象的_proto_指向构造函数的prototype(即构造函数的原型),

  3. 运行构造函数,

  4. 检查返回值,如果返回值为基本数据类型,则无视该返回值,而将生成的对象返回。如果为引用类型,则将该返回值返回。

function SuperType(){
this.colors = ["red", "blue", "green"];
}

function SubType(name){
SuperType.call(this)
}

var s = new SubType('ykx');

s.colors.push('yellow');

console.log(s.colors);

var s2 = new Subtype('lhd');

console.log(s2.colors)

优点:

1、实例化子类型的时候可以传参

2、父类型中的属性不会变为公共的属性

缺点:虽然构造函数实现继承的方式比较好用,但是并不推荐这种方式。构造函数继承存在内存浪费的情况,每生成一个实例,都会占用一些内存。

寄生式继承

寄生式继承有点类似与工厂模式,即仅创建一个封装继承过程的函数,该函数在函数内部使用某种方式增强对象,最后再返回一个对象。

function inhertprototype(original){
var child = Object.creata(original);
child.say = function(){
alert(0)
}
return child;
}
var Person = {
age:10
}
var boy = inhertprototype(Person);
boy.say()

使用寄生式继承来为对象添加函数,不能够使函数得到更多的复用,降低了效率,类似于构造函数。

寄生组合式继承

function Man(age,name){
this.eat = function(something){
console.log(this.age + '岁的' + this.name + '正在吃: ' + something)
}
}

Man.prototype.drink = function(something){
console.log(this.name + '正在喝:' + something)
}
function Boy(age,name){
Man.call(this,age,name);//组合继承
this.age = age;
this.name = name;
}

/**

不推荐使用 Boy.prototype = new Man();//组合继承
因为在JavaScript中没有显式的constructor,
所以使用new关键字实例化的时候 该函数会被调用一次

*/

function inhertprototype (child,parent){
var prototype = Object.create(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}

//Boy.prototype = Object.create(Man.prototype);

//Boy.prototype.constructor = Boy;//避免原型错乱

inhertprototype(Boy,Man)

var boy = new Boy(12,'ykx');

boy.eat('apple');

boy.drink('water')

原型错乱:

function SuperType(){
}

function Sub(){
SuperType.call(this)
}

Sub.prototype = Object.create(SuperType.prototype);

console.dir(Sub);

结果如下:

原型错乱

正确的继承结果应该如下:

原型正常

JavaScript数据类型

JavaScript中有一下七种数据类型:Number、String、Null、Undefined、Bool、Object以及Symbal。

基本数据类型

Number、String、Null、Undefined、Bool。
JavaScript的基本数据类型的访问是按值访问的,因为基本数据类型的值是保存在栈中的。

引用数据类型

Object、Function、Array等。
JavaScript的引用数据类型的访问是按引用访问的,引用数据类型的值是存放在栈和堆中的:指针存放在栈中,值存放在堆中。栈区内存保存变量标识符和指向堆内存中该对象的指针,也可以说是该对象在堆内存的地址。

数据类型的区分

  1. typeof 检查
typeof   123   //Number

typeof   'abc'  //String

typeof    true       //Boolean

typeof    undefined   //Undefined

typeof    null        //Object     

typeof    { }           //Object

typeof    [ ]           //Object

typeof    console.log()       //Function

可以看出 typeof 是检查不出来 null、{} 、[] 的。
推荐使用一下方法:

判断基本类型:
Object.prototype.toString.call(null) //"[object Null]"

Object.prototype.toString.call([]) //"[object Array]"

Object.prototype.toString.call({}) //"[object Object]"

Object.prototype.toString.call(123) //"[object Number]"

Object.prototype.toString.call("123") //"[object String]"

Object.prototype.toString.call(undefined) //"[object Undefined]"

Object.prototype.toString.call(true) //"[object Boolean]"

判断引用类型:
Function fn(){console.log(“test”);}
Object.prototype.toString.call(fn);//"[object Function]"

var date = new Date();
Object.prototype.toString.call(date);//"[object Date]"

var reg = /[hbc]at/gi;
Object.prototype.toString.call(reg);//"[object RegExp]"

数据包装类型

数据包装类型包含:Number、String、Boolean。数据包装类型是特殊的引用类型,每当创建一个基本数据类型的时候,就会在后台创建一个对应的数据包装类型对象,用来使基本数据类型可以访问对象上的方法和属性。
举个🌰:

var str = '123';
str.split('');

//后台创建对应的数据包装类型
var str = new String('123')//创建str对象
str.split('');//执行方法
str = null//销毁str

而且,通过构造函数和对象字面量创建的对象是不一样的:

var str = '123';
typeof str // string
var str1 = new String('123');
typeof str1 // object

其他的 Boolean 和 Number 也和 String 类似。

为什么使用Object.prototype.toString.call()?

因为toString方法是Object原型上的方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象的调用toString方法的时候,调用的都是重写的toString方法,而不是Object原型链上的方法。所以如果要得到对象的具体的类型,需要调用Object原型上的toString方法,即使用call将toString的this指向改变。

var fun = function(){
    alert(0)
};
console.log(fun.toString())//返回的是 函数的字符串形式
delete Function.prototype.toString;
console.log(fun.toString())// 返回的是[object Function]

因为删除了Function.prototype上的toString方法之后,根据原型链,它会沿着原型链查找到Object.prototype.toString方法